11

我有 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_xreader_thread

  1. 这是一个有效的担忧吗?
  2. 有没有另一种方法来保证没有互斥锁的原子性?
  3. sig_atomic_t假设它有足够的位用于我的目的,是否可以用作共享变量?

有问题的语言是使用 pthreads 的 C。

4

7 回答 7

14

不同的架构有不同的规则,但总的来说,对齐的、int大小的对象的内存加载和存储是原子的。越来越小可能会有问题。因此,如果sizeof(float) == sizeof(int)您可能是安全的,但我仍然不会在可移植程序中依赖它。

此外, 的行为volatile也不是特别明确......规范使用它作为一种防止优化访问内存映射设备 I/O 的方法,但没有说明它在任何其他内存访问上的行为。

简而言之,即使加载和存储float xvolatile. 如果不保证加载和存储是原子的,您将不得不使用锁,这确实意味着内存屏障。

于 2009-05-13T19:02:37.410 回答
6

根据 GNU C 库文档的第 24.4.7.2 节:

在实践中,您可以假设它int和其他整数类型不再int是原子的。你也可以假设指针类型是原子的;这很方便。这两个假设在 GNU C 库支持的所有机器和我们知道的所有 POSIX 系统上都是正确的。

float技术上不计入这些规则,尽管如果 a与架构上的 afloat大小相同int,您可以做的是使您的全局变量 an int,然后在每次读取或写入时将其转换为带有联合的浮点数.

最安全的做法是使用某种形式的互斥锁来保护对共享变量的访问。由于关键部分非常小(读取/写入单个变量),因此您几乎可以肯定会从自旋锁等轻量级互斥锁中获得更好的性能,而不是使系统呼吁做它的工作。

于 2009-05-13T19:09:40.580 回答
3

我会把它锁起来。我不确定float您的环境中有多大,但它可能不会在单个指令中读取/写入,因此您的读者可能会读取一半写入的值。请记住,这volatile并没有说明操作的原子性,它只是说明读取将来自内存,而不是缓存在寄存器或类似的东西中。

于 2009-05-13T18:54:00.763 回答
3

分配不是原子的,至少对于某些编译器而言,并且在某种意义上它需要一条指令来执行。以下代码由 Visual C++ 6.0 生成 - f1 和 f2 是浮点类型。

4:        f2 =  f1;
00401036   mov         eax,dword ptr [ebp-4]
00401039   mov         dword ptr [ebp-8],eax
于 2009-05-13T19:16:02.240 回答
1

我相信你应该使用互斥锁。有一天,您的代码可能会在没有真正浮点硬件并使用仿真的系统上运行,从而导致非原子浮点变量。例如,请查看 -msoft-float here

我的回答不是很有用。 gcc -msoft-float可能只是浮动的加载和存储不是原子的特定情况。

于 2009-05-13T19:03:51.213 回答
0

由于它是内存中的一个单词,因此您正在更改您应该只使用 volatile 声明就可以了。

我认为您不能保证您在阅读时会获得最新的价值,除非您使用锁。

于 2009-05-13T18:48:42.660 回答
0

很可能,没有。由于你没有写冲突的机会,唯一关心的是你是否可以在它写到一半的时候阅读它。如果您使用线程编写某些东西,那么您的代码极不可能在单个操作中不会发生写入浮点数的平台上运行。

然而,这是可能的,因为 C 中浮点数的定义并不要求底层硬件存储限于处理器的字长。您可能正在编译为机器代码,例如,符号和尾数是用两种不同的操作编写的。

我认为真正的问题是两个问题:“在这里使用互斥锁有什么缺点?” 和“如果我被垃圾读了会有什么影响?”

也许您应该编写一个断言而不是互斥锁来确定浮点数的存储大小是否小于或等于底层 CPU 的字大小。

于 2009-05-13T19:16:37.310 回答