1. 程式人生 > >go併發程式設計基礎

go併發程式設計基礎

go被譽為網際網路的C語言,它的同步其實還是基於傳統語言,但是它的優勢在於語言層面實現了協程支援,goroutine和channel組成的CSP模型,大大減少了併發程式設計開發難度,簡單記下心得供有需要朋友參考。

go中開闢新執行緒使用go 關鍵字即可,同步方法分為傳統方法和基於通道(channel)的方法,通道包括無緩衝和有緩衝通道,這些同步方法結合保證了go併發程式設計的靈活性。

1.傳統方法

傳統方法包括有鎖程式設計和無鎖程式設計,有鎖指使用鎖來保證程式碼同步,無鎖程式設計使用原子程式設計來保證程式碼同步。

如下,開闢兩個go執行緒,同時做加操作,因為涉及到競態條件,對於全域性變數count=0結果為1.

func test_count1(name string){
	defer g.Done()

	fmt.Println(name, " run...")

	v := count
	runtime.Gosched()

	v++
	count=v
	fmt.Println(name, "done... count=", v)
}

為了解決這個問題,就必須保證count增加過程中的不可被分割,使用加鎖程式設計如下:

//鎖同步
func test_count2(name string){
	defer g.Done()

	fmt.Println(name, " run...")

	m.Lock()
	v := count
	runtime.Gosched()

	v++
	count=v
	m.Unlock()

	fmt.Println(name, "done... count=", v)
}

也可以使用原子程式設計如下:

//原子同步
func test_count3(name string){
	defer g.Done()

	fmt.Println(name, " run...")

	atomic.AddInt32(&count, 1)
	runtime.Gosched()
}

2.無緩衝通道

無緩衝通道保證資料傳送和接收是同時的,適合用於多個執行緒併發執行且相互之間有一定互斥關係的,每個時刻只能有一個執行緒持有通道資料。

比如如下,兩個選手相互擊球,通道是球,每個時刻一定只有一個選手能持有球,通過將資料傳給通道來模擬傳球過程,這也是channel的特質——將資料同步轉成goroutine間的資料傳遞

//無緩衝通道1-球賽對打
func Play(name string, ball chan int){
	defer w.Done()

	for {
		v, ok := <-ball
		if !ok {
			fmt.Println(name, " win the game.")
			break
		}

		n := rand.Intn(100)
		if n%13 == 0 {
			fmt.Println(name, " lose the game.")
			close(ball)
			break
		}

		v++;
		fmt.Println(name, " kick the ball.")
		ball <- v
	}
}

func testPlay(){
	w.Add(2)

	ball := make(chan int)
	go Play("wenzhou", ball)
	go Play("wenwei", ball)
	ball <- 1

	w.Wait()
}

還可以比喻成接力賽,每次只有一個選手能夠獲取接力球,如下,通過傳遞資料給通道來模擬選手接力的過程

//無緩衝通道2-接力賽
func Run(ball chan int){
	v, ok := <-ball
	if !ok{
		return
	}

	fmt.Printf("Runner %d start to run\n", v)

	if v!=4 {
		v++
		fmt.Printf("Runner %d ready to run\n", v)
		go Run(ball)
	}

	//Running
	fmt.Println("...")
	time.Sleep(time.Duration(1)*time.Second)

	if v==4 {
		fmt.Printf("Runner %d Finished, race over\n", v)
		w.Done()
		return
	}

	fmt.Printf("Runner %d give ball to Runner %d\n", v-1, v)
	ball <- v
}

func testRun(){
	w.Add(1)

	ball := make(chan int)
	go Run(ball)
	ball <- 1

	w.Wait()
}

3.有緩衝通道

有緩衝通道的可以起到快取和佇列的作用,如下採用通道來完成任務排隊的功能,很輕易的實現多路處理器的功能。

//帶緩衝通道
func Worker(task chan string, worker int){
	defer w.Done()

	for  {
		t, ok := <-task
		if !ok {
			fmt.Printf("Worker %d Shutdwon\n", worker)
			break
		}

		fmt.Printf("Worker %d - Started:%s...\n", worker, t)
		time.Sleep(time.Duration(rand.Intn(100))*time.Microsecond)

		fmt.Printf("Worker %d - Completed:%s...\n", worker, t)
	}
}

func testWorker(){
	//緩衝通道
	tasks := make(chan string, NUM_TASK)

	//建立Worker,等待處理任務
	w.Add(NUM_WORKER)
	for i:=1; i<=NUM_WORKER; i++  {
		go Worker(tasks, i)
	}

	//拋任務處理
	for t:=1; t<=NUM_TASK; t++  {
		tasks <- fmt.Sprintf("Task-%d", t)
	}

	//關閉通道,等待處理完畢,注意go機制保證關閉通道後剩餘任務可以被處理完畢
	close(tasks)
	w.Wait()
}

演示程式碼下載連結