go併發程式設計入門-常用同步原語

1。sync。Mutex 互斥鎖2。sync。RWMutex 讀寫鎖3。sync。WaitGroup 等待一組goroutine完成工作4。sync。Once 無論呼叫多少次Do方法,都只執行一次。5。atomic。Value 提供原子獲取值和儲存值

1.sync.Mutex 互斥鎖

互斥鎖:一段程式碼使用了互斥鎖加鎖,只會有一個goroutine執行它,其他執行這段程式碼的goroutine都會堵塞等待。

保證同一時刻內,只有一個 goroutine 持有鎖,其他 goroutine 都要等待持有鎖的 goroutine 釋放鎖後,才能繼續執行。持有鎖的 goroutine 沒有釋放,其他等待加鎖 goroutine都會堵塞。在同一個 goroutine 內 二次加鎖會導致死鎖。

透過下面的2個例子我們驗證:

結論1: goroutine 2 必須等待 goroutine 1 鎖釋放了,才能獲取到鎖,執行後續操作。

結論2: 在同一個 goroutine 內 二次加鎖會導致死鎖。

package mainimport ( “fmt” “sync” “time”)// 互斥鎖:保證同一時刻內,保證只有一個 goroutine 持有鎖,其他 goroutine 都要等待持有鎖的 goroutine 釋放鎖後,才能繼續執行。// 示例:goroutine 2 必須等待 goroutine 1 鎖釋放了,才能獲取到鎖,執行後續操作。var mutex sync。Mutexfunc main() { // goroutine 1 fmt。Println(“goroutine 1 before”) mutex。Lock() fmt。Println(“goroutine 1 locked”) // goroutine 2 go func() { fmt。Println(“goroutine 2 lock before”) // 這裡會堵塞,一直到 goroutine 1 釋放鎖,goroutine 2才能繼續執行 mutex。Lock() fmt。Println(“goroutine 2 locked”) mutex。Unlock() fmt。Println(“goroutine 2 unlock”) }() for i := 1; i < 3; i++ { time。Sleep(time。Second * 1) } mutex。Unlock() fmt。Println(“goroutine 1 unlock”) time。Sleep(time。Second * 2)}/**goroutine 1 beforegoroutine 1 lockedgoroutine 2 lock beforegoroutine 1 unlockgoroutine 2 lockedgoroutine 2 unlock*/

