假设foo的元素正确对齐并设置了大小,从而不会造成单词撕裂,以下构造函数是否是线程安全的?如果没有,为什么不呢?
注意:以下代码只是我想要做的一个玩具示例,而不是我的实际场景。显然,在我的示例中,有更好的方法来编码可观察的行为。
uint[] foo;
// Fill foo with data.
// In thread one:
for(uint i = 0; i < foo.length; i++) {
if(foo[i] < SOME_NUMBER) {
foo[i] = MAGIC_VAL;
}
}
// In thread two:
for(uint i = 0; i < foo.length; i++) {
if(foo[i] < SOME_OTHER_NUMBER) {
foo[i] = MAGIC_VAL;
}
}
乍一看,这显然是不安全的,因此我将着重说明为什么我认为它可能是安全的:
SOME_OTHER_NUMBER
或不是。如果它是<SOME_OTHER_NUMBER
,线程二还将尝试将其设置为MAGIC_VAL。如果没有,则线程二将无能为力。 编辑:另外,如果foo是long或double或类似的东西怎么办,以致无法自动进行更新呢?您仍然可以假设对齐方式等使得更新foo的一个元素不会影响任何其他元素。同样,在这种情况下,多线程的重点是性能,因此任何类型的锁定都将使性能失效。
最佳答案
在现代的多核处理器上,没有内存屏障,代码不是线程安全的(至少在大多数语言中)。简而言之,在没有显式障碍的情况下,每个线程都可以从缓存中看到完全不同的foo副本。
假设您的两个线程在某个时间点运行,然后在稍后的某个时间点,第三个线程读取foo,它可能会看到一个完全未初始化的foo,或者其他两个线程中的任何一个的foo,或某些线程的混合两者都取决于CPU内存缓存的情况。
我的建议-不要试图对并发“聪明”,而总是要“安全”。聪明每次都会咬你。 broken double-checked locking文章对在没有内存障碍的情况下内存访问和指令重新排序可能发生的情况有一些大开眼界的见解(尽管特别是关于Java及其(不断变化的)内存模型,它对任何语言都具有洞察力)。
您必须真正掌握语言指定的内存模型才能克服快捷键障碍。例如,Java允许将变量标记为volatile,并与记录为具有原子分配的类型组合在一起,可以通过强制将其传递到主内存来允许不同步的分配和获取(因此该线程不观察/更新缓存的副本) 。
关于performance - 同时将变量更新为相同值的线程安全性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/448224/