假设我有以下代码:
/* Global Variables */
int flag = 0;
int number1;
int number2;
//------------------------------------
/* Thread A */
number1 = 12345;
number2 = 678910;
flag = 1;
//------------------------------------
/* Thread B */
while (flag == 0) {}
printf("%d", number1);
printf("%d", number2);
在线程A中,代码可以不按顺序执行,例如:
/* Thread A */
flag = 1;
number1 = 12345;
number2 = 678910;
为了防止这种情况,我应该使用记忆屏障。
但我不确定是否应该使用常规的内存屏障,例如:
/* Thread A */
number1 = 12345;
number2 = 678910;
MEMORY_BARRIER_GOES_HERE
flag = 1;
或者我是否应该使用关键部分,例如:
/* Thread A */
number1 = 12345;
number2 = 678910;
EnterCriticalSection(&cs);
flag = 1;
LeaveCriticalSection(&cs);
3条答案
按热度按时间0qx6xfy61#
试图聪明地使用无锁线程模型(屏障,原子,互锁操作等)来保护多个变量而不是标准的锁定机制只会导致错误。
您需要用锁保护所有变量(
number1
、number2
和flag
)(关键部分)线程A:
线程B:
此外,在Windows上,您可以避免整个
while (flag == 0) {}
循环使用Conditional Variable来烧毁CPU核心。从连续轮询机制切换到基于通知的机制将产生比尝试执行棘手的互锁操作好得多的性能结果。更好:
线程A:
线程B:
jhdbpxl92#
在具体示例中,您需要的正是 * 发布-获取排序 *
如果线程 A 中的原子存储被标记为
memory_order_release
,并且线程 B 中的原子加载来自同一变量(flag
)标记为memory_order_acquire
,所有内存写入(非原子的和松弛的原子的)发生-从线程A的Angular 来看在原子存储之前,在线程B中变成可见的副作用,也就是说,一旦原子加载完成线程B保证看到线程A写入内存的所有内容(number1, number2
)。你也可以将flag定义为
volatile int flag
,并使用/volatile:ms
CL.exe 选项:/volatile:ms
选择Microsoft扩展的volatile语义,它在ISO标准 C++ 之外添加了内存排序保证。在易失性访问上保证获取/释放语义。但是,此选项也会强制编译器生成硬件内存屏障,这可能会在ARM和其他弱内存排序架构上增加大量开销。如果编译器针对除ARM以外的任何平台,则这是volatile的默认解释。
但无论如何
while (flag == 0) ;
不是好的解决方案(自旋锁)。这里可以使用设置/等待事件、条件变量、向具体线程或IOCP发送/发布消息。取决于具体任务w8ntj3qf3#
如果你声明你的标志为
volatile LONG
,那么你可以这样做:InterlockedExchange (&flag, 1);
这会产生一个完全的内存屏障,参见MSDN。考虑到这是一个C问题,这似乎是一个很好的方法。
在真实的代码中(例如低延迟音频处理)可能导致priority inversion,如果一个较低优先级的线程曾经要求它。我怀疑这是否是一个问题,但我已经发生在我身上。