iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
自我挑戰組

Concurrency in go 讀書心得系列 第 9

9.Select, GOMAXPROC

  • 分享至 

  • xImage
  •  

select

select 在 Go 語言中用於處理多個通道操作,它允許程序等待多個通道操作,然後執行第一個可以完成的操作。如果有多個操作都可以執行,則會隨機選擇一個操作執行。如果 select 裡沒有預設情況(default case)且所有通道操作都不可執行,它將會阻塞,直到至少有一個通道操作可以執行。基本上,select 可以看作是專為通道操作而設計 switch 語句。

第一個例子,主要的點是這裡的 close(c) 。上一篇channel的文章有提到當你關閉一個通道,就是通知接收者這個通道上不會再有更多的值發送。對於已經關閉的通道,嘗試讀取(接收操作)不會阻塞,而會立即返回該類型的零值。
所以,當 close(c) 在另一個 goroutine 被調用後,select 語句中的 case <-c: 會被立即執行,因為這個接收操作不會再阻塞。

func main() {
	start := time.Now()
	c := make(chan interface{})
	go func() {
		time.Sleep(5 * time.Second)
		close(c) // <1>
	}()

	fmt.Println("Blocking on read...")
	select {
	case <-c: // <2>
		fmt.Printf("Unblocked %v later.\n", time.Since(start))
	}
}
輸出:
Blocking on read...
Unblocked 5.001127708s later.


多個通道讀取時的情況

從下面的例子可以看到,大約有一半的時間從 c1讀取,有一半是從c2讀取的。事實上Go Runtime會對一組case語句執行偽隨機統一選擇。這意味著在同樣的條件下,每個case被選中的機會幾乎是一樣的。
Go Runtime 所能做的最好的事情就是在任何情況下運行良好。一個好的方法是在你的程序中中引入一個隨機變量——以決定選擇哪個case執行。通過加權平均使用每個通道的機會,使得所有使用select語句的Go程式表現良好。

func main() {
	start := time.Now()
	var c1, c2 <-chan int
	select {
	case <-c1:
	case <-c2:
	default:
		fmt.Printf("In default after %v\n\n", time.Since(start))
	}
}

default的運用

select 語句中允許我們添加 default 條件,以便你在所有分支都不符合執行條件的時候執行。

func main() {
	start := time.Now()
	var c1, c2 <-chan int
	select {
	case <-c1:
	case <-c2:
	default:
		fmt.Printf("In default after %v\n\n", time.Since(start))
	}
}
輸出:In default after 125ns

你可以看到它幾乎是瞬間運行default,這允許你在不阻塞的情況下退出select。通常你會看到for- select循環結合使用,使得goroutine可以在等待另一個goroutine報告結果的同時取得進展。
例子如下:

func main() {
	// 1. 創建了一個新的通道 `done`,它將用於通知主 goroutine 何時停止工作。
	done := make(chan interface{})

	// 2. 啟動了一個新的 goroutine,該 goroutine 將在等待 5 秒後關閉 `done` 通道,從而向主 goroutine 發出停止工作的信號。
	go func() {
		time.Sleep(5 * time.Second)
		close(done)
	}()

	// 3. 這是一個計數器,用於跟踪主 goroutine 完成了多少工作循環。
	workCounter := 0

	// 4. 這是一個標籤,之後可以通過 `break loop` 來跳出 `for` 循環。
loop:
	// 5. 這是一個無限循環,模擬主 goroutine 的工作循環。
	for {
		select {
		// 6. 這是一個 `select` 語句,它嘗試從 `done` 通道接收消息。
		case <-done:
			break loop
		default:
			// 如果通道仍然打開並且沒有消息可以接收,那麼 `default` 分支將被執行,從而繼續工作循環。
		}

		// 7. 這部分模擬了一些工作,即增加 `workCounter` 的值,並讓 goroutine 休眠 1 秒。
		workCounter++
		time.Sleep(1 * time.Second)
	}

	// 8. 當從 `done` 通道接收到消息並跳出 `for` 循環後,這行代碼將輸出主 goroutine 完成的工作循環次數。
	fmt.Printf("Achieved %v cycles of work before signalled to stop.\n", workCounter)
}

輸出:Achieved 5 cycles of work before signalled to stop.

所以,當運行這個程式時,會看到輸出 "Achieved 5 cycles of work before signalled to stop.",因為主 goroutine 將工作 5 秒(每次循環休眠 1 秒)直到另一個 goroutine 關閉了 done 通道。


上一篇
8.Channels
下一篇
10.Confinement pattern
系列文
Concurrency in go 讀書心得30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言