线程间的内存可视性:
(1).Pthreads提供了一些关于内存可视性的基本原则:
1.当线程调用pthread_create时,线程所能看到的内存也是新线程所能看到的。任何在调用pthread_create之后向内存写入的数据,可能不会被新线程看到,即使写操作发生在新线程启动前。--经测试,在fedora7环境下没有这样的效果。
2.当线程解锁互斥量时所能看到的内存数据,同样也被后来直接锁住(或者通过等待条件变量锁住)相同互斥量的线程看到。同样,在解锁互斥量之后写入的数据不必被其它线程看到,即使写操作发生在其它线程锁住互斥量以前。--经测试,在fedora7环境下没有这样的效果。
3.线程终止(被其它线程取消,或者从启动函数返回,或者调用pthread_exit)是看到的数据,同样可以被连接该线程的其它线程(通过pthread_join)所看到。同样,终止后写入内存的数据不会被连接的线程看到,即使写操作发生在连接之前。
4.线程发送信号或者广播条件变量时看到的内存数据,同样可以被唤醒的其它线程看到。同样,数据在发送信号或者广播条件变量后写入,不会被唤醒的线程看到,即使写操作发生在其它线程被唤醒之前。
(2).程序员需要注意的地方:
1.确定哪些地方只有一个线程可以访问。线程的寄存器变量不能被其它线程修改,线程分配的堆栈和堆空间是线程私有的,除非线程把指向该内存的指针传递给其它线程,线程中任何寄存器变量和自动变量的数据都可以在随后任意时刻访问,每个线程与自己是同步的。
2.任何时候两个线程需要访问相同数据时,我们都需要应用上述的可视化规则中的一种。通常使用互斥量,不仅是为了保护多个写操作,即使线程是读数据,它也需要锁住互斥量以确保读到最新的数据值。
(3).与Pthreads API相关的内存硬件结构问题:
1.在单一线程中,即在完全同步的程序中,在任何时刻读写任何内存都是安全的。即:程序向某个内存写入数据,在随后的某个时刻读该内存的数据,总是能够读到“最后更新“的数据。(这是显然的!)。
2.当两个线程一个接一个的向同一个内存地址写不同的数据时,最终的结果像是一个线程按相同的顺序写两次一样。问题在于如何知道哪个写操作最后发生。比如:处理器A向某个内存写入1,处理器B向同一个内存写入2,但并不意味着最终的结果是2!
因为处理器可能有高速缓存,用来保存最近从主存中读出数据拷贝的快速本地内存。在一个“回写“的高速缓存系统中,数据最初始保存在告诉缓存中的,而不是立刻更新到内存,只是在以后的某个合适的时间将告诉缓存里的数据刷新到内存,这时候写操作才算是真正的完成。“在不保证读写顺序的系统中“,告诉缓存的数据快可能在任何处理器认为方便的时候写入主存。上面的例子中,即使通过精确的外部时间测量,知道处理器A先往高速缓存中存放1,B随后存放2,也不能确定最后的内存的值是2,因为从缓存写到内存并不保证B后写。
同样,如果两个处理器(两个线程)向同一地址写不同的数据,则不同的数据被存放在两者的告诉缓存中,最后两个值从高缓写入到内存的顺序,与相应的从处理器写入到高缓的顺序无关;即使是同一线程(或者处器)中的两个写操作也不需要在内存中表现相同的顺序,内存控制器可能发现以相反的顺序写入会更快或者更方便(如下表所示)。
时间 线程1 线程2
t 向地址1(cache)中写入1
t+1 向地址2(cache)中写入2 从地址1读入0
t+2 cache刷新地址2
t+3 从地址2读入2
t+4 cache刷新地址1
3.
<1>.问题并不局限与多个线程写内存。想象一下,一个线程在一个处理器上向内存中写入数据,另一个线程在其它处理器上读相同的内存,好像很显然会读到最新写入的值,而在有些硬件上确实如此,这种现象有时被称为“内存一致性”或者“读写排序”。但在处理器间保持这种同步是很复杂的,会降低内存的速度,并对大多数代码没有任何意义。现在很多计算机尤其是最快的计算机。都不再保证不同处理器间内存访问顺序,除非程序使用特殊的指令,这些指令就是常说的“内存屏障"(memory barrier)。
<2>.在使用内存屏障的计算机的内存访问,至少原则上被内存控制器排队,并以最高效的顺序处理。如果读取没有在cache中包含的地址数据,则读操作一直等到随后cache添入需要的数据后结束,如果向“脏”的cache地址中写数据,则写操作将等到将cache刷新后才能结束。
<3>.内存屏障保证:所有发生在设置内存屏障之前的内存访问,必须先于在设置内存屏障之后发起的内存访问之前完成。
所以经常对内存屏障的误解是,内存屏障是将cache刷新到主存中,以保证数据对其它处理器可见。实际不是这样,内存屏障要作的是对一组操作排序,如果每个内存访问俄对应队列中的一个元素,你可以将内存屏障看作是队列中的一个令牌,不像其它内存访问,内存控制器不能删除内存屏障,不能越过它,直到完成内存屏障前的所有操作。
例如:互斥量锁开始于锁住互斥量,结束于发布一个内存屏障,结果是任何在互斥量锁住期间才能进行的内存访问都不能在其它线程看到互斥量被锁住之前完成。
解锁互斥量开始于发布一个内存屏障,而结束于解锁互斥量,以确保所有在互斥量锁住期间的内存操作不能晚于其它线程看到该互斥量解锁完成。
下面的例子使用互斥量来保证期望的读写排序:
时间 线程1 线程2
t 锁住互斥量(内存屏障)
t+1 向地址1(cache)中写入1
t+2 向地址2(cache)中写入2
t+3 (内存屏障)解锁互斥量
t+4 锁住互斥量(内存屏障)
t+5 从地址1中读出1
t+6 从地址2中读出2
t+7 (内存屏障)解锁互斥量
<4>.内存屏障模型隐藏在Pthreads内存规则描述后面的逻辑,对于每个规则而言,都有一个源事件(如线程调用pthread_mutex_unlock)和目标事件(如其它线程从调用pthread_mutex_lock中返回),由于在每个事件中小心的设置了内存屏障,所以“内存视图“从第一个线程传到第二个线程。
阅读(1366) | 评论(0) | 转发(0) |