package mainimport ( “fmt” “sync”)var mutex sync。Mutexfunc main() { // goroutine 1 fmt。Println(“goroutine 1 before”) mutex。Lock() fmt。Println(“goroutine 1 locked”) // 在同一個 goroutine 內 二次加鎖會導致死鎖。 mutex。Lock() mutex。Unlock() fmt。Println(“goroutine 1 unlock”)}/**goroutine 1 beforegoroutine 1 lockedfatal error: all goroutines are asleep - deadlock!goroutine 1 [semacquire]:sync。runtime_SemacquireMutex(0x118c234, 0x0, 0x1) /usr/local/Cellar/go/1。16/libexec/src/runtime/sema。go:71 +0x47sync。(*Mutex)。lockSlow(0x118c230) /usr/local/Cellar/go/1。16/libexec/src/sync/mutex。go:138 +0x105sync。(*Mutex)。Lock(。。。) /usr/local/Cellar/go/1。16/libexec/src/sync/mutex。go:81main。main() /Users/concurrency/value。go:16 +0x1a5 */

2.sync.RWMutex 讀寫鎖

讀寫鎖:讀鎖寫鎖互斥,有讀鎖時,寫鎖會堵塞,有寫鎖時讀堵塞,只有讀鎖不會堵塞,常用於讀多寫少的場景

Lock/Unlock 針對寫操作:  1。 不管有其他 goroutine 持有讀鎖還是 寫鎖,Lock 會一直堵塞, 直到 Unlock/RUnlock 來釋放鎖RLock/RUnlock 針對讀操作:  1。 當其他 goroutine持有讀鎖時,RLock直接返回  2。 當有 goroutine 持有寫鎖時 RLock會一直堵塞,直到能獲取鎖

package mainimport ( “sync” “time”)/*讀寫鎖:讀鎖寫鎖互斥,有讀鎖時,寫鎖會堵塞,有寫鎖時讀堵塞,只有讀鎖不會堵塞,常用語讀多寫少的場景Lock/Unlock 針對寫操作: 1。 不管有其他 goroutine 持有讀鎖還是 寫鎖,Lock 會一直堵塞,直到 Unlock/RUnlock 來釋放鎖RLock/RUnlock 針對讀操作: 1。 當其他 goroutine持有讀鎖時,RLock直接返回 2。 當有 goroutine 持有寫鎖時 RLock會一直堵塞,直到能獲取鎖 */var rwMutex sync。RWMutexfunc main() { // goroutine 1 fmt。Println(“goroutine 1 before”) rwMutex。RLock() fmt。Println(“goroutine 1 locked”) go func(){ fmt。Println(“goroutine 2 before”) // goroutine 2 獲取寫鎖會堵塞,只有 goroutine 1 釋放鎖以後, goroutine 2 才會繼續執行 rwMutex。Lock() fmt。Println(“goroutine 2 locked”) for i := 1; i < 3; i++ { time。Sleep(time。Second * 1) } rwMutex。Unlock() fmt。Println(“goroutine 2 unlock”) }() go func(){ // 休眠1秒讓 goroutine 2 獲得寫鎖 time。Sleep(time。Second * 1) fmt。Println(“goroutine 3 before”) // 這裡會堵塞,等待 goroutine 2解鎖後才能獲得鎖 rwMutex。RLock() fmt。Println(“goroutine 3 locked”) rwMutex。RUnlock() fmt。Println(“goroutine 3 unlock”) }() for i := 1; i < 3; i++ { time。Sleep(time。Second * 1) } time。Sleep(time。Second * 5) fmt。Println(“goroutine 1 sleep 5 second”) // goroutine 1 解鎖後, goroutine 2才能獲得寫鎖 rwMutex。RUnlock() fmt。Println(“goroutine 1 unlock”) time。Sleep(time。Second * 10)}/**goroutine 1 beforegoroutine 1 lockedgoroutine 2 beforegoroutine 3 beforegoroutine 1 sleep 5 secondgoroutine 1 unlockgoroutine 2 lockedgoroutine 2 unlockgoroutine 3 lockedgoroutine 3 unlock*/

package mainimport ( “fmt” “sync” “time”)// 讀鎖之間互不影響var rwMutex sync。RWMutexfunc main() { // goroutine 1 fmt。Println(“goroutine 1 before”) rwMutex。RLock() fmt。Println(“goroutine 1 locked”) go func(){ fmt。Println(“goroutine 2 before”) rwMutex。RLock() fmt。Println(“goroutine 2 locked”) rwMutex。RUnlock() fmt。Println(“goroutine 2 unlock”) }() fmt。Println(“goroutine 1 locking 1”) time。Sleep(time。Second * 1) fmt。Println(“goroutine 1 locking 2”) rwMutex。RUnlock() fmt。Println(“goroutine 1 unlock”) time。Sleep(time。Second * 1)}/**goroutine 1 beforegoroutine 1 lockedgoroutine 1 locking 1goroutine 2 beforegoroutine 2 lockedgoroutine 2 unlockgoroutine 1 locking 2goroutine 1 unlock*/

3.sync.WaitGroup 等待一組goroutine完成工作

Add()設定等待多少個 goroutine

Done() goroutine完成工作呼叫 Done表示已經完成了

Wait()方法會一直堵塞,直到 所有的 goroutine 完成才會返回

package mainimport ( “fmt” “sync”)var wg sync。WaitGroupfunc main() { // 等待 4個 goroutine wg。Add(4) go func() { fmt。Printf(“goroutine 0\n”) for i := 1; i <= 3; i ++ { go func(index int) { fmt。Printf(“goroutine %d\n”, index) // goroutine完成時呼叫 Done wg。Done() }(i) } wg。Done() }() // Wait會一直堵塞,直到 所有的 goroutine 完成才會返回 wg。Wait()}/**goroutine 0goroutine 3goroutine 2goroutine 1 */

4.sync.Once 無論呼叫多少次Do方法,都只執行一次。

無論呼叫多少次Do方法,都只執行一次,並且所有 goroutine 會等待執行Do的goroutine完成後才會繼續執行,否則會一直堵塞等待。適用於單例、懶載入等

package mainimport ( “fmt” “sync” “time”)var wg sync。WaitGroupvar once sync。Oncefunc main() { wg。Add(4) go func() { fmt。Printf(“goroutine 0\n”) for i := 1; i <= 3; i ++ { go func(index int) { fmt。Printf(“goroutine %d\n”, index) // 只會執行一次 once。Do(func() { time。Sleep(time。Second * 2) fmt。Println(“print once”) }) // 所有 goroutine 會等待執行Do的goroutine完成後才會繼續執行,否則會一直等待 fmt。Printf(“goroutine %d after\n”, index) // goroutine完成時呼叫 Done wg。Done() }(i) } wg。Done() }() // Wait會一直堵塞,直到 所有的 goroutine 完成才會返回 wg。Wait()}/**goroutine 0goroutine 3goroutine 1goroutine 2print oncegoroutine 1 aftergoroutine 3 aftergoroutine 2 after */

5.atomic.Value 提供原子獲取值和儲存值

atomic。Value 提供原子獲取值和儲存值並且是執行緒安全無鎖實現的,比互斥鎖和讀寫鎖有更高的效能,適用於讀多寫少。適用於配置、熱點資料、運營類資料載入。

Store():儲存值Load():獲取值適用場景:1。 配置載入2。 熱點資料3。 運營類資料我們在服務內部開啟一個goroutine每隔10秒載入一次配置資料、熱點資料、運營類資料等,後續使用直接從記憶體裡面拿就好。

package mainimport ( “fmt” “sync” “sync/atomic”)var hotDataOrConfig atomic。Valuevar wg sync。WaitGroupfunc main() { wg。Add(1) go func() { defer wg。Done() for i := 0; i <= 20000;i++ { // 儲存資料(示例:配置資料 熱點資料 運營類資料) hotDataOrConfig。Store(i) } }() wg。Add(4) go func() { defer wg。Done() for i := 0; i < 3; i++ { go func(index int) { defer wg。Done() // 獲取資料(示例:配置資料 熱點資料 運營類資料) v := hotDataOrConfig。Load() fmt。Println(“goroutine ”, index, v) }(i) } }() wg。Wait()}