golang併發基礎

1. go協程(go routine)git

go原生支持併發:goroutine和channel。golang

go協程是與其餘函數或方法一塊兒併發運行的函數和方法。go協程能夠看做是輕量級線程。數據庫

調用函數或者方法時,在前面加上關鍵字go,可讓一個新的GO協程併發地運行。服務器

  • l  啓動一個新的協程時,協程的調用會當即返回。與函數不一樣,程序控制不會去等待 Go 協程執行完畢。在調用 Go 協程以後,程序控制會當即返回到代碼的下一行,忽略該協程的任何返回值。
  • l  若是但願運行其餘 Go 協程,Go 主協程必須繼續運行着。若是 Go 主協程終止,則程序終止,因而其餘 Go 協程也不會繼續運行。

2. 信道channel併發

信道能夠想象成Go協程之間通訊的管道。dom

chan T 表示 T 類型的信道。信道與類型相關,只能運輸該類型的數據。函數

信道的零值爲 nil。信道的零值沒有什麼用,應該像對 map 和切片所作的那樣,用 make 來定義信道。google

data := <- a // 讀取信道 a 
a <- data // 寫入信道 a

信道發送與接收默認是阻塞的。url

信道會產生死鎖。當Go協程給一個信道發送數據,而沒有信道接收數據時,程序觸犯panic,造成死鎖。spa

單向信道

sendch := make(chan<- int)    // 定義單向信道,定義只寫數據的信道,<-指向chan

能夠把一個雙向信道轉換成惟送信道或者惟收信道(send only or receive only),但反過來不能夠。

關閉信道

數據發送方能夠關閉信道,通知接收方這個信道再也不有數據發送過來。

當從信道接收數據時,接收方能夠多用一個變量來檢查信道是否已經關閉。

v, ok := <- ch

若是能夠信道能夠接收數據,ok等於true;若是信道關閉,ok等於false。

從一個關閉的信道中讀取到的值時該信道類型的零值。

range遍歷信道

for range 循環用於在一個信道關閉以前,從信道接收數據。

ch := make(chan int) go producer(ch) for v := range ch { fmt.Println("Received ",v) }

3. 緩衝信道

buffered Channel只有緩衝已滿的狀況纔會阻塞發送數據,一樣只有緩衝爲空時才阻塞接收數據。

ch := make(chan type, capacity)   // capacity大於0

緩衝信道的容量是指信道能夠存儲的值的數量。緩衝信道的長度是指信道中當前排隊的元素個數。

4.工做池

WaitGroup 用於等待一批 Go 協程執行結束。程序控制會一直阻塞,直到這些協程所有執行完畢。

定義:var wg sync.WaitGroup

調用:wg.Add(1)…wg.Done()…wg.Wait()

package main import ( "fmt"
    "sync"
    "time" ) func process(i int, wg *sync.WaitGroup) { fmt.Println("started Goroutine ", i) time.Sleep(2 * time.Second) fmt.Printf("Goroutine %d ended\n", i) wg.Done() } func main() { no := 3
    var wg sync.WaitGroup for i := 0; i < no; i++ { wg.Add(1) go process(i, &wg) } wg.Wait() fmt.Println("All go routines finished executing") } output: started Goroutine 2 started Goroutine 0 started Goroutine 1 Goroutine 1 ended Goroutine 2 ended Goroutine 0 ended All go routines finished executing

協程中傳參wg地址很是重要,wg.Done()執行完畢後主協程才知道。如果值拷貝,main函數不知道。

package main import ( "fmt"
        "math/rand"
        "sync"
        "time" ) type Job struct { id int randomno int } type Result struct{ job Job sumofdigits int } var jobs = make(chan Job, 10) var results = make(chan Result, 10) func sum_digits(number int)int { sum := 0
        for number !=0 { i:= number%10 sum += i number = number/10 } time.Sleep(2*time.Second) return sum } func worker(wg *sync.WaitGroup){ for job := range jobs{ output := Result{job, sum_digits(job.randomno)} results <- output } wg.Done() } func create_worker_pool(num_workers int){ var wg sync.WaitGroup for i:=0; i < num_workers; i++{ wg.Add(1) go worker(&wg) } wg.Wait() close(results) } func allocate(num_jobs int){ for i := 0; i < num_jobs; i++{ randomno := rand.Intn(999) job := Job{i, randomno} jobs <- job } close(jobs) } func result(done chan bool){ for result := range results{ fmt.Printf("Job id %d, input random no %d, sum of digits %d\n", result.job.id, result.job.randomno, result.sumofdigits) } done <- true } func main(){ startTime := time.Now() num_jobs := 100 go allocate(num_jobs) done := make(chan bool) go result(done) num_workers := 10 create_worker_pool(num_workers) <-done endTime := time.Now() diff := endTime.Sub(startTime) fmt.Println("total time taken ", diff.Seconds(), "seconds") }

5.select

select 語句用於在多個發送/接收信道操做中進行選擇。select 語句會一直阻塞,直到發送/接收操做準備就緒。若是有多個信道操做準備完畢,select 會隨機地選取其中之一執行。該語法與 switch 相似,所不一樣的是,這裏的每一個 case 語句都是信道操做。

在沒有 case 準備就緒時,能夠執行 select 語句中的默認狀況(Default Case)。這一般用於防止 select 語句一直阻塞。

package main import ( "fmt"
    "time" ) func server1(ch chan string) { time.Sleep(6 * time.Second) ch <- "from server1" } func server2(ch chan string) { time.Sleep(3 * time.Second) ch <- "from server2" } func main() { output1 := make(chan string) output2 := make(chan string) go server1(output1) go server2(output2) select { case s1 := <-output1: fmt.Println(s1) case s2 := <-output2: fmt.Println(s2) } }

select應用:假設咱們有一個關鍵性應用,須要儘快地把輸出返回給用戶。這個應用的數據庫複製而且存儲在世界各地的服務器上。咱們向兩臺服務器發送請求,並使用 select 語句等待相應的信道發出響應。select 會選擇首先響應的服務器,而忽略其它的響應。使用這種方法,咱們能夠向多個服務器發送請求,並給用戶返回最快的響應了。

package main func main() { select {} }

select 語句沒有任何 case,所以它會一直阻塞,致使死鎖。該程序會觸發 panic。

6.mutex

Mutex 用於提供一種加鎖機制(Locking Mechanism),可確保在某時刻只有一個協程在臨界區運行,以防止出現競態條件。

var mutex sync.Mutex mutex.Lock() x = x + 1 mutex.Unlock()

信道處理競態條件

ch <- true x = x + 1
    <- ch

當 Go 協程須要與其餘協程通訊時,可使用信道。而當只容許一個協程訪問臨界區時,可使用 Mutex。

7. once

sync.Once能夠控制函數只能被調用一次,不會被屢次重複調用。 

func (o *Once) Do(f func())
import ( "sync" ) type Watchers struct { devices map[string] string } var ( wcOnce sync.Once watchers *Watchers ) // Create a singleton WatcherCache instance
func newWatchers() *Watchers { wcOnce.Do(func() { watchers = &Watchers{} }) return watchers }

sync.Once.Do(f func())能保證once只執行一次,不管以後是否更換once.Do(xx)裏的方法。

package main import ( "fmt"
    "sync" ) func main(){ var once sync.Once once.Do(func(){ fmt.Println("Test sync Once") }) }

 

參考:

1.   https://studygolang.com/subject/2

相關文章
相關標籤/搜索