欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言中 Channel 詳解

 更新時間:2018年10月07日 10:37:04   作者:smallnest  
Go 語言中的 channel 是實現 goroutine 間無鎖通信的關鍵機制,他使得寫多線程并發(fā)程序變得簡單、靈活、觸手可得。下面就個人理解對 channel 使用過程中應該注意的地方進行一個簡要的總結。

Channel是Go中的一個核心類型,你可以把它看成一個管道,通過它并發(fā)核心單元就可以發(fā)送或者接收數據進行通訊(communication)。

它的操作符是箭頭 <- 。

ch <- v    // 發(fā)送值v到Channel ch中
v := <-ch  // 從Channel ch中接收數據,并將數據賦值給v
(箭頭的指向就是數據的流向)

就像 map 和 slice 數據類型一樣, channel必須先創(chuàng)建再使用:

ch := make(chan int)
Channel類型
Channel類型的定義格式如下:

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
它包括三種類型的定義??蛇x的<-代表channel的方向。如果沒有指定方向,那么Channel就是雙向的,既可以接收數據,也可以發(fā)送數據。

chan T          // 可以接收和發(fā)送類型為 T 的數據
chan<- float64  // 只可以用來發(fā)送 float64 類型的數據
<-chan int      // 只可以用來接收 int 類型的數據
<-總是優(yōu)先和最左邊的類型結合。(The <- operator associates with the leftmost chan possible)

chan<- chan int    // 等價 chan<- (chan int)
chan<- <-chan int  // 等價 chan<- (<-chan int)
<-chan <-chan int  // 等價 <-chan (<-chan int)
chan (<-chan int)

使用make初始化Channel,并且可以設置容量:

make(chan int, 100)

容量(capacity)代表Channel容納的最多的元素的數量,代表Channel的緩存的大小。
如果沒有設置容量,或者容量設置為0, 說明Channel沒有緩存,只有sender和receiver都準備好了后它們的通訊(communication)才會發(fā)生(Blocking)。如果設置了緩存,就有可能不發(fā)生阻塞, 只有buffer滿了后 send才會阻塞, 而只有緩存空了后receive才會阻塞。一個nil channel不會通信。

可以通過內建的close方法可以關閉Channel。

你可以在多個goroutine從/往 一個channel 中 receive/send 數據, 不必考慮額外的同步措施。

Channel可以作為一個先入先出(FIFO)的隊列,接收的數據和發(fā)送的數據的順序是一致的。

channel的 receive支持 multi-valued assignment,如

v, ok := <-ch
它可以用來檢查Channel是否已經被關閉了。

send語句

send語句用來往Channel中發(fā)送數據, 如ch <- 3。
它的定義如下:

SendStmt = Channel "<-" Expression .
Channel  = Expression .

在通訊(communication)開始前channel和expression必選先求值出來(evaluated),比如下面的(3+4)先計算出7然后再發(fā)送給channel。

c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)

send被執(zhí)行前(proceed)通訊(communication)一直被阻塞著。如前所言,無緩存的channel只有在receiver準備好后send才被執(zhí)行。如果有緩存,并且緩存未滿,則send會被執(zhí)行。

往一個已經被close的channel中繼續(xù)發(fā)送數據會導致run-time panic。

往nil channel中發(fā)送數據會一致被阻塞著。

receive 操作符

<-ch用來從channel ch中接收數據,這個表達式會一直被block,直到有數據可以接收。
從一個nil channel中接收數據會一直被block。

從一個被close的channel中接收數據不會被阻塞,而是立即返回,接收完已發(fā)送的數據后會返回元素類型的零值(zero value)。

如前所述,你可以使用一個額外的返回參數來檢查channel是否關閉。

x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch

如果OK 是false,表明接收的x是產生的零值,這個channel被關閉了或者為空。

blocking

缺省情況下,發(fā)送和接收會一直阻塞著,直到另一方準備好。這種方式可以用來在gororutine中進行同步,而不必使用顯示的鎖或者條件變量。

如官方的例子中x, y := <-c, <-c這句會一直等待計算結果發(fā)送到channel中。

import "fmt"
func sum(s []int, c chan int) {
 sum := 0
 for _, v := range s {
  sum += v
 }
 c <- sum // send sum to c
}
func main() {
 s := []int{7, 2, 8, -9, 4, 0}
 c := make(chan int)
 go sum(s[:len(s)/2], c)
 go sum(s[len(s)/2:], c)
 x, y := <-c, <-c // receive from c
 fmt.Println(x, y, x+y)
}
Buffered Channels

