您好,登錄后才能下訂單哦!
本篇內容主要講解“怎么使用Go語言sync包與鎖實現限制線程對變量的訪問”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么使用Go語言sync包與鎖實現限制線程對變量的訪問”吧!
Go語言中 sync 包里提供了互斥鎖 Mutex 和讀寫鎖 RWMutex 用于處理并發過程中可能出現同時兩個或多個協程(或線程)讀或寫同一個變量的情況。
鎖是 sync 包中的核心,它主要有兩個方法,分別是加鎖(Lock)和解鎖(Unlock)。
在并發的情況下,多個線程或協程同時其修改一個變量,使用鎖能保證在某一時間內,只有一個協程或線程修改這一變量。
不使用鎖時,在并發的情況下可能無法得到想要的結果,如下所示:
package main import ( "fmt" "time" ) func main() { var a = 0 for i := 0; i < 1000; i++ { go func(idx int) { a += 1 fmt.Println(a) }(i) } time.Sleep(time.Second) }
從理論上來說,上面的程序會將 a 的值依次遞增輸出,然而實際結果卻是下面這樣子的。
537
995
996
997
538
999
1000
通過運行結果可以看出 a 的值并不是按順序遞增輸出的,這是為什么呢?
協程的執行順序大致如下所示:
從寄存器讀取 a 的值;
然后做加法運算;
最后寫到寄存器。
按照上面的順序,假如有一個協程取得 a 的值為 3,然后執行加法運算,此時又有一個協程對 a 進行取值,得到的值同樣是 3,最終兩個協程的返回結果是相同的。
而鎖的概念就是,當一個協程正在處理 a 時將 a 鎖定,其它協程需要等待該協程處理完成并將 a 解鎖后才能再進行操作,也就是說同時處理 a 的協程只能有一個,從而避免上面示例中的情況出現。
上面的示例中出現的問題怎么解決呢?加一個互斥鎖 Mutex 就可以了。那什么是互斥鎖呢 ?互斥鎖中其有兩個方法可以調用,如下所示:
func (m *Mutex) Lock() func (m *Mutex) Unlock()
將上面的代碼略作修改,如下所示:
package main import ( "fmt" "sync" "time" ) func main() { var a = 0 var lock sync.Mutex for i := 0; i < 1000; i++ { go func(idx int) { lock.Lock() defer lock.Unlock() a += 1 fmt.Printf("goroutine %d, a=%d\n", idx, a) }(i) } // 等待 1s 結束主程序 // 確保所有協程執行完 time.Sleep(time.Second) }
運行結果如下:
goroutine 995, a=996
goroutine 996, a=997
goroutine 997, a=998
goroutine 998, a=999
goroutine 999, a=1000
需要注意的是一個互斥鎖只能同時被一個 goroutine 鎖定,其它 goroutine 將阻塞直到互斥鎖被解鎖(重新爭搶對互斥鎖的鎖定),示例代碼如下:
package main import ( "fmt" "sync" "time" ) func main() { ch := make(chan struct{}, 2) var l sync.Mutex go func() { l.Lock() defer l.Unlock() fmt.Println("goroutine1: 我會鎖定大概 2s") time.Sleep(time.Second * 2) fmt.Println("goroutine1: 我解鎖了,你們去搶吧") ch <- struct{}{} }() go func() { fmt.Println("goroutine2: 等待解鎖") l.Lock() defer l.Unlock() fmt.Println("goroutine2: 歐耶,我也解鎖了") ch <- struct{}{} }() // 等待 goroutine 執行結束 for i := 0; i < 2; i++ { <-ch } }
上面的代碼運行結果如下:
goroutine1: 我會鎖定大概 2s
goroutine2: 等待解鎖
goroutine1: 我解鎖了,你們去搶吧
goroutine2: 歐耶,我也解鎖了
讀寫鎖有如下四個方法:
寫操作的鎖定和解鎖分別是func (*RWMutex) Lock和func (*RWMutex) Unlock;
讀操作的鎖定和解鎖分別是func (*RWMutex) Rlock和func (*RWMutex) RUnlock。
讀寫鎖的區別在于:
當有一個 goroutine 獲得寫鎖定,其它無論是讀鎖定還是寫鎖定都將阻塞直到寫解鎖;
當有一個 goroutine 獲得讀鎖定,其它讀鎖定仍然可以繼續;
當有一個或任意多個讀鎖定,寫鎖定將等待所有讀鎖定解鎖之后才能夠進行寫鎖定。
所以說這里的讀鎖定(RLock)目的其實是告訴寫鎖定,有很多協程或者進程正在讀取數據,寫操作需要等它們讀(讀解鎖)完才能進行寫(寫鎖定)。
我們可以將其總結為如下三條:
同時只能有一個 goroutine 能夠獲得寫鎖定;
同時可以有任意多個 gorouinte 獲得讀鎖定;
同時只能存在寫鎖定或讀鎖定(讀和寫互斥)。
示例代碼如下所示:
package main import ( "fmt" "math/rand" "sync" ) var count int var rw sync.RWMutex func main() { ch := make(chan struct{}, 10) for i := 0; i < 5; i++ { go read(i, ch) } for i := 0; i < 5; i++ { go write(i, ch) } for i := 0; i < 10; i++ { <-ch } } func read(n int, ch chan struct{}) { rw.RLock() fmt.Printf("goroutine %d 進入讀操作...\n", n) v := count fmt.Printf("goroutine %d 讀取結束,值為:%d\n", n, v) rw.RUnlock() ch <- struct{}{} } func write(n int, ch chan struct{}) { rw.Lock() fmt.Printf("goroutine %d 進入寫操作...\n", n) v := rand.Intn(1000) count = v fmt.Printf("goroutine %d 寫入結束,新值為:%d\n", n, v) rw.Unlock() ch <- struct{}{} }
其執行結果如下:
goroutine 0 進入讀操作...
goroutine 0 讀取結束,值為:0
goroutine 3 進入讀操作...
goroutine 1 進入讀操作...
goroutine 3 讀取結束,值為:0
goroutine 1 讀取結束,值為:0
goroutine 4 進入寫操作...
goroutine 4 寫入結束,新值為:81
goroutine 4 進入讀操作...
goroutine 4 讀取結束,值為:81
goroutine 2 進入讀操作...
goroutine 2 讀取結束,值為:81
goroutine 0 進入寫操作...
goroutine 0 寫入結束,新值為:887
goroutine 1 進入寫操作...
goroutine 1 寫入結束,新值為:847
goroutine 2 進入寫操作...
goroutine 2 寫入結束,新值為:59
goroutine 3 進入寫操作...
goroutine 3 寫入結束,新值為:81
下面再來看兩個示例。
【示例 1】多個讀操作同時讀取一個變量時,雖然加了鎖,但是讀操作是不受影響的。(讀和寫是互斥的,讀和讀不互斥)
package main import ( "sync" "time" ) var m *sync.RWMutex func main() { m = new(sync.RWMutex) // 多個同時讀 go read(1) go read(2) time.Sleep(2*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") }
運行結果如下:
1 read start
1 reading
2 read start
2 reading
1 read over
2 read over
【示例 2】由于讀寫互斥,所以寫操作開始的時候,讀操作必須要等寫操作進行完才能繼續,不然讀操作只能繼續等待。
package main import ( "sync" "time" ) var m *sync.RWMutex func main() { m = new(sync.RWMutex) // 寫的時候啥也不能干 go write(1) go read(2) go write(3) time.Sleep(2*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") }
運行結果如下:
1 write start
3 write start
1 writing
2 read start
1 write over
2 reading
到此,相信大家對“怎么使用Go語言sync包與鎖實現限制線程對變量的訪問”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。