前言:
單個線程時數(shù)據(jù)操作的只有一個線程,數(shù)據(jù)的修改也只有一個線程參與,數(shù)據(jù)相對來說是安全的,多線程時對數(shù)據(jù)操作的不止一個線程,所以同時對數(shù)據(jù)進行修改的時候難免紊亂
一、互斥鎖是什么?
1.概念
互斥鎖是為了并發(fā)的安全,在多個goroutine
共同工作的時候,對于共享的數(shù)據(jù)十分不安全寫入時容易因為競爭造成數(shù)據(jù)不必要的丟失。互斥鎖一般加在共享數(shù)據(jù)修改的地方。
2.未加鎖
- 線程不安全,操作的全局變量會計算異常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package main import ( "fmt" "sync" ) var x int = 0 var wg sync .WaitGroup func add() { defer wg.Done() for i := 0; i < 5000; i++ { x++ } } func main() { wg.Add(2) go add() go add() wg.Wait() fmt .Println(x) } /* 打印結果:(每次打印不一樣,正常的結果應該是10000) 6051 5059 5748 10000 */ |
3.加鎖之后
- 線程安全,全局變量計算無異常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
package main import ( "fmt" "sync" ) var x int = 0 var wg sync .WaitGroup // 創(chuàng)建一個鎖對象 var lock sync .Mutex func add() { defer wg.Done() for i := 0; i < 5000; i++ { // 加鎖 lock.Lock() x++ // 解鎖 lock.Unlock() } } func main() { wg.Add(2) // 開啟兩個線程 go add() go add() wg.Wait() fmt .Println(x) } /* 打印結果: 全為10000 */ |
二、讀寫鎖【效率革命】
1.為什么讀寫鎖效率高
使用鎖的時候,安全與效率往往需要互相轉換,對數(shù)據(jù)進行操作的時候,只會進行數(shù)據(jù)的讀與寫。 而讀與讀之間可以同時進行,讀與寫之間需要保證寫的時候不去讀。此時為了提高效率就發(fā)明讀寫鎖,在讀寫鎖機制下,安全沒有絲毫降低,但效率進行了成倍的提升提升的效率在讀與寫操作次數(shù)差異越大時越明顯
2.使用方法
代碼如下(示例):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
package main import ( "fmt" "sync" "time" ) var ( x = 0 rwlock sync .RWMutex wg sync .WaitGroup ) func write() { defer wg.Done() rwlock.Lock() x++ rwlock.Unlock() } func read () { wg.Done() // 開啟讀鎖 rwlock.RLock() fmt .Println(x) // 釋放讀鎖 rwlock.RUnlock() } func main() { start := time .Now() for i := 0; i < 100; i++ { wg.Add(1) go write() } // time .Sleep( time .Second) for i := 0; i < 10000; i++ { wg.Add(1) go read () } wg.Wait() fmt .Println( time .Now().Sub(start)) } |
三、sync.once
1.sync.once產生背景
在多個goroutine
中往往會由于線程不同步造成數(shù)據(jù)讀寫的沖突,特別是在進行文件打開對象創(chuàng)建的時候,可能會造成向關閉的文件寫內容,使用未初始化的對象,或者對一個對象進行多次初始化。
2.sync.once機制概述
sync.once
保證函數(shù)內的代碼只執(zhí)行一次, 實現(xiàn)的機制是在once內部有一個標志位,在執(zhí)行代碼的時候執(zhí)行一次之后標志位將置為1后續(xù)判斷標志位,如果標志位被改為1則無法再進行操縱
3.sync.once注意點
sync.Once.Do()
傳進去的函數(shù)參數(shù)無參無返,一個once對象只能執(zhí)行一次Do方法,向Do方法內傳多個不同的函數(shù)時只能執(zhí)行第一個傳進去的,傳進去Do方法的函數(shù)無參無返,可以用函數(shù)閉包把需要的變量傳進去
4.使用方法
- 一般結合并發(fā)使用,旨在對通道或文件只進行一次關閉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func f2(a <-chan int, b chan<- int) { for { x, ok := <-a if !ok { break } fmt .Println(x) b <- x * 10 } // 確保b通道只關閉一次 once.Do(func() { close(b) }) } |
四、atomic原子包操作
原子包將指定的數(shù)據(jù)進行安全的加減交換操作; 網(wǎng)上還有一大堆關于原子包的api感興趣的小伙伴可以自行百度,這里就不細細闡述了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
package main import ( "fmt" "sync" "sync/atomic" ) var x int64 = 0 var wg sync .WaitGroup /* 原子操作是將數(shù)據(jù)進行打包枷鎖,直接通過指定的函數(shù)進行相應的操作 可以使用load讀取、store寫入、add修改、swap交換。 // 類似于讀取一個變量、對一個變量進行賦值 */ func addone() { // 沒有加鎖進行并發(fā)的話,會產生數(shù)據(jù)丟失的情況 defer wg.Done() // x++ // 不用加鎖也可以使用的行云流水 // 第一個參數(shù)是進行操作的數(shù)據(jù),第二個是增加的步長 atomic.AddInt64(&x, 1) } func csf() { // 進行比較相等則將新值替換舊值 ok := atomic.CompareAndSwapInt64(&x, 100, 200) fmt .Println(ok, x) } func main() { for i := 0; i < 50000; i++ { wg.Add(1) go addone() } wg.Wait() fmt .Println(x) x = 100 csf() fmt .Println(123) } |
總結:
讀寫鎖區(qū)分讀者和寫者,而互斥鎖不區(qū)分 互斥鎖同一時間只允許一個線程訪問該對象,無論讀寫;讀寫鎖同一時間內只允許一個寫者, 但是允許多個讀者同時讀對象。 聯(lián)系:讀寫鎖在獲取寫鎖的時候機制類似于互斥鎖。
到此這篇關于Go語言線程安全之互斥鎖與讀寫鎖的文章就介紹到這了,更多相關Go語言互斥鎖與讀寫鎖內容請搜索服務器之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/apple_51931783/article/details/122553203