2010-07-30 97 views
2

在下面的代码:易失性和多线程?

#include <pthread.h> 
#include <unistd.h> 
#include <stdio.h> 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
int ready = 0; 

wait() 
{ 
    int i; 
    do 
    { 
     usleep(1000); 
     pthead_mutex_lock(&mutex); 
     i = ready; 
     pthread_mutex_unlock(&mutex); 
    } while (i == 0); 
    printf("Finished\n"); 
} 

signal() 
{ 
    pthead_mutex_lock(&mutex); 
    ready = 1; 
    pthread_mutex_unlock(&mutex); 
} 

我们产卵两个线程,我们调用wait在一个线程,然后调用另一个信号我们也让编译器优化积极。

现在代码的行为会如预期的那样,还是我们需要做好准备工作以使其发挥作用?不同的编译器和库会以不同的方式处理它们吗

编辑:我希望可能有围绕互斥体函数,会阻止优化本身或编译器通常不优化轮回函数调用。

注意:我还没有编译和测试代码,当我有机会时会这样做。

回答

-1

是的,这里总是需要volatile int ready = 0;

更新
如果你想周围的一些代码片断没有优化,您可以使用#pragma指令周围的代码(如果你的编译器支持它们)或 - 这是完全便携 - 移动代码分离文件和编译这些文件没有或几乎没有优化。
后一种方法可能仍然需要使用volatile关键字,因为ready变量可能会被其他您可能想要优化的模块使用。

+0

请参阅我的编辑。 – doron 2010-07-30 15:11:48

+0

它不便携式。该编译指示可能会阻止编译器优化,但编译器可以做的任何优化原则上也可以通过芯片组,CPU,内存控制器等来完成。 – 2011-08-30 23:14:18

2

如果编译器在存在库函数调用时假定有关全局变量的任何内容,我会感到惊讶。这就是说,波动不会让你付出任何代价,并且它显示你的意图。我会把它放在那里。

+1

那么,你认为优化器不会查看这些库函数的代码?今天可能是这样,但可能不是明年。 – Darron 2010-07-30 15:16:34

+0

@达隆:编译器无法看到它从DLL加载的代码。 – Puppy 2010-07-30 15:19:24

+0

并非所有的库都是DLL的。并不是所有的优化器都在编译时运行;已经有针对机器代码的即时优化器的实验。 – Darron 2010-07-30 15:31:36

-1

存在优化时需要挥发性。否则,就绪的读取可以合法移出while循环。

假设现在对标准没有承诺的优化限制现在可能没什么问题,但是随着编译器的改进,将会给未来的维护者带来巨大的悲痛。

+0

将即时读取移出while循环会违反POSIX标准。 – 2011-08-31 06:59:34

0

现在代码将按预期工作,或者我们需要做好准备工作以使其正常工作吗?

我会建议在这种情况下使用volatile。虽然在这种情况下似乎并不需要。

IOW,我个人会添加volatile并删除锁定:它不需要设置/读取变量的值。

不同的编译器和库会以不同的方式处理这个问题吗? 我希望可能会有一些围绕互斥函数的事情,它们会阻止围绕自身进行优化,或者编译器通常不会优化循环函数调用。

在你的情况下,调用函数(pthread_mutex_lock()的)具有副作用和改变执行环境。因此,编译器必须重新读取全局变量,该变量可能会因调用该函数而发生变化。

要确定的是,您想咨询C99的5.1.2.3 Program execution从哪里借用了术语。为了让你的味道:

[...]访问一个volatile对象,修改对象,修改一个文件,或调用任何做这些操作都是副作用,这是变化的函数处于执行环境的状态。评估表达可能会产生副作用。在执行顺序中的某些特定点上,称为顺序点,先前评估的所有副作用都应完整,且不会发生后续评估的副作用。 (附录C中给出了序列点的摘要。)

在抽象机器中,所有表达式都按照语义指定进行评估。一个实际的实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用并且没有产生任何需要的副作用(包括由调用一个函数或访问一个volatile对象引起的任何副作用)。 [...]从附录C

摘录:

以下是5.1.2.3中描述的序列点:
- 给一个函数调用,该参数已经被评估之后(6.5.2.2)。

并从那里。

根据我的经验,编译器现在足够聪明,甚至在积极优化时也不会对全局变量的循环控制变量做任何事情。

+0

即使添加了volatile关键字,也需要锁定。这有两个原因。更新整数可能不是原子的。即使写入是原子性的,在具有单独缓存的多核机器上,也不能保证读取和写入将按顺序发生。确保按顺序发生的唯一方法是插入内存屏障指令。锁将确保这肯定发生。 – doron 2010-07-30 23:12:22

+0

@ deus-ex-machina399:关于原子性。几乎所有的拱都有int小于或等于cpu字。这使得读/写操作成为原子。 (增加/减少/等 - 不是原子的,因为它们结合了多个操作:读取,更改值,写入。)关于排序。在上面的简单读/写代码的情况下,缓存一致性保证就足够了。 http://en.wikipedia.org/wiki/Cache_coherence – Dummy00001 2010-07-31 09:39:12

+0

如果我们在调用signal之前写入一个全局变量(如myval),然后另一个线程在等待退出后读取该变量,那么您将遇到问题。如果没有内存屏障,由于读取和写入的重新排序,等待线程可能会在全局变量(myval)被设置之前获得读取准备。数据存储屏障将确保不会发生。请参阅http://software.intel.com/zh-cn/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/ – doron 2010-07-31 13:20:43

1

挥发性既不需要也不足够。所以没有理由使用它。这是不够的,因为没有标准指定它将提供线程之间的可见性。这是没有必要的,因为pthreads标准说互斥量就足够了。

更糟糕的是,使用它表明你是一个无能的程序员,他试图在你的代码上撒上魔法灰尘来让它工作。它充满了对货物崇拜的编程,任何查看代码的人都会推断你不知道它不是必需的。更糟糕的是,他们可能认为你觉得已经足够了,并且他们会怀疑你写的任何其他代码,担心你使用“volatile”隐藏多线程错误而不是修复它们。

+0

我很好奇,知道一个像本地POSIX线程库这样的pthread实现如何处理共享变量的情况,编译器? – 2014-01-13 14:47:47

+0

@ManuelSelva每个实现都可以处理它,但它是想要的。通常,没有什么特别的你需要做。任何另一个线程都可以访问共享变量的方式,另一个编译单元中的函数也可以访问它,所以编译器已经做了正确的事情。 – 2014-01-13 17:17:35