遞歸與Stream流轉換 今天寫一個很久以前一直不太會的,今天花了大量的時間進行研究處理,現將代碼解析於此 list轉為類中一個屬性為key,類實例為value的Map Map<String, List<OrgTreeVo>> orgMap = orgList.stream().filter(h - ...
今天這篇筆記我們來記錄Channel 和 Select, Go語言併發中Channel是goroutine傳遞數據的橋梁,是非常重要的一個工具。
定義Channel
雙向Channel
要定義一個channel很簡單,只需要在類型前面加上chan就可以了,
stringStream := make(chan string)
這樣就是定義和實例化了一個string 類型的雙向channel,
先來看一個Hello World的例子
func main() {
stringStream := make(chan string)
go func() {
stringStream <- "Hello channels"
}()
fmt.Println(<-stringStream)
運行代碼控制台列印出“Hello channels”, 這個簡單的例子中我們定義了一個string類型的channel, 啟動一個goroutine, 往這個channel中寫入“Hello channels”, 主的goroutinue會讀取這個channel裡面的value, 讀取是阻塞的,如果我們把寫的代碼註釋掉“stringStream <- "Hello channels"”,程式運行會報死鎖,因為沒有誰會寫入了,它一直等待。
單向Channel
我們也可以聲明單向的channel,也就是只讀或者只寫的channel.
var receiveChan <-chan string //一個只讀的channel
var sendChan chan<- string //一個只寫的channel,
既然是只讀,那麼誰來給它寫入呢, 這裡其實還是需要一個雙向的channel,然後把雙向的channel賦值給單向channel,如
stringStream := make(chan string)
receiveChan = stringStream
sendChan = stringStream
只讀和只寫channel有什麼作用呢? 他們主要是用在方法的參數或者返回中,用戶看到這個chan是只讀的或者只寫的就明確了它的使用方法。 對於只讀的,我們實際上用個雙向的channel,然後寫入雙向channel後, 把雙向channel賦值給只讀的channel. 如下示例代碼
func main() {
stringStream := make(chan string)
go send(stringStream, "passed message")
receive(stringStream)
}
func send(pings chan<- string, msg string) {
fmt.Println("ping " + msg)
pings <- msg
}
func receive(receiver <-chan string) {
fmt.Println(<-receiver)
}
我們在send方法中知道pings是只寫的,不會讀取它
在receive方法中知道receiver是只讀的, 不會寫它
讀取和寫入Channel
上面例子我們一件看到讀就通過value := <-channel, 把channel中的數據讀出來, 寫就通過 channel<- value, 箭頭方向也比較明確,比較好理解。這裡再給個通過range讀取channel的方法
先看示例代碼
intStream := make(chan int)
go func() {
defer close(intStream)
for i := 1; i <= 5; i++ {
intStream <- i
fmt.Printf("writer %d \n", i)
}
}()
for integer := range intStream {
fmt.Printf("receive %v \n", integer)
}
fmt.Println(<-intStream)
fmt.Println(<-intStream)
我們寫入了五個value到intStream裡面, 讀取的時候通過range我就不用知道這個次數了,通過for range 就都拿到了。 上面程式輸出結果如下:
writer 1
receive 1
receive 2
writer 2
writer 3
receive 3
receive 4
writer 4
writer 5
receive 5
0
0
結果比較有意思, receive 2 跑到writer3的前面去了, 我猜測是這個channel是阻塞的,寫入的時候,必須讀了才能再寫,讀到1以後,2就可以寫了,還沒有來得及列印writer, read就拿到了。所以感覺上receive跑到writer前面去了。
最後兩個00是我故意列印出來的,從關閉的channel也能拿到有返回的數據,如果想確定數據是不是正常寫入的,可以加上 value,ok := <- intStream, 判斷 ok 是true和false判斷是否是正常寫入的。
緩衝Channel
我們前面看到的例子,寫入數據到channel後,必須等別的goroutine讀到後才可以繼續寫,那麼如果我想寫入後繼續去乾別的,就需要用到緩衝Channel, 也就可以多寫幾個到channel。 如下示例代碼
intStream := make(chan int, 2)
go func() {
defer close(intStream)
defer fmt.Println("Producer Done")
for i := 0; i < 5; i++ {
intStream <- i
fmt.Printf("Sending: %d \n", i)
}
}()
time.Sleep(10 * time.Second)
for i := 0; i < 5; i++ {
v := <-intStream
fmt.Printf("Received: %d \n", v)
time.Sleep(1 * time.Second)
}
定義一個緩衝區為2的channel, 當寫入兩個後會被阻塞。 輸出結果如下
Sending: 0
Sending: 1
Received: 0
Sending: 2
Received: 1
Sending: 3
Received: 2
Sending: 4
Producer Done
Received: 3
Received: 4
可以看到當發送了兩個後,發送就阻塞起來了,直到讀取了之後,才可以繼續發送。
這裡有個疑問點作者說 make(chan int) 和 make(chan int, 0) 是等效的,我實際驗證效果也確實是一樣的,但是我想不應該是make(chan int, 1) 嗎?但是實際效果make(chan int, 1) 和make(chan int, 0) 確實不一樣。 我實驗了下,make(chan int, 1) 寫第二個的時候被阻塞,用
make(chan int, 0),寫一開始就會阻塞,直到開始讀了,寫才會成功。當然不是先寫後讀,只是一種相互的阻塞狀態。
Select
作者在書中寫道:“Select是一個具有併發性的Go語言最重要的事情之一, 在一個系統中兩個或者多個組件的交集中,可以在本地、單個函數、或者類型以及全局範圍內查找select語句綁定在一起的channel。除了連接組件之外,在程式的某些關鍵節點上, select 語句可以幫助安全地將channel與諸如取消、超時、等待、預設值之類的概率結合起來”。
單一channel select
先來看一個簡單的例子
start := time.Now()
c := make(chan interface{})
go func() {
time.Sleep(5 * time.Second)
close(c)
}()
fmt.Print("Blocking on read ... \n")
select {
case <-c:
fmt.Printf("Unblocked %v later. \n", time.Since(start))
}
程式輸出結果如下, 等待5S後,關閉了channel, 阻塞結束。
Blocking on read ...
Unblocked 5.0101794s later.
上面是一個單一channel select的例子, 它等效於下麵的語句
if c == nil {
block()
}
<- c
多個channel
接著我們看一個多個channel可用的例子, 我自己稍微改裝了一下上面的例子
start := time.Now()
c := make(chan interface{})
c2 := make(chan int)
go func() {
time.Sleep(5 * time.Second)
for i := 0; i < 3; i++ {
c2 <- i
}
close(c)
}()
fmt.Print("Blocking on read ... \n")
loop:
for {
select {
case <-c:
fmt.Printf("Unblocked %v later. \n", time.Since(start))
break loop
case data := <-c2:
fmt.Printf("C2 received %d, %v later. \n", data, time.Since(start))
}
}
這裡有兩個case, 一個收到後會退出迴圈,一個會讀取channel裡面的數據,程式運行結果如下所示
Blocking on read ...
C2 received 0, 5.0148217s later.
C2 received 1, 5.0155568s later.
C2 received 2, 5.0161642s later.
Unblocked 5.0167839s later.
我們寫入的3個數據都被讀取到了,並且關閉channel後退出了迴圈。
書中還列舉了一個當多個channel都可用的時候,Go 語言執行偽隨機選擇,
c1 := make(chan interface{})
close(c1)
c2 := make(chan interface{})
close(c2)
var c1Count, c2Count int
for i := 1000; i >= 0; i-- {
select {
case <-c1:
c1Count++
case <-c2:
c2Count++
}
}
fmt.Printf("c1Count:%d \nc2Count: %d \n", c1Count, c2Count)
程式運行結果如下
c1Count:483
c2Count: 518
運行1000次,兩個case比較平均的執行
超時
我們來看一個超時的例子
start := time.Now()
c1 := make(chan interface{})
select {
case <-c1:
fmt.Println("received c1.")
case <-time.After(2 * time.Second):
fmt.Printf("Timed out. after %v later. \n", time.Since(start))
}
程式輸出
Timed out. after 2.0099758s later.
沒有程式寫入c1, 所以在等待2S後,執行了time out.
default
來看default的例子
start := time.Now()
var c1, c2 <-chan interface{}
select {
case <-c1:
fmt.Println("received c1.")
case <-c2:
fmt.Println("received c2.")
default:
fmt.Printf("default after %v later. \n", time.Since(start))
}
程式幾乎立刻執行了default, 輸出如下
default after 0s later.