我想在寫任何多執行序(mulit thread)的程式時,最常碰到就是 data race的問題,讀書會剛好我負責講解這篇,也替自己做一個筆記。

首先先介紹一下 sync 這個 package裡面需要了解到的api。

sync

先介紹一下 golang 的 package sync 他有實現兩種鎖 Mutex & RWMutex,RWMutex 是基於 Mutex實現的

sync.Mutex

又稱全局鎖、互斥鎖,使用Lock() 後 便不能在對它加鎖,直到Unlock()呼叫,反之在Unlock()呼叫之前,沒有Lock()執行過,會噴錯

var l sync.Mutex

var a string

func f() {
    a = "hello, world"
    l.Unlock()
}

func main() {

    l.Lock()
    go f()
    l.Lock()
    print(a)

}

example

第二個l.Lock 會在那邊等待第一個 l.Lock 被 f() 裡面的 l.Unlock 解鎖後才會繼續往下執行

sync.RWMutex

可以稱為讀寫鎖,可以加多個讀鎖或是一個寫鎖,呼叫寫鎖時 Lock(),如果有其他的讀鎖或是寫鎖,則Lock()或阻塞到該鎖可以用為止,就是寫鎖時優先寫鎖

RLock()

var m *sync.RWMutex

func main() {
    m = new(sync.RWMutex)
    go read(1)
    go read(2)

    //為了等待其他goroutine 跑完
    time.Sleep(10*time.Second)
}

func read(i int) {
    println(i,"read start")

    m.RLock()
    println(i,"reading")
    time.Sleep(1*time.Second)
    m.RUnlock()    

    println(i,"read over")
}

example

Lock()

var m *sync.RWMutex

func main() {
    m = new(sync.RWMutex)

    go write(1)
    go read(2)
    go write(3)

    //為了等待其他 goroutine 跑完
    time.Sleep(10*time.Second)
}

func read(i int) {
    println(i,"read start")

    m.RLock()
    println(i,"reading")
    time.Sleep(1*time.Second)
    m.RUnlock()    

    println(i,"read over")
}

func write(i int) {
    println(i,"write start")

    m.Lock()
    println(i,"writing")
    time.Sleep(1*time.Second)
    m.Unlock()

    println(i,"write over")
}

example

sync.WaitGroup


func main() {
    var wg sync.WaitGroup

    //所有要抓取的url
    var urls = []string{
        "http://www.golang.org/",
        "http://www.google.com/",
        "http://tw.yahoo.com/",
    }
    for _, url := range urls {
        // 群組計數器
        wg.Add(1)
        // Launch a goroutine to fetch the URL.
        go func(url string) {
            // 這個 goroutine結束 遞減
            defer wg.Done()
            // 抓url
            http.Get(url)
            fmt.Println(url);
        }(url)
    }
    // 等待全部response返回
    wg.Wait()
    fmt.Println("over");
}

example

Data Race

再來,有的前面的介紹我們進入一些比較時常見的應用,我們了解一下什麼是 data race 以下引自 wiki

A race condition or race hazard is the behavior of an electronic, software or other system where the output is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when events do not happen in the order the programmer intended. The term originates with the idea of two signals racing each other to influence the output first. Race conditions can occur in electronics systems, especially logic circuits, and in computer software, especially multithreaded or distributed programs.

簡單來說就是兩個進程都要修改一個共享內容時,在沒有併發控制的情況,只能仰賴兩個進程執行順序與時機,會導致結果錯誤。

用個簡單例子來說,下面是一個在其他語言很常見的 singleton 模式,如果要再這種 muilt thread 環境運行下,該怎麼確保資源不會重複建構

type Singleton struct{
    Data int
}

var instance * Singleton
func GetInstance() * Singleton {
    if instance == nil {
        time.Sleep(5 * time.Second)

        // 仿造一個耗費資源的建構情境
        println("Init instance")
        instance = &Singleton{42}
    }
    
    println("finish instance")
    return instance
}

func main(){
    go GetInstance()
    go GetInstance()

    // 等待上面兩個 goroutine 執行完
    time.Sleep(7 * time.Second)
    println("end")

}


example

加入前面講過得讀寫鎖,防止重複建構

var mutex = &sync.RWMutex{}

type Singleton struct{
    Data int
}

var instance * Singleton
func GetInstance() * Singleton {
    mutex.RLock()
    if instance == nil {
        mutex.RUnlock()
        mutex.Lock()
        defer mutex.Unlock()
        if instance == nil {
            time.Sleep(5 * time.Second)

            // 仿造一個耗費資源的建構情境
            println("Init instance")
            instance = &Singleton{42}
        }
        
        println("finish instance")
        return instance
    }else {
        defer mutex.RUnlock()
        
        println("instance exist")
        return instance
    }
}

func main(){
    go GetInstance()
    go GetInstance()

    // 等待上面兩個 goroutine 執行完
    time.Sleep(7 * time.Second)
    println("end")

}


example