这足以检测竞争条件吗?

时间:2012-06-04 22:45:45

标签: c linux gcc

假设我有一个多线程应用程序,并使用相同输入运行它。是否足以检测每个加载和存储以检测写入和写入读取数据竞争?我的意思是从记录的加载和存储地址,如果我们可以看到哪个线程执行了哪个加载以及哪个线程执行哪个存储,我们可以通过注意重叠的地址来检测写入读取和写入写入数据。或者我错过了什么?

3 个答案:

答案 0 :(得分:5)

  

或者我错过了什么?

错过了很多。正如Pubby所说,如果你看到读取,然后写入T1,然后读取,然后写入T2,你不能说任何关于没有比赛。您需要了解所涉及的锁。

您可能希望使用工具,例如Google的ThreadSanitizer

更新

  

但我的方法是否涵盖所有种族或至少一些种族?

您在这里和其他答案中的评论似乎表明您不了解比赛是什么。

您的方法可能公开某些种族,是的。保证覆盖其中大部分(这将使练习徒劳无功)。

答案 1 :(得分:2)

这是维基百科的一个简单示例,我稍作修改:

  

作为一个简单的例子,让我们假设两个线程T1和T2都需要   对一个全局整数的值进行算术运算。理想情况下,   将执行以下操作序列:

     
      
  1. 整数i = 0; (存储器)
  2.   
  3. T1从内存中读取i的值到register1:0
  4.   
  5. T1递增寄存器1中的i值:(寄存器1内容)+ 1 = 1
  6.   
  7. T1将register1的值存储在内存中:1
  8.   
  9. T2从内存中读取i的值到register2:1
  10.   
  11. T2乘以寄存器2中的i值:(寄存器2内容)* 2 = 2
  12.   
  13. T2将register2的值存储在内存中:2
  14.   
  15. 整数i = 2; (存储器)
  16.         

    在上面显示的情况中,正如预期的那样,i的最终值是2。   但是,如果两个线程同时运行而没有锁定或   同步,操作的结果可能是错误的。该   下面的替代操作序列演示了这种情况:

         
        
    1. 整数i = 0; (存储器)
    2.   
    3. T1从内存中读取i的值到register1:0
    4.   
    5. T2从存储器读取i的值到register2:0
    6.   
    7. T1递增寄存器1中的i值:(寄存器1内容)+ 1 = 1
    8.   
    9. T2乘以寄存器2中的i值:(寄存器2内容)* 2 = 0
    10.   
    11. T1将register1的值存储在内存中:1
    12.   
    13. T2将register2的值存储在内存中:0
    14.   
    15. 整数i = 0; (存储器)
    16.         

      i的最终值为0而不是2的预期结果   发生的原因是第二种情况的增量操作不是   相互排斥。互斥操作就是那些   访问某些资源(如内存)时无法中断   地点。在第一种情况下,T1在访问时没有被中断   变量i,因此它的操作是互斥的。

所有这些操作都是原子的。出现竞争条件是因为此特定顺序与第一个顺序不具有相同的语义。你如何证明语义与第一语义不同?嗯,你知道他们在这种情况下是不同的,但你需要证明每一个可能的顺序,以确定你没有竞争条件。这是一项非常困难的事情,并且具有极大的复杂性(可能是NP难或需要AI完成),因此无法可靠地检查。

如果某个订单永不停止会怎样?你怎么知道它永远不会停止?你基本上只能解决暂停问题,这是一项不可能完成的任务。

如果您正在谈论使用连续读取或写入来确定比赛,请观察:

  
      
  1. 整数i = 0; (存储器)
  2.   
  3. T2从存储器读取i的值到register2:0
  4.   
  5. T2乘以寄存器2中的i值:(寄存器2内容)* 2 = 0
  6.   
  7. T2将register2的值存储在内存中:0
  8.   
  9. T1从内存中读取i的值到register1:0
  10.   
  11. T1递增寄存器1中的i值:(寄存器1内容)+ 1 = 1
  12.   
  13. T1将register1的值存储在内存中:1
  14.   
  15. 整数i = 1; (存储器)
  16.   

这与第一个具有相同的读/存储模式,但会产生不同的结果。

答案 2 :(得分:1)

你将学到的最明显的事情是有几个线程使用相同的内存。这本身并不一定是坏事。

良好的用途包括信号量保护,原子访问和RCU或双缓冲等机制。

糟糕的用途包括种族条件,真假共享:

  • 竞争条件主要源于排序问题 - 如果某个任务A在执行结束时写入某些内容而任务B在其开始时需要该值,则最好确保B的读取仅在A完成后发生。信号量,信号或类似信号是一个很好的解决方案。或者当然在同一个线程中运行它。
  • 真正共享意味着两个或多个内核正在积极地读取和写入相同的内存地址。这会降低处理器的速度,因为它将不断地将任何更改发送到其他内核的缓存(当然还有内存)。你的方法可以解决这个问题,但可能不会强调它。
  • 虚假共享甚至比真正的共享更复杂:处理器缓存不能在单个字节上工作,而是在“缓存行”上工作 - 它们包含多个值。如果核心A继续锤击一行的字节0而核心B继续写入字节4,则缓存更新仍将使整个处理器停止。