如何使用std::atomic_bool或std::atomic_flag?相关线程安全问题咨询
你在跨线程回调中用std::atomic_bool做控制标志,以下是针对你的四个疑问的解答:
1. 使用std::atomic_bool是否会出现死锁?还有其他需要注意的问题吗?
std::atomic_bool的读写操作都是无锁原子操作(主流平台均支持),不会引发死锁。需要注意的点:
- 所有对该原子变量的访问必须是原子操作,不能通过取地址后强制转换为普通指针进行非原子读写;
- 你代码中
autoBackup里的“读标志+重置”是两个独立的原子操作,并非原子的复合操作。如果你的场景允许偶尔重复执行do stuff,当前逻辑没问题;但如果要求严格只触发一次,建议用原子成员函数(比如exchange)把这两步合并成一个原子操作; - 默认内存顺序是
std::memory_order_seq_cst,这是最严格的全局顺序保证,对于简单标志场景完全够用,性能开销可以忽略。
2. 像普通变量一样使用std::atomic_bool是否正确?若不正确该如何操作?
大部分场景下直接赋值、读取是合法的——因为std::atomic_bool重载了operator=和operator bool(),这些操作本身就是原子的。但有个优化点:
如果你需要“读取当前值并原子重置”这类复合逻辑,不要拆分成两次操作,应该用原子成员函数实现。比如你autoBackup里的逻辑可以改成:
if (standbyResumeTimerDelayBool.exchange(false)) { // do stuff }
exchange会原子地把变量设为false并返回旧值,这样能避免两个线程同时进入do stuff的情况,逻辑更严谨。
3. 是否应该改用std::atomic_flag?
完全没必要。std::atomic_flag是功能最受限的原子类型,仅支持test_and_set和clear操作,而且必须用ATOMIC_FLAG_INIT初始化,代码可读性差。std::atomic_bool的API更贴合布尔标志的使用场景,易用性和可读性都更好。只有在极端追求最小开销或必须保证绝对无锁(atomic_flag是C++标准唯一保证无锁的原子类型)的场景下,才需要考虑它。你的场景用std::atomic_bool是最优选择。
4. 在类成员列表中初始化standbyResumeTimerDelayBool未报错,是否正确?
正确。C++11及以后支持非静态成员的类内初始化,std::atomic_bool standbyResumeTimerDelayBool = false;这种写法是合法的,等效于在构造函数初始化列表中完成初始化(如果构造函数没有显式覆盖的话)。
另外提醒一下:你Worker类里的std::atomic_bool cancel;没有显式初始化,这会导致它的初始值不确定,建议改成std::atomic_bool cancel = false;,避免未定义行为。
附你提供的代码片段(修正了注释错误):
//some code omitted to keep things simple //first callback function, this boolean flag is checked at one other point in the program static ULONG Replicator::standbyResumeCallbackFunction(PVOID context, ULONG type, PVOID setting); ULONG Replicator::standbyResumeCallbackFunction(PVOID context, ULONG type, PVOID setting) { if (type == PBT_APMRESUMEAUTOMATIC) { Replicator* replicator = reinterpret_cast<Replicator*>(context); replicator->standbyResumeSetTimerDelayBool(); } } //Replicator class members void standbyResumeSetTimerDelayBool() { standbyResumeTimerDelayBool = true; } std::atomic_bool standbyResumeTimerDelayBool = false; //behavior modified here void Replicator::autoBackup() { if (standbyResumeTimerDelayBool) { standbyResumeTimerDelayBool = false; //do stuff } } //second callback function static DWORD CALLBACK copyProgress( LARGE_INTEGER totalSize, LARGE_INTEGER totalTransferred, LARGE_INTEGER streamSize, LARGE_INTEGER streamTransferred, DWORD streamNo, DWORD callbackReason, HANDLE src, HANDLE dst, LPVOID data); DWORD Worker::copyProgress(LARGE_INTEGER totalSize, LARGE_INTEGER totalTransferred, LARGE_INTEGER, LARGE_INTEGER, DWORD, DWORD, HANDLE, HANDLE, LPVOID data) { Worker *worker = static_cast<Worker *>(data); if (worker->getStop()) return PROGRESS_CANCEL; } //Worker class members bool getStop() { return cancel; } std::atomic_bool cancel; //cancel is set to false when backup begins void Worker::startBackup() { cancel = false; } //if user clicks cancel button cancel flag is set to true void stop() { cancel = true; }
内容的提问来源于stack exchange,提问作者riverofwind

