对Memory Reordering Caught in the Act的学习

最近迷上了preshing.com,真的是非常专业的blog,每篇深浅合适而且可以相互印证,达到出书的质量了

学习了Memory Reordering Caught in the Act,内容很简单,主要是说“即使汇编码是顺序的,CPU执行时会对Load-Save进行乱序执行,导致无锁的两线程出现意料之外的结果”

简述一下:

原因是CPU会做乱序执行,因为Ta/Tb的代码乱序后,比如r1=b, a=1,从单线程的角度来看对结果没有影响。而对于多线程,就会出现r1=r2=0的状况

解决方案是在两句之间插入Load-Save fence,参看这里

我自己用go想重现这个场景,代码参看最后。但是奇怪的是go的编译码跟文章描述的差不多

    [thread 1]
    ...
    MOVQ    $1,a+0(SB)
    MOVQ    b+0(SB),BX
    MOVQ    BX,r1+0(SB)
    
    [thread 2]
    MOVQ    $1,b+0(SB)
    MOVQ    a+0(SB),BX
    MOVQ    BX,r2+0(SB)

但是在MBP (Intel Core i7)上跑并没有出现CPU乱序的现象,希望有同学能帮我提供线索,谢谢

(2013.11.11 更新:关于以上现象的原因参看续 - 关于go的部分)

go 代码:

    package main
    
    import (
    	"fmt"
    	"math/rand"
    )
    
    var x, y, r1, r2 int
    var detected = 0
    
    func randWait() {
    	for rand.Intn(8) != 0 {
    	}
    }
    
    func main() {
    	beginSig1 := make(chan bool, 1)
    	beginSig2 := make(chan bool, 1)
    	endSig1 := make(chan bool, 1)
    	endSig2 := make(chan bool, 1)
    	go func() {
    		for {
    			<-beginSig1
    			randWait()
    			x = 1
    			r1 = y
    			endSig1 <- true
    		}
    	}()
    	go func() {
    		for {
    			<-beginSig2
    			randWait()
    			y = 1
    			r2 = x
    			endSig2 <- true
    		}
    	}()
    	for i := 1; ; i = i + 1 {
    		x = 0
    		y = 0
    		beginSig1 <- true
    		beginSig2 <- true
    		<-endSig1
    		<-endSig2
    		if r1 == 0 && r2 == 0 {
    			detected = detected + 1
    			fmt.Println(detected, "reorders detected after ", i, "iterations")
    		}
    	}
    }
comments powered by Disqus