make的第二個參數指定緩存的大?。篶h := make(chan int, 100)。

通過緩存的使用,可以盡量避免阻塞,提供應用的性能。

Range

for …… range語句可以處理Channel。

func main() {
 go func() {
  time.Sleep(1 * time.Hour)
 }()
 c := make(chan int)
 go func() {
  for i := 0; i < 10; i = i + 1 {
   c <- i
  }
  close(c)
 }()
 for i := range c {
  fmt.Println(i)
 }
 fmt.Println("Finished")
}

range c產生的迭代值為Channel中發(fā)送的值,它會一直迭代直到channel被關閉。上面的例子中如果把close(c)注釋掉,程序會一直阻塞在for …… range那一行。

select

select語句選擇一組可能的send操作和receive操作去處理。它類似switch,但是只是用來處理通訊(communication)操作。
它的case可以是send語句,也可以是receive語句,亦或者default。

receive語句可以將值賦值給一個或者兩個變量。它必須是一個receive操作。

最多允許有一個default case,它可以放在case列表的任何位置,盡管我們大部分會將它放在最后。

import "fmt"
func fibonacci(c, quit chan int) {
 x, y := 0, 1
 for {
  select {
  case c <- x:
   x, y = y, x+y
  case <-quit:
   fmt.Println("quit")
   return
  }
 }
}
func main() {
 c := make(chan int)
 quit := make(chan int)
 go func() {
  for i := 0; i < 10; i++ {
   fmt.Println(<-c)
  }
  quit <- 0
 }()
 fibonacci(c, quit)
}

如果有同時多個case去處理,比如同時有多個channel可以接收數據,那么Go會偽隨機的選擇一個case處理(pseudo-random)。如果沒有case需要處理,則會選擇default去處理,如果default case存在的情況下。如果沒有default case,則select語句會阻塞,直到某個case需要處理。

需要注意的是,nil channel上的操作會一直被阻塞,如果沒有default case,只有nil channel的select會一直被阻塞。

select語句和switch語句一樣,它不是循環(huán),它只會選擇一個case來處理,如果想一直處理channel,你可以在外面加一個無限的for循環(huán):

for {
 select {
 case c <- x:
  x, y = y, x+y
 case <-quit:
  fmt.Println("quit")
  return
 }
}

timeout

select有很重要的一個應用就是超時處理。 因為上面我們提到,如果沒有case需要處理,select語句就會一直阻塞著。這時候我們可能就需要一個超時操作,用來處理超時的情況。
下面這個例子我們會在2秒后往channel c1中發(fā)送一個數據,但是select設置為1秒超時,因此我們會打印出timeout 1,而不是result 1。

import "time"
import "fmt"
func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}

其實它利用的是time.After方法,它返回一個類型為<-chan Time的單向的channel,在指定的時間發(fā)送一個當前時間給返回的channel中。

Timer和Ticker

我們看一下關于時間的兩個Channel。
timer是一個定時器,代表未來的一個單一事件,你可以告訴timer你要等待多長時間,它提供一個Channel,在將來的那個時間那個Channel提供了一個時間值。下面的例子中第二行會阻塞2秒鐘左右的時間,直到時間到了才會繼續(xù)執(zhí)行。

timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")

當然如果你只是想單純的等待的話,可以使用time.Sleep來實現。

你還可以使用timer.Stop來停止計時器。

timer2 := time.NewTimer(time.Second)
go func() {
 <-timer2.C
 fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
 fmt.Println("Timer 2 stopped")
}

ticker是一個定時觸發(fā)的計時器,它會以一個間隔(interval)往Channel發(fā)送一個事件(當前時間),而Channel的接收者可以以固定的時間間隔從Channel中讀取事件。下面的例子中ticker每500毫秒觸發(fā)一次,你可以觀察輸出的時間。

ticker := time.NewTicker(time.Millisecond * 500)
go func() {
 for t := range ticker.C {
  fmt.Println("Tick at", t)
 }
}()

類似timer, ticker也可以通過Stop方法來停止。一旦它停止,接收者不再會從channel中接收數據了。

close

內建的close方法可以用來關閉channel。

總結一下channel關閉后sender的receiver操作。
如果channel c已經被關閉,繼續(xù)往它發(fā)送數據會導致panic: send on closed channel:

import "time"
func main() {
 go func() {
  time.Sleep(time.Hour)
 }()
 c := make(chan int, 10)
 c <- 1
 c <- 2
 close(c)
 c <- 3
}

