假设我有一段不能同时执行的代码。尝试线程锁定的我(假定的天真)方法看起来像这样:
int lock = 0;
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
if (lock)
return 1;
lock = 1;
/* code goes here */
lock = 0;
return 0;
}
使用以下方法进行测试时:
for (i = 0; i < 2; i++)
thandle[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, &tid[i]);
WaitForMultipleObjects(2, thandle, TRUE, INFINITE);
我总是得到预期的效果,只有第一个要创建的线程实际到达代码并返回0.但是,我不断遇到这个方法可能失败的建议,因为没有使用原子操作实现锁。 / p>
我看不到同时创建线程的方法,所以,实际上,这里真的需要原子性吗?有人可以提供一个会导致上述情况失败的例子吗?
答案 0 :(得分:4)
您需要能够以原子方式检查锁定并同时获取锁定。
可能发生以下情况:
线程1检查锁定:
if (lock) // lock = 0 so skips the body of the if statement
线程2同时检查锁定:
if (lock) // lock = 0 so skips the body of the if statement
线程1分配锁:
lock = 1
线程2分配锁:
lock = 1
线程1运行其代码:
/* code goes here */ // Thread 1 starts running the critical section code
线程2同时运行其代码:
/* code goes here */ // Thread 2 starts running the critical section code
由于没有使用原子“test and acquire lock”,在线程1检查锁定并设置锁定的时间之间,线程2能够检查锁定,因此两个线程都可以在关键部分在同一时间。
在单核系统上,如果在线程1检查锁定的时间与设置锁定的时间之间发生到线程2的任务切换,则会发生这种情况。
在多核系统上,如果两个线程都位于同一个核心并且发生了线程2的任务切换,则会发生这种情况,如果线程在不同的核心上运行,则会发生这种情况。
答案 1 :(得分:3)
是的,我可以看到这可能会失败。
如果您在单核/单处理器计算机上运行它,您可以永远运行它,并且永远不会看到故障。
在具有多个内核的计算机上,看到故障可能是相当常规的。
如果机器上的所有核心都忙,那么失败的显而易见的方法就是。创建新线程的一个线程将创建许多线程,其中没有一个线程立即开始运行,因为核心都很忙。
然后许多其他线程同时完成处理,并且许多线程都同时启动。所有这些都在锁定步骤中执行,因此它们都读取相同的值,所有尝试写入相同的值(产生未定义的行为),所有都同时执行代码,所有设置锁定为0,并且全部返回0。
除非你有很多线程和核心,否则在给定的运行中发生这种情况的可能性可能相当低。毫无疑问,它最终能够并且将会发生 - 并且涉及的线程和内核越多,它就会越早发生。
答案 2 :(得分:3)
大多数情况下,您的代码都没问题,但在某些情况下,它会失败,并且两个或多个线程可能会同时执行受锁保护的代码。
两个线程在同一时间开始执行函数的图像。他们都会执行
if (lock)
return 1;
到达之前
lock = 1;
因此都输入受保护的代码。这就是为什么锁必须是原子的。
解决方案非常简单,只需使用Win32函数InitializeCriticalSection创建一个临界区,并将其与EnterCriticalSection和LeaveCriticalSection一起使用。
答案 3 :(得分:1)
你的代码充满了问题:
lock
不是volatile
,因此编译器可以自由地将其读入寄存器并继续在寄存器中使用/更新它而不将更改写回内存(至少其他线程注意到的机会,尽管在许多CPU上需要一个显式的内存屏障(同步操作码)来保证其他线程的可见性)
volatile
。可能会重新排序说明,以便在您尝试锁定之前或尝试解锁之后执行某些/* code [that] goes here */
在if (lock)
测试和lock = 1;
代码之间仍然存在竞争条件......即使线程写入原子变量,您也需要比较和交换/比较和交换样式操作,以确保安全,如果失败,你需要旋转等待 - 燃烧CPU - 或经过多次尝试后屈服,然后重试(猜猜是什么) - 到那时你已经重新实现了互斥,而不是使用系统的互联网
答案 4 :(得分:0)
由于代码使用的是Windows函数,如CreateThread(),WaitForMultipleObjects(),也可以使用Windows互斥锁或信号量,并在线程中使用WaitForSingleObject(),假设您希望线程等待而不是中止。