我总是被告知要锁定多个线程可以访问的变量,我一直认为这是因为你要确保在你写回来之前你所使用的值没有改变 即。
mutex.lock()
int a = sharedVar
a = someComplexOperation(a)
sharedVar = a
mutex.unlock()
这是有道理的,你会锁定它。但在其他情况下,我不明白为什么我不能不使用互斥锁。
主题A:
sharedVar = someFunction()
主题B:
localVar = sharedVar
在这种情况下可能出现什么问题?特别是如果我不关心线程B读取线程A分配的任何特定值。
答案 0 :(得分:5)
这很大程度上取决于sharedVar
的类型,您正在使用的语言,任何框架和平台。在许多情况下,为sharedVar
分配单个值可能需要多条指令,在这种情况下,您可能会读取该值的“半集”副本。
即使不是这种情况,并且分配是原子的,如果没有memory barrier,您可能看不到最新的值。
答案 1 :(得分:3)
MSDN Magazine对多线程代码中可能遇到的各种问题有很好的解释:
您问题中的代码特别容易受到读/写撕裂的影响。但是你的代码既没有锁也没有内存障碍,也受无锁重新排序(可能包括推测性写入,其中线程B读取线程A从未存储过的值)的副作用以与源代码中出现的顺序不同的顺序显示第二个主题。
继续描述一些避免这些问题的已知设计模式:
该文章可用here
答案 2 :(得分:2)
主要问题是赋值运算符(operator = in C ++)并不总是保证是原子的(即使对于原始的内置类型)也是如此。简单来说,这意味着分配可能需要一个以上的时钟周期才能完成。如果在中间,线程被中断,那么变量的当前值可能会被破坏。
让我建立你的榜样:
让我们说sharedVar
是operator=
定义的对象:
object& operator=(const object& other) {
ready = false;
doStuff(other);
if (other.value == true) {
value = true;
doOtherStuff();
} else {
value = false;
}
ready = true;
return *this;
}
如果示例中的线程A在此函数中间被中断,则线程B开始运行时,ready仍然为false。这可能意味着当线程B尝试将对象复制到局部变量时,该对象仅部分复制或处于某种中间无效状态。
对于一个特别令人讨厌的例子,考虑一个被删除的节点被删除的数据结构,然后在它被设置为NULL之前被中断。
(有关不需要锁定的结构的一些更多信息(也就是原子),here是另一个可以进一步讨论的问题。)
答案 3 :(得分:0)
这可能会出错,因为线程调度程序可以挂起并恢复线程,因此您无法确定这些指令的执行顺序。它也可能按此顺序排列:
主题B:
localVar = sharedVar
主题A:
sharedVar = someFunction()
在这种情况下localvar
将为null或0(或者在不安全的语言中为某些完整的意外值),可能不是您想要的。
Mutexes实际上不会解决这个问题。您提供的示例不适合并行化。