同时对单个变量进行读写的真正危险是什么?
如果我使用一个线程来写入一个变量,另一个线程在while循环中读取该变量,并且在写入变量时读取变量不会有危险,那么还有什么其他的危险呢?
同时读写是否会导致线程崩溃,当确切的同时读写发生时低级别会发生什么?
同时对单个变量进行读写的真正危险是什么?
如果我使用一个线程来写入一个变量,另一个线程在while循环中读取该变量,并且在写入变量时读取变量不会有危险,那么还有什么其他的危险呢?
同时读写是否会导致线程崩溃,当确切的同时读写发生时低级别会发生什么?
对于单个编写者和单个读者,最常见的结果是读者看到陈旧的值,但如果更新需要多个周期或变量跨越缓存行,则还可能看到部分更新的值。然后发生的情况取决于您对其的处理方式 --- 如果它是指针并且您获取了部分更新的值,则可能不是有效的指针,并且也不会指向您打算的内容,然后由于取消引用无效指针值而导致任何类型的损坏或错误。这可能包括格式化硬盘或其他不良后果,如果不良指针值恰好指向内存映射I/O寄存器....
两个线程读取同一个值没有任何问题。
当一个线程写入非原子变量并且另一个线程读取它时,问题就开始了。那么读取的结果是未定义的。因为一个线程可以随时被抢占(停止)。只有对原子变量的操作才能保证不可分割。原子操作通常是写入 int
类型变量。
如果你有两个线程访问相同的数据,最好的做法 + 通常是不可避免的是使用锁定(互斥锁,信号量)。
希望这有所帮助。
Mario
这取决于平台。例如,在Win32上,对齐的32位值的读写操作是原子性的-也就是说,您不能半读取新值和半读取旧值,如果您写入,则当有人来读取时,他们要么得到完整的新值,要么得到旧值。当然,并非所有值或所有平台都是如此。
最糟糕的情况取决于实现方式。有许多完全独立的pthread实现,运行在不同的系统和硬件上,我怀疑没有人知道所有这些实现的全部细节。
如果p
不是指向易失性的指针,那么我认为符合Posix实现的编译器是被允许将以下代码转换的:
while (*p == 0) {}
exit(0);
将*p
的单个检查转换为一个无限循环,它根本不关心*p
的值。实际上,它不会这样做,所以问题在于您想要按照标准编程还是按照您正在使用的实现的未记录观察行为进行编程。后者通常适用于简单情况,然后您可以构建代码,直到您执行的操作足够复杂,以至于它意外地不起作用。
在实践中,在没有一致性内存缓存的多CPU系统上,可能需要很长时间才能看到来自不同CPU的更改,因为如果没有内存屏障,它可能永远不会更新其对主内存的缓存视图。但是英特尔具有一致性缓存,因此您个人很可能不会看到任何长时间的延迟。如果某些可怜的人试图在更奇特的架构上运行您的代码,则可能最终不得不修复它。
回到理论上,您描述的设置可能会导致崩溃。想象一下一个假设的架构:
p
指向一个非原子类型,例如在典型的32位架构中使用的long long
。long long
具有陷阱表示,例如因为它有一个用作奇偶校验的填充位。*p
的写入是半完成的。哎呀,未定义行为,您读取了一个陷阱表示。可能Posix禁止C标准允许的某些陷阱表示,如果是这种情况,那么long long
可能不是*p
类型的有效示例,但我认为您可以找到一个类型,其中允许陷阱表示。
结果未定义。
考虑以下代码:
global int counter = 0;
tread()
{
for(i=0;i<10;i++)
{
counter=counter+1;
}
}
要解决这个锁定问题,您需要使用互斥体或信号量。
互斥体是代码锁定。在上例中,您将锁定代码的一部分。
counter = counter+1;
信号量是变量的锁
counter
基本上是解决同类型问题的相同事物。
在您的线程库中检查这些工具。
long long
变量,其中一半来自新值,另一半来自旧值)。pthread_mutex_unlock()
包含隐式内存屏障)。