写入/读取变量的2+线程的真正危险

时间:2011-05-08 12:00:14

标签: c pthreads

同时读/写单个变量的真正危险是什么?

如果我使用一个线程来编写变量而另一个线程在while循环中读取变量,并且如果在写入时读取变量并且使用旧值没有危险,那么还有什么危险?

同时读/写是否会导致线程崩溃或在精确同时读/写发生时在低电平上发生什么?

8 个答案:

答案 0 :(得分:5)

如果两个线程在没有适当同步的情况下访问变量,并且这些访问中至少有一个是写入,那么您将遇到数据争用和未定义的行为。

未定义的行为表现形式完全取决于实现。在大多数现代架构中,您不会从硬件中获得陷阱或异常或任何内容,它将读取某些内容,或存储某些内容。问题是,它不一定会读或写你所期望的。

e.g。使用两个线程递增变量,您可以错过计数,如我在devx的文章中所述:http://www.devx.com/cplus/Article/42725

对于单个编写者和单个阅读器,最常见的结果是阅读器会看到陈旧的值,但如果更新需要多个循环,或者变量被分割,您可能还会看到部分更新的值缓存行。然后会发生什么取决于你用它做什么 - 如果它是一个指针,你得到一个部分更新的值,那么它可能不是一个有效的指针,并且不会指向你想要的东西,然后你可能由于解除引用无效指针值而导致任何类型的损坏或错误。这可能包括格式化硬盘或其他不良后果,如果坏指针值恰好指向内存映射的I / O寄存器....

答案 1 :(得分:3)

一般来说,你会得到意想不到的结果。维基百科定义了两种不同的竞赛条件:

  

当内部变量的更改顺序决定状态机最终进入的最终状态时,就会发生严重竞争。

     

当内部变量的更改顺序不会改变最终状态时,会发生非关键竞赛。换句话说,当移动到期望状态时发生非关键竞赛意味着必须一次改变多个内部状态变量,但无论这些内部状态变量以何种顺序改变,结果状态都是相同的。

因此输出不会总是搞砸,这取决于代码。 始终处理竞争条件以便以后进行代码扩展并防止可能的错误,这是一种很好的做法。没有什么比不相信自己的数据更烦人了。

答案 2 :(得分:2)

读取相同值的两个线程完全没问题。

当一个线程写入非原子变量而另一个线程读取它时,问题就开始了。然后读取的结果是不确定的。由于线程可能随时被抢占(停止)。只保证对原子变量的操作是不可破坏的。原子操作通常写入int类型变量。

如果你有两个线程访问相同的数据,最佳做法+通常不可避免地使用锁定(互斥,信号量)。

HTH

马里奥

答案 3 :(得分:1)

最糟糕的情况取决于实施情况。有很多完全独立的pthread实现,运行在不同的系统和硬件上,我怀疑任何人都知道所有这些。

如果p不是指向易失性的指针,那么我认为符合Posix实现的编译器允许转为:

while (*p == 0) {}
exit(0);

进入*p的单个检查,然后是无限循环,根本不用考虑*p的值。在实践中,它不会,所以这是一个问题,你是否想要编程到标准,或编程到你正在使用的实现的未记录的观察行为。后者通常适用于简单的情况,然后你构建代码,直到你做了足够复杂的事情,它出乎意料地不起作用。

实际上,在没有连贯内存缓存的多CPU系统上,可能需要很长时间才能在循环中看到由不同CPU进行的更改,因为没有内存障碍,它可能永远不会更新它的主存储器的缓存视图。但英特尔拥有连贯的缓存,所以很可能你个人不会看到任何延迟足够长的关注。如果一个可怜的傻瓜试图在更奇特的架构上运行你的代码,他们可能最终不得不修复它。

回到理论,您描述的设置可能导致崩溃。想象一个假想的架构,其中:

  • p指向非原子类型,例如典型32位架构上的long long
  • 该系统上的
  • long long具有陷阱表示,例如因为它有一个填充位用作奇偶校验。
  • 发生读取时对*p的写入是半完成
  • 半写已更新该值的某些位,但尚未更新奇偶校验位。

Bang,未定义的行为,您读取陷阱表示。可能是Posix禁止C标准允许的某些陷阱表示,在这种情况下long long可能不是*p类型的有效示例,但我希望您可以找到哪个类型的陷阱允许陈述。

答案 4 :(得分:0)

如果要写入和写入的变量无法自动更新或读取,则读者可能会获取损坏的“部分更新”值。

答案 5 :(得分:0)

  • 您可以看到部分更新(例如,您可能会看到long long变量,其中一半来自新值,另一半来自旧值。
  • 在使用内存屏障(pthread_mutex_unlock()包含隐式内存屏障)之前,无法保证看到新值。

答案 6 :(得分:0)

取决于平台。例如,在Win32上,然后读取和写入对齐的32位值的操作是原子的 - 也就是说,你不能半读新值并半读旧值,如果你写,那么当有人来读,要么获得全新值,要么获得旧值。当然,并非所有价值观或所有平台都是如此。

答案 7 :(得分:0)

结果未定义。

考虑以下代码:

global int counter = 0;


tread()
{
   for(i=0;i<10;i++)
   {
       counter=counter+1;
   }
}

问题是,如果你有N个线程,结果可以是10到N * 10之间的任何值。 这是因为它可能会发生所有踏板读取相同的值增加它然后写入值+1返回。但是你问你是否可以使程序或硬件崩溃 这取决于。在大多数情况下,错误的结果是无用的。

要解决此锁定问题,您需要使用互斥锁或信号量。

Mutex锁定代码。在大写字母中,您将锁定部分代码

counter = counter+1;

信号量锁定变量

counter 

解决相同类型的问题基本相同。

检查胎面库中的这些工具。

http://en.wikipedia.org/wiki/Mutual_exclusion