第 18.19.3 節

std::atomic

0瀏覽次數0訪問次數--跳出率--平均停留

本節解決什麼問題

如果共享數據只是一個簡單計數器,可以用 std::atomic<int>。它能保證 ++counter 這樣的操作是線程安全的,不需要手動加鎖。

atomic 適合簡單獨立的原子操作。多個變量需要保持一致,或者需要執行復雜邏輯時,仍然應該使用 mutex

示例代碼

示例 1:用 atomic 實現線程安全計數

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

// atomic 提供原子操作,适合简单的跨线程共享状态。
std::atomic<int> counter{0};

void add_many(int times)
{
    for (int i = 0; i < times; ++i)
    {
        ++counter;
    }
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // vector 是动态数组,元素数量可以在运行时变化。
    std::vector<std::thread> threads;

    for (int i = 0; i < 4; ++i)
    {
        threads.emplace_back(add_many, 100000);
    }

    for (auto& thread : threads)
    {
        // join 会等待子线程结束,避免 main 提前退出。
        thread.join();
    }

    std::cout << "counter = " << counter << "\n";
    std::cout << "expected = 400000\n";

    return 0;
}

運行結果

counter = 400000
expected = 400000

示例 2:atomic 適合狀態標記

下面例子用一個原子布爾值做停止標記。主線程修改標記,工作線程能安全看到變化。

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

// atomic 提供原子操作,适合简单的跨线程共享状态。
std::atomic<bool> running{true};

void worker()
{
    int count = 0;
    while (running)
    {
        ++count;
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    std::cout << "worker count = " << count << "\n";
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // 创建子线程,让这部分代码和 main 线程并发运行。
    std::thread t(worker);

    std::this_thread::sleep_for(std::chrono::milliseconds(350));
    running = false;

    // join 会等待子线程结束,避免 main 提前退出。
    t.join();
    std::cout << "stopped\n";

    return 0;
}

一種可能的運行結果

worker count = 4
stopped

mutex 和 atomic 怎麼選

場景推薦
簡單計數、自增、自減std::atomic
一個布爾停止標記std::atomic<bool>
多個變量要同時保持一致std::mutex
修改容器、對象內部複雜狀態std::mutex
需要等待某個條件成立std::condition_variable

常見錯誤

  1. 以為用了 atomic,整個對象就都線程安全。只有這個原子變量自己的操作是線程安全的。
  2. 用多個 atomic 表達一個整體狀態,卻沒有保證它們之間的一致性。
  3. 在複雜共享數據結構上強行用 atomic,導致代碼難懂又容易錯。

小結

  • std::atomic<T> 讓簡單變量的基本操作具備線程安全性。
  • 簡單計數器和停止標記很適合 atomic
  • 複雜共享狀態仍然應該用 mutex
  • 初學階段不必展開內存序,先掌握默認用法和適用邊界。
音乐页