在哪些情况下我需要锁定变量来同时访问?

时间:2013-08-02 12:24:38

标签: c++ c multithreading

在C或C ++程序中,如果2个线程使用相同的全局变量,则需要通过互斥锁来锁定var。

但在哪些情况下呢?

  1. 线程1:读取线程2:读取
  2. 线程1:写线程2:读取
  3. 线程1:写线程2:写
  4. 当然你需要锁定案例3,但其他2个案例是什么?案例2(非原子操作)会发生什么?是否存在某种访问冲突或者线程2是否只获得旧值?我对此感到困惑,因为硬件级别的内存和寄存器不能同时访问(在普通的PC硬件中),还是我们有某种并行CPU和并行总线到并行ram芯片?

4 个答案:

答案 0 :(得分:8)

想想每个案例中可能发生的事情。我们只考虑竞争条件:这很简单,这足以让我们看到后果。

在情况1中,变量未被修改,因此无论顺序是什么,两个线程都将读取相同的值。基本上,这里没有任何问题。

案例2和3更糟糕。假设你有一个竞争条件,并且不知道哪个线程会提前访问。这意味着:

对于情况2:所有操作结束时变量的值都很好(它将是线程1写入的值),但是线程2可能得到变量的旧值,这可能导致崩溃,或其他问题。

对于情况3:变量的结束值是不可预测的,因为它取决于最后执行写操作的线程。

对于情况2和3,也可能发生其中一个线程在处于不一致状态时尝试访问该变量,并且您可能最终得到其中一个线程读取的一些垃圾数据(即Case 2),甚至在完成所有操作后变量中的垃圾数据。

所以,请锁定案例2和3。

答案 1 :(得分:3)

锁定规则很简单:

  

如果您写入一个由另一个线程同时访问的变量,那么您需要正确的操作顺序(例如,通过锁定)。

通过这个简单的规则,我们可以轻松评估每个案例:

  1. 无需写入,无需同步
  2. 写入,并发访问,需要同步
  3. 写入,并发访问,需要同步
  4. 如果你不锁定会发生什么?

    嗯,正式来说,这是未定义的行为。意思是我们不知道。虽然唯一可能的回应确实有助于掌握问题的严重程度。

    在机器级别,可能发生的是:

    • 如果是:访问陈旧值
    • 在读取的情况下:访问部分值(只有一半在您查看时更新)
    • 在写的情况下:最终值是值的大杂烩(在位级别)

    ...让我们不要忘记,每当您读取不正确的指针/尺寸时,都可能导致崩溃。

    在编译器级别,编译器可以优化,就好像线程具有唯一访问权限一样。在这种情况下,它可能意味着:

    • 删除“多余”读取:将while (flag)转换为if (flag) while (true) ...
    • 删除“未使用”写入
    • ...

    内存防护和显式同步指令(使用互斥锁引入)会阻止这些优化。

答案 2 :(得分:2)

规则很简单:如果一个对象被访问(读或写) 多个线程,并由任何线程修改,然后所有 访问需要同步。否则,你有未定义 行为。

答案 3 :(得分:0)

除了两种情况外,它看起来并不那么简单:

  1. 当只有读者而没有作家时,需要同步。
  2. 当有多个线程正在写入并且其中至少有一个正在执行读取 - 修改 - 写入操作(例如++x;)时,总是需要同步,或者您将得到完全不可预测的结果。
  3. 在所有其他情况下,如果您有至少一个编写器和一个阅读器(或多个编写者),那么通常(极少数例外)需要同步访问权限,但不一定总是,并不总是以最严格的方式。

    这在很大程度上取决于您需要什么样的保证。有些应用程序需要在线程上进行严格的顺序一致性(有时甚至需要锁定公平性)。有些应用程序同样可以正常工作,但性能要好得多,如果它们只在同一个线程中有保证。然而,其他应用程序甚至不需要那么多,并且完全满意放松操作或根本没有任何保证。

    例如,工作线程的这种“典型”实现有一个作者和一个读者:

    // worker thread
    running = true;
    while(running) { task = pull_task(); execute(task); }
    
    // main thread exit code
    running = false;
    join(workerthread);
    

    无需任何同步即可完美运行。是的,迂腐地说,running的价值何时或如何变化是不明确的,但实际上并没有区别。内存位置无法具有一些“随机”中间值,并且由于工作线程很可能正在忙于执行任务,因此更改是否在之前或之后几十纳秒内可见并不重要。在最糟糕的情况下,它会在几毫秒后获得更改。最后,在下一次迭代中,工作线程接收更改并退出 几年前在Dobb博士发表的SPSC快进队列也采用了类似的原则,只有指针。

    GCC documentation中给出了关于许多不同同步模式及其含义的完整而全面的阅读。