在C或C ++程序中,如果2个线程使用相同的全局变量,则需要通过互斥锁来锁定var。
但在哪些情况下呢?
当然你需要锁定案例3,但其他2个案例是什么?案例2(非原子操作)会发生什么?是否存在某种访问冲突或者线程2是否只获得旧值?我对此感到困惑,因为硬件级别的内存和寄存器不能同时访问(在普通的PC硬件中),还是我们有某种并行CPU和并行总线到并行ram芯片?
答案 0 :(得分:8)
想想每个案例中可能发生的事情。我们只考虑竞争条件:这很简单,这足以让我们看到后果。
在情况1中,变量未被修改,因此无论顺序是什么,两个线程都将读取相同的值。基本上,这里没有任何问题。
案例2和3更糟糕。假设你有一个竞争条件,并且不知道哪个线程会提前访问。这意味着:
对于情况2:所有操作结束时变量的值都很好(它将是线程1写入的值),但是线程2可能得到变量的旧值,这可能导致崩溃,或其他问题。
对于情况3:变量的结束值是不可预测的,因为它取决于最后执行写操作的线程。
对于情况2和3,也可能发生其中一个线程在处于不一致状态时尝试访问该变量,并且您可能最终得到其中一个线程读取的一些垃圾数据(即Case 2),甚至在完成所有操作后变量中的垃圾数据。
所以,请锁定案例2和3。
答案 1 :(得分:3)
锁定规则很简单:
如果您写入一个由另一个线程同时访问的变量,那么您需要正确的操作顺序(例如,通过锁定)。
通过这个简单的规则,我们可以轻松评估每个案例:
如果你不锁定会发生什么?
嗯,正式来说,这是未定义的行为。意思是我们不知道。虽然唯一可能的回应确实有助于掌握问题的严重程度。
在机器级别,可能发生的是:
...让我们不要忘记,每当您读取不正确的指针/尺寸时,都可能导致崩溃。
在编译器级别,编译器可以优化,就好像线程具有唯一访问权限一样。在这种情况下,它可能意味着:
while (flag)
转换为if (flag) while (true)
... 内存防护和显式同步指令(使用互斥锁引入)会阻止这些优化。
答案 2 :(得分:2)
规则很简单:如果一个对象被访问(读或写) 多个线程,并由任何线程修改,然后所有 访问需要同步。否则,你有未定义 行为。
答案 3 :(得分:0)
除了两种情况外,它看起来并不那么简单:
++x;
)时,总是需要同步,或者您将得到完全不可预测的结果。在所有其他情况下,如果您有至少一个编写器和一个阅读器(或多个编写者),那么通常(极少数例外)需要同步访问权限,但不一定总是,并不总是以最严格的方式。
这在很大程度上取决于您需要什么样的保证。有些应用程序需要在线程上进行严格的顺序一致性(有时甚至需要锁定公平性)。有些应用程序同样可以正常工作,但性能要好得多,如果它们只在同一个线程中有保证。然而,其他应用程序甚至不需要那么多,并且完全满意放松操作或根本没有任何保证。
例如,工作线程的这种“典型”实现有一个作者和一个读者:
// worker thread
running = true;
while(running) { task = pull_task(); execute(task); }
// main thread exit code
running = false;
join(workerthread);
无需任何同步即可完美运行。是的,迂腐地说,running
的价值何时或如何变化是不明确的,但实际上并没有区别。内存位置无法具有一些“随机”中间值,并且由于工作线程很可能正在忙于执行任务,因此更改是否在之前或之后几十纳秒内可见并不重要。在最糟糕的情况下,它会在几毫秒后获得更改。最后,在下一次迭代中,工作线程将接收更改并退出
几年前在Dobb博士发表的SPSC快进队列也采用了类似的原则,只有指针。
GCC documentation中给出了关于许多不同同步模式及其含义的完整而全面的阅读。