但是從這個關閉的channel中不但可以讀取出已發(fā)送的數據,還可以不斷的讀取零值:

c := make(chan int, 10)
c <- 1
c <- 2
close(c)
fmt.Println(<-c) //1
fmt.Println(<-c) //2
fmt.Println(<-c) //0
fmt.Println(<-c) //0

但是如果通過range讀取,channel關閉后for循環(huán)會跳出:

c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
 fmt.Println(i)
}

通過i, ok := <-c可以查看Channel的狀態(tài),判斷值是零值還是正常讀取的值。

c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false

同步

channel可以用在goroutine之間的同步。
下面的例子中main goroutine通過done channel等待worker完成任務。 worker做完任務后只需往channel發(fā)送一個數據就可以通知main goroutine任務完成。

import (
 "fmt"
 "time"
)
func worker(done chan bool) {
 time.Sleep(time.Second)
 // 通知任務已完成
 done <- true
}
func main() {
 done := make(chan bool, 1)
 go worker(done)
 // 等待任務完成
 <-done
}

相關文章

  • 一文帶你徹底搞懂 Golang 中的方法(Methods)

    一文帶你徹底搞懂 Golang 中的方法(Methods)

    Golang 支持一些類似面向對象編程的特性,方法就其中之一,本文將詳細介紹 Golang 中方法相關的知識,感興趣的小伙伴跟著小編一起來學習吧
    2023-07-07
  • 詳解Go如何基于現有的context創(chuàng)建新的context

    詳解Go如何基于現有的context創(chuàng)建新的context

    在?Golang?中,context?包提供了創(chuàng)建和管理上下文的功能,那么在GO語言中如何基于現有的context創(chuàng)建新的context,下面小編就來和大家詳細聊聊
    2024-01-01
  • Golang使用gvm進行版本控制的安裝使用教程

    Golang使用gvm進行版本控制的安裝使用教程

    這篇文章主要為大家介紹了Golang使用gvm進行版本控制的安裝使用教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • 一文帶你吃透Golang中net/http標準庫服務端

    一文帶你吃透Golang中net/http標準庫服務端

    這篇文章將從服務端(Server)作為切入點和大家分享一下Go語言net/http標準庫的實現邏輯,進而一步步分析http標準庫內部是如何運作的,感興趣的可以了解下
    2024-03-03
  • go語言通過odbc訪問Sql Server數據庫的方法

    go語言通過odbc訪問Sql Server數據庫的方法

    這篇文章主要介紹了go語言通過odbc訪問Sql Server數據庫的方法,實例分析了Go語言通過odbc連接與查SQL Server詢數據庫的技巧,需要的朋友可以參考下
    2015-03-03
  • 一文帶你使用Golang實現SSH客戶端

    一文帶你使用Golang實現SSH客戶端

    SSH?全稱為?Secure?Shell,是一種用于安全地遠程登錄到網絡上的其他計算機的網絡協議,本文主要為大家詳細介紹了如何使用?Golang?實現?SSH?客戶端,需要的可以參考下
    2023-11-11
  • Go語言使用讀寫OPC詳解

    Go語言使用讀寫OPC詳解

    這篇文章主要介紹了Go語言使用讀寫OPC詳解,圖文講解的很清晰,有感興趣的同學可以學習下
    2021-03-03
  • Go語言之自定義集合Set

    Go語言之自定義集合Set

    本文主要介紹的是Go語言的自定義集合Set,文中介紹的很詳細,有需要的可以參考學習。
    2016-08-08
  • Go語言設計模式之實現觀察者模式解決代碼臃腫

    Go語言設計模式之實現觀察者模式解決代碼臃腫

    今天學習一下用?Go?實現觀察者模式,觀察者模式主要是用來實現事件驅動編程。事件驅動編程的應用還是挺廣的,除了我們都知道的能夠用來解耦:用戶修改密碼后,給用戶發(fā)短信進行風險提示之類的典型場景,在微服務架構實現最終一致性、實現事件源A?+?ES
    2022-08-08
  • Golang?編寫Tcp服務器的解決方案

    Golang?編寫Tcp服務器的解決方案

    Golang?作為廣泛用于服務端和云計算領域的編程語言,tcp?socket?是其中至關重要的功能,這篇文章給大家介紹Golang?開發(fā)?Tcp?服務器及拆包粘包、優(yōu)雅關閉的解決方案,感興趣的朋友一起看看吧
    2022-10-10

最新評論