我有2个线程和一个共享的float
全局。一个线程只写入变量,而另一个只读取它,我是否需要锁定对此变量的访问?换句话说:
volatile float x;
void reader_thread() {
while (1) {
// Grab mutex here?
float local_x = x;
// Release mutex?
do_stuff_with_value(local_x);
}
}
void writer_thread() {
while (1) {
float local_x = get_new_value_from_somewhere();
// Grab mutex here?
x = local_x;
// Release mutex?
}
}
我主要担心的是float
的加载或存储不是原子的,因此local_x
中的reader_thread
最终会出现虚假,部分更新的值。
sig_atomic_t
作为共享变量是否可行,假设它有足够的位用于我的目的?有问题的语言是C使用pthreads。
答案 0 :(得分:13)
不同的体系结构具有不同的规则,但通常,对齐的int
大小的对象的内存加载和存储是原子的。更小和更大可能是有问题的。因此,如果sizeof(float) == sizeof(int)
您可能是安全的,但我仍然不会在便携式程序中依赖它。
此外,volatile
的行为没有特别明确定义......规范使用它作为一种方法来防止优化对内存映射设备I / O的访问,但对其行为一无所知在任何其他内存访问。
简而言之,即使加载和存储在float x
上是原子的,我也会使用显式内存屏障(尽管平台和编译器有多大不同),而不是依赖于volatile
。如果没有加载和存储是原子的保证,你将使用来使用锁,这意味着内存障碍。
答案 1 :(得分:5)
根据GNU C库文档的第24.4.7.2节:
实际上,您可以假设
int
和其他不超过int
的整数类型是原子的。您还可以假设指针类型是原子的;这很方便。这两个假设都适用于GNU C库支持的所有机器以及我们所知道的所有POSIX系统。
float
在技术上不计入这些规则,但如果float
与您的架构上的int
大小相同,那么您可以做的是使您的全局变量成为int
,然后在每次读取或写入时将其转换为带有并集的浮点数。
最安全的做法是使用某种形式的互斥锁来保护对共享变量的访问。由于关键部分非常小(读/写一个变量),你几乎可以肯定会从轻量级互斥锁(如自旋锁)中获得更好的性能,而不是制造系统的重量互斥体要求做好自己的工作。
答案 2 :(得分:3)
我会锁定它。我不确定您的环境中有多大float
,但它可能无法在一条指令中读/写,因此您的读者可能会读取半写值。请记住,volatile
没有说明操作的原子性,它只是声明读取将来自内存,而不是缓存在寄存器或类似的东西中。
答案 3 :(得分:3)
赋值不是原子的,至少对于某些编译器而言,并且在某种意义上它只需要执行一条指令。下面的代码由Visual C ++ 6.0生成 - f1和f2的类型为float。
4: f2 = f1;
00401036 mov eax,dword ptr [ebp-4]
00401039 mov dword ptr [ebp-8],eax
答案 4 :(得分:1)
我相信你应该使用互斥锁。有一天,您的代码可能在没有实际浮点硬件的系统上运行,而是使用仿真,从而产生非原子浮点变量。例如,请查看-msoft-float here。
<击> 撞击>
我的回答并不是很有用。 gcc -msoft-float
可能只是浮点数的加载和存储不是原子的特定情况。
答案 5 :(得分:0)
因为它是内存中的单个单词,所以你只需要挥动声明即可。
我不认为您保证在阅读时会获得最新的价值,除非您使用锁定。
答案 6 :(得分:0)
很可能,没有。由于你没有机会进行写冲突,唯一的问题是你是否可以在半写时阅读它。你的代码很可能不会在一个平台上运行,如果你用线程写一些东西,写一个浮点数不会在一个操作中发生。
然而,可能因为C中float的定义并未强制要求底层硬件存储仅限于处理器的字大小。您可能正在编译机器代码,例如,符号和尾数是用两种不同的操作编写的。
我认为,真正的问题是两个问题:“在这里使用互斥锁有什么缺点?”并且“如果我读取垃圾会有什么影响?”
也许你应该编写一个断言来确定浮点数的存储大小是否小于或等于底层CPU的字大小,而不是互斥锁。