Go 資源競爭的解決方案 (1. 原子函數 / 互斥鎖 2. 通道)

資源競爭

若是多個goroutine在沒有互相同步的狀況,訪問某個共享的資源, 並試圖同時讀和寫這個資源,就處於相互競爭的狀態, 這種狀況被稱做競爭狀態(race candition)。 競爭狀態的存在是讓併發程序變得複雜的地方,十分容易引發潛在問題。對一個共享資源的讀和寫操做必 須是原子化的。(即同一時刻只能有一個 goroutine 對共享資源進行讀和寫操做)安全

代碼解析bash

// 這個示例程序展現如何在程序裏形成競爭狀態
// 實際上不但願出現這種狀況
package main

import (
	"fmt"
	"runtime"
	"sync"
)
var (
	counter int             // counter 是全部 goroutine 都要增長其值的變量
	wg sync.WaitGroup       // wg 用來等待程序結束
)

// main 是全部 Go 程序的入口
func main() {
	// 計數加 ,表示要等待兩個 goroutine
	wg.Add()
	// 建立兩個 goroutine
	go incCounter()
	go incCounter()
	// 等待 goroutine 結束
	wg.Wait()
	fmt.Println("Final Counter:", counter)
}

// incCounter 增長包裏 counter 變量的值
func incCounter(id int) {
	// 在函數退出時調用 Done 來通知 main 函數工做已經完成
	defer wg.Done()

	for count :=; count <; count++ {
		// 捕獲 counter 的值
		value := counter

		// 當前 goroutine 從線程退出,並放回到隊列
		runtime.Gosched()

		// 增長本地 value 變量的值
		value++

		// 將該值保存回 counter
		counter = value
	}
}

/*
    在這段代碼中
    變量counter會進行4次讀和寫操做,每一個goroutine執行兩次。
    可是,程序終止時,counter變量的值爲 2。
    下圖 提供了爲何會這樣的線索。
    每一個 goroutine 都會覆蓋另外一個 goroutine 的工做。
    這種覆蓋發生在 goroutine 切換的時候。
    每一個 goroutine 創造了一個 counter 變量的副本,
    以後就切換到另外一個 goroutine。當這個 goroutine再次運行的時候,
    counter 變量的值已經改變了,可是 goroutine 並無更新本身的那個副本的值,
    而是繼續使用這個副本的值,用這個值遞增,並存回 counter 變量
    結果覆蓋了另外一個goroutine 完成的工做。
*/
複製代碼

Go 語言中經過三種方式 處理競爭狀態的狀況markdown

鎖住共享資源併發

1. 原子函數

使用atmoic 包內的相關函數執行原子操做 對數據進行安全的讀和寫 
保證數據不會出現覆的狀況 
atomic.LoadInt64(&shutdown)        // 同步獲取該數據的值 
atomic.StoreInt64(&shutdown, 1);   // 同步寫入該數據的值
複製代碼

2. 互斥鎖

/*
    經過 mutex.Lock() mutex.Unlock() 劃分臨界區
    被包裹在臨界區內的代碼 同一個時間點只能被一個goroutine 執行
*/

// incCounter 使用互斥鎖來同步並保證安全訪問,
// 增長包裏 counter 變量的值

func incCounter(id int) {
	// 在函數退出時調用 Done 來通知 main 函數工做已經完成
	defer wg.Done()
	for count := 0; count < 2; count++ {
		// 同一時刻只容許一個 goroutine 進入
		// 這個臨界區
		mutex.Lock()     // <<<<<<<<<<<<<<<<<<<<< 互斥鎖開始語句
		{
			// 捕獲 counter 的值
			value := counter
			// 當前 goroutine 從線程退出,並放回到隊列
			runtime.Gosched()
			// 增長本地 value 變量的值
			value++
			// 將該值保存回 counter
			counter = value
		}
		mutex.Unlock()   // <<<<<<<<<<<<<<<<<<<<< 互斥鎖結束語句
		// 釋放鎖,容許其餘正在等待的 goroutine
		// 進入臨界區
	}
}

複製代碼

3. 通道

經過使用通道發送和接收須要共享的資源,能夠在goroutine之間作數據同步。
當一個資源須要在 goroutine 之間共享時,通道在 goroutine 之間架起了一個管道,
並提供了確保同步交換數據的機制。聲明通道時,須要指定將要被共享的數據的類型。
能夠經過通道共享內置類型、命名類型、結構類型和引用類型的值或者指針。
複製代碼
// 無緩衝的整型通道
unbuffered := make(chan int)
// 有緩衝的字符串通道
buffered := make(chan string, 10)


// 經過通道發送一個字符串
buffered <- "Gopher"
// 從通道接收一個字符串
value := <-buffered
複製代碼

對於有緩衝和無緩衝的通道的區別能夠看下面的這張圖 詳細地解釋其中的差別性函數

無緩衝 的狀況下 須要接送方與發送方 同時鏈接上. 若一方不存在 則會處於阻塞狀態 atom

有緩衝 的狀況下 不須要雙方同時鏈接上 只要有讓發送者有空間能夠存放 接收者有數據能夠取出 就不會出現阻塞 spa

相關文章
相關標籤/搜索