第 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 |
常見錯誤
- 以爲用了
atomic,整個對象就都線程安全。只有這個原子變量自己的操作是線程安全的。 - 用多個
atomic表達一個整體狀態,卻沒有保證它們之間的一致性。 - 在複雜共享數據結構上強行用
atomic,導致代碼難懂又容易錯。
小結
std::atomic<T>讓簡單變量的基本操作具備線程安全性。- 簡單計數器和停止標記很適合
atomic。 - 複雜共享狀態仍然應該用
mutex。 - 初學階段不必展開內存序,先掌握默認用法和適用邊界。