使goroutine同步的方法總結

前言:

在前面併發性能對比的文章中,咱們能夠看到Golang處理大併發的能力十分強勁,並且開發也特別方便,只須要用go關鍵字便可開啓一個新的協程。數組

但當多個goroutine同時進行處理的時候,就會遇到同時搶佔一個資源的狀況(併發都會遇到的問題),因此咱們但願某個goroutine等待另外一個goroutine處理完某一個步驟以後才能繼續。sync包就是爲了讓goroutine同步而出現的。固然還可使用channel實現,這個後面會介紹到。多線程

鎖:

鎖有兩種:互斥鎖(mutex)和讀寫鎖(RWMutex)併發

互斥鎖: 當數據被加鎖了以後,除次外的其餘協程不能對數據進行讀操做和寫操做。 這個固然能解決併發程序對資源的操做。可是,效率上是個問題,由於當加鎖後,其餘協程只有等到解鎖後才能對數據進行讀寫操做。性能

讀寫鎖: 讀數據的時候上讀鎖,寫數據的時候上寫鎖。有寫鎖的時候,數據不可讀不可寫。有讀鎖的時候,數據可讀,不可寫。線程

兩種鎖的使用方式相同,這裏就只列出互斥鎖的代碼:協程

package main

import (
  "sync"
  "time"
  "fmt"
)

var num = 0

func main ()  {
  mu := &sync.Mutex{}
  for i:=0;i<10000;i++ {
    go func(){
      mu.Lock()
      defer mu.Unlock()
      num += 1
    }()
  }
  time.Sleep(time.Second)
  fmt.Println("num:", num)  // 若是不加鎖這裏的num的值會是一個隨機數而不是10000
}

 

Once:

有的時候,咱們啓動多個相同goroutine,可是裏面的某個操做我只但願被執行一次,這個時候Once就上場了。blog

package main


import (
  "fmt"
  "sync"
  "time"
)

func main() {
  var once sync.Once
  one := func() {
	fmt.Println("just once")
  }

  for i := 0; i < 10; i++ {
	go func(a int) {
	  once.Do(one)   // 只是被執行一次
	}(i)
  }
  time.Sleep(time.Millisecond*200)
}

 

WaitGroup:

當某個操做或是某個goroutine須要等待一批goroutine執行完畢之後才繼續執行,那麼這種多線程(go裏面說的線程就是goroutine)等待的問題就可使用WaitGroup了。資源

代碼以下:開發

package main

import (
	"sync"
	"fmt"
	"time"
)

var waitGroup sync.WaitGroup

func main () {
	for i := 0; i < 10; i++ {
		waitGroup.Add(1)  // 添加須要等待goroutine的數量
		go func() {
			fmt.Println("hehe")
			time.Sleep(time.Second)
			waitGroup.Done() // 減小須要等待goroutine的數量 至關於Add(-1)
		} ()
	}

	waitGroup.Wait()  // 執行阻塞,直到全部的須要等待的goroutine數量變成0
	fmt.Println("over")
}

 

Cond:

sync.Cond是用來控制某個條件下,goroutine進入等待時期,等待信號到來,而後從新啓動。get

代碼以下:

package main

import (
	"fmt"
	"sync"
	"time"
)
var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)

func test(x int) {
	cond.L.Lock() //獲取鎖
	cond.Wait()//等待通知 暫時阻塞
	fmt.Println(x)
	time.Sleep(time.Second * 1)
	cond.L.Unlock()//釋放鎖
}
func main() {
	for i := 0; i < 40; i++ {
		go test(i)
	}
	fmt.Println("start all")
	time.Sleep(time.Second * 3)
	fmt.Println("signal1")
	cond.Signal()   // 下發一個通知隨機給已經獲取鎖的goroutine
	time.Sleep(time.Second * 3)
	fmt.Println("signal2")
	cond.Signal()// 下發第二個通知隨機給已經獲取鎖的goroutine
	time.Sleep(time.Second * 1)  // 在廣播以前要等一會,讓全部線程都在wait狀態
	fmt.Println("broadcast")
	cond.Broadcast()//下發廣播給全部等待的goroutine
	time.Sleep(time.Second * 60)
}

上面代碼有幾個要點要特別說明一下:

1. 每一個Cond都必須有個與之關聯的鎖  // 見第9行

2. 協程方法裏面一開始/結束都必須加/解鎖 // 見第12行和16行

3. cond.Wait()時會自動解鎖,當被喚醒時,又會加上鎖。因此第2點提到必須加/解鎖。

 

Channel

channel不只能夠用來goroutine之間的通訊,也可使goroutine同步完成協做。這點主要基於從channel取數據的時候,會阻塞當前goroutine這個特性。示例代碼以下:

package main

import (
	"fmt"
	"time"
)


var chan1 = make(chan string, 512)

var arr1 = []string{"qq","ww","ee","rr","tt"}

func chanTest1() {
	for _, v := range arr1 {
		chan1 <- v
	}
	close(chan1) // 關閉channel
}

func chanTest2() {
	for {
		getStr, ok := <- chan1  // 阻塞,直到chan1裏面有數據
		if !ok {   // 判斷channel是否關閉或者爲空
			return
		}
		fmt.Println(getStr) // 按數組順序內容輸出
	}
}

func main () {
	go chanTest1()
	go chanTest2()

	time.Sleep(time.Millisecond*200)
}
相關文章
相關標籤/搜索