1. 程式人生 > >go——通道(二)

go——通道(二)

在Go語言裡面,你不僅可以使用原子函式和互斥鎖來保證對共享資源的安全訪問以消除競爭狀態,

還可以使用通道,通過傳送和接收需要共享的資源,在goroutine之間做同步。

 

當一個資源需要在goroutine之間共享時,通道在goroutine之間架起了一個管道,並提供了確保同步交換資料的機制。

宣告通道時,需要指定將要被共享的資料型別。可以通過通道共享內建型別、命名型別、結構型別和引用型別的值或者指標。

在Go語言中需要使用內建函式make來建立一個通道。

//使用make建立通道

//無緩衝的整型通道
unbuffered := make(chan int)


//有緩衝的字串通道
buffered := make(chan string, 10)

向通道傳送值或者指標需要用到<-操作符。

//向通道傳送值

//有緩衝的字串通道
buffered := make(chan string, 10)

//通過通道傳送一個字串
buffered <- "gopher"

我們建立了一個有緩衝的通道,資料型別是字串,包含一個10個值的緩衝區。

之後,我們通過通道傳送字串”gopher“。為了讓另一個goroutine可以從通道里接收到這個字串,我們依舊使用<-操作符,但這次是一元操作符。

//從通道里接收一個字串
value := <-buffered

  

  (1)無緩衝的通道

無緩衝通道是指在接收前沒有能力儲存任何值得通道。

這種型別的通道要求傳送goroutine和接收goroutine同時準備好,才能完成傳送和接收任務。

如果兩個giroutine沒有同時準備好,通道會導致先執行傳送或接收操作的goroutine阻塞等待。

這種對通道進行傳送和接收的互動行為本身就是同步的。其中任意一個操作都無法離開另一個操作單獨存在。

下面說明一下無緩衝通道是如何來共享資料的:

兩個goroutine(假設為A和B)都到達通道,但哪個都沒有開始執行傳送或者接收。

假設goroutine A向通道傳送了資料,goroutine A會在通道中被鎖住,直到交換完成。

goroutine B會從通道里接收資料,goroutine B一樣會在通道中被鎖住,直到交換完成。

隨後會完成資料交換,隨後會釋放兩個goroutine。

示例1:

//如何用無緩衝的通道來模擬2個goroutine間的網球比賽
package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

//wg用來等待程式結束
var wg sync.WaitGroup

func init() {
	rand.Seed(time.Now().UnixNano())
}

func main() {
	//建立一個無緩衝的通道
	court := make(chan int)

	wg.Add(2)

	//啟動兩個選手
	go player("kebi", court)
	go player("maoxian", court)

	//發球,向通道中傳送資料
	court <- 1

	//等待遊戲結束
	wg.Wait()

}

//player模擬一個選手在打網球
func player(name string, court chan int) {
	defer wg.Done()

	for {
		//等待資料發過來
		ball, ok := <-court
		if !ok {
			//檢測通道是否為false,如果是false表示通道已經關閉
			//如果通道被關閉了,就知道
			fmt.Printf("Player %s Won\n", name)
			return
		}

		//選個隨機數,然後用這個數來判斷我們是否丟球
		n := rand.Intn(100)
		if n%13 == 0 {
			fmt.Printf("Player %s Missed\n", name)
			
			//關閉通道表示已經輸了
			close(court)
			return
		}
		
		//顯式擊球數,並加一
		fmt.Printf("Player %s Hit %d\n", name, ball)
		ball++

		court <- ball
	}
}

/*
Player maoxian Hit 1
Player kebi Hit 2
Player maoxian Hit 3
Player kebi Hit 4
Player maoxian Missed
Player kebi Won
*/

示例2:

//如何用無緩衝的通道來模擬4個goroutine間的接力比賽
package main

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

var wg sync.WaitGroup

func main() {
	baton := make(chan int)

	//為最後一位跑步者將計數加一
	wg.Add(1)

	//第一位跑步者持有接力棒
	go Runner(baton)

	開始比賽
	baton <- 1

	wg.Wait()
}

func Runner(baton chan int) {
	var newRunner int
	
	//等待接力棒
	runner := <-baton
	
	//開始跑步
	fmt.Printf("runner %d running With Baton\n", runner)

	//建立下一位跑步者
	if runner != 4 {
		newRunner = runner + 1
		fmt.Printf("runner %d To the Line\n", newRunner)
		go Runner(baton)
	}

	//圍繞跑道跑
	time.Sleep(100 * time.Millisecond)

	//比賽結束了嗎?
	if runner == 4 {
		fmt.Printf("runner %d Finished,Race Over\n", runner)
		wg.Done()
		return
	}
	
	//將接力棒交給下一位跑步者
	fmt.Printf("Runner %d Exchange With Runner %d\n", runner, newRunner)

	baton <- newRunner
}


/*
runner 1 running With Baton
runner 2 To the Line
Runner 1 Exchange With Runner 2
runner 2 running With Baton
runner 3 To the Line
Runner 2 Exchange With Runner 3
runner 3 running With Baton
runner 4 To the Line
Runner 3 Exchange With Runner 4
runner 4 running With Baton
runner 4 Finished,Race Over
*/

  

(2)有緩衝的通道

有緩衝的通道是一種在被接收前能儲存一個或者多個值的通道,這種型別的通道並不強制要求goroutine之間必須同時完成傳送和接收。

通道會阻塞傳送和接收動作的條件也會不同。只有在通道中沒有要接收的值時,接收動作才會阻塞。

只有在通道沒有可用緩衝區容納被髮送的值時,傳送動作才會被阻塞。

這導致有緩衝的通道和無緩衝的通道之間的一個很大的不同:

無緩衝通道保證進行傳送和接收的goroutine會在同一時間進行資料交換,有緩衝的通道沒有這種保證。

//展示如何使用有緩衝的通道和固定數目的goroutine來處理一堆工作
package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

const (
	numberGoroutines = 4  //使用的goroutine的數量
	taskLoad         = 10  //要處理的工作的數量
)

var wg sync.WaitGroup

func init() {
	rand.Seed(time.Now().Unix())
}

func main() {
	tasks := make(chan string, taskLoad)

	//啟動goroutine來處理工作
	wg.Add(numberGoroutines)
	for gr := 1; gr <= numberGoroutines; gr++ {
		go worker(tasks, gr)
	}

	//增加一組要完成的工作
	for post := 1; post <= taskLoad; post++ {
		tasks <- fmt.Sprintf("Task : %d", post)
	}

	//當所有工作處理完畢時關閉通道
	close(tasks)

	wg.Wait()
}

func worker(tasks chan string, worker int) {
	defer wg.Done()

	for {
		//等待分配工作
		task, ok := <-tasks
		if !ok {
			//通道已空且被關閉
			fmt.Printf("Worker: %d : Shutting Down\n", worker)
			return
		}

		//開始工作
		fmt.Printf("Worker: %d : Started %s\n", worker, task)

		//隨機等待一段時間來模擬工作
		sleep := rand.Int63n(100)
		time.Sleep(time.Duration(sleep) * time.Millisecond)

		//顯式完成了工作
		fmt.Printf("Worker: %d : Completed %s\n", worker, task)
	}
}

/*
Worker: 4 : Started Task : 4
Worker: 2 : Started Task : 1
Worker: 1 : Started Task : 3
Worker: 3 : Started Task : 2
Worker: 3 : Completed Task : 2
Worker: 3 : Started Task : 5
Worker: 1 : Completed Task : 3
Worker: 1 : Started Task : 6
Worker: 4 : Completed Task : 4
Worker: 4 : Started Task : 7
Worker: 2 : Completed Task : 1
Worker: 2 : Started Task : 8
Worker: 4 : Completed Task : 7
Worker: 4 : Started Task : 9
Worker: 2 : Completed Task : 8
Worker: 2 : Started Task : 10
Worker: 3 : Completed Task : 5
Worker: 3 : Shutting Down
Worker: 1 : Completed Task : 6
Worker: 1 : Shutting Down
Worker: 2 : Completed Task : 10
Worker: 2 : Shutting Down
Worker: 4 : Completed Task : 9
Worker: 4 : Shutting Down
*/