Mutex和内存可见性

时间:2022-04-06 17:44:37

http://ifeve.com/mutex-and-memory-visibility/

POSIX内存可见性规则

IEEE 1003.1-2008定义了XBD 4.11内存同步中的内存可见性规则。特别地,POSIX实现保证:

  • pthread_create()同步:任何变量在pthread_create()调用之前修改,对刚由它创建的线程来说是可见的。当变量在pthread_create()之后修改,那么这条规则就不能保证了,即使是在线程开始执行之前修改的。
  • pthread_join()同步:任何变量由某线程在结束之前修改,那回收(join)它的另一线程 在pthread_join()完成后是可见的。
  • mutex操作——pthread_lock(), pthread_timedlock(), pthread_trylock() , pthread_unlock()同步:任何变量由线程对mutex解锁之前修改,对后面成功锁住同一mutex的线程是可见的,请参阅图2。再强调一次,如果锁住另一个mutex,或者根本没有加锁,又或者变量在pthread_unlock之后又被修改的,这一规则不保证。

Mutex和内存可见性

图2:mutex引入正确的内存可见性

总结

读完本文后,你应该弄明白Cert POS03-C编码规则背后的原因:

POS03-C:请勿使用volatile作为同步原语

只要遵从POSIX的内存可见性规则这条底线,编写出来的代码理所当然是安全的。特别当一个线程写某个值,而另一线程读此值时,即使能保证原子访问,仍需要使用mutex来构造适当的内存同步访问。

Posix 内存可见性:

1 当线程调用pthread_create时,它所能看到的内存值也是它建立的线程能够看到的。任何在调用pthread_create之后向内存写入的数据,可能不会被建立的线程看到,即使写操作发生在启动新线程之前。
2 当线程解锁互斥量时看到的内存中的数据,同样也能被后来直接锁住(或通过等待条件变量锁住)相同互斥量的线程看到。同样,在解锁互斥量之后写入的数据不必被其他线程看见,即使写操作发生在其他线程锁互斥量之前。
3 线程终止(或者通过取消操作,或者从启动函数中返回,或者调用pthread_exit)时看到的内存数据,同样能够被连接该线程的其他线程(通过调用pthread_join)看到。当然,终止后写入的数据不会被连接线程看到,即使写操作发生在连接之前。
4 线程发信号或广播条件变量时看到的内存数据,同样可以被唤醒的其他线程看到。而在发信号或广播之后写入的数据不会被唤醒的线程看到,即使写操作发生在线程被唤醒之前。

在实际应用的使用方法:

首先,确保哪些可能的地方只有一个线程能访问某段数据。线程的寄存器变量不能被其他线程修改。线程分配的堆栈和堆空间是私有的,除非线程将指向该内存的指针传给其他线程。任何放在register或auto变量中的数据可以在随后的某时刻读取,就像在完全同步的程序中一样。在线程间共享的数据越少,需要做的工作越多。

其次,任何时候两个线程需要访问相同数据时,你就需要应用其中一条内存可视化规则,大多数情况下是指使用互斥量,这不仅是为了保护多个写操作,即使线程是读数据,它也需要锁住互斥量以确保读到最新的数据值。
有一些特别的情况下不需要使用互斥量来确保可视性。如果线程设置了一个全局变量,然后创建了一个新线程读取同一个变量,则新线程将看不到旧的变量值(只能看到改过的新的变量值)。但是,若你创建了一个新线程然后再设置变量的值,则新线程可能看不到新的变量值,即使原线程在新线程读取数据之前向变量内写入了新值。