[golang note] 協程通訊

channel基本語法


• channel介紹

        golang社區口號:不要經過共享內存來通訊,而應該經過通訊來共享內存golang

        golang提供一種基於消息機制而非共享內存的通訊模型。消息機制認爲每一個併發單元都是自包含的獨立個體,而且擁有本身的變量,但在不一樣併發單元間這些變量不共享。每一個併發單元的輸入和輸出只有一種,那就是消息。安全

        channel是golang在語言級提供的goroutine間的通訊方式,可使用channel在兩個或多個goroutine之間傳遞消息。 併發

        channel是進程內的通訊方式,若是須要跨進程通訊,建議使用分佈式的方法來解決,好比使用Socket或HTTP等通訊協議。 異步

        channel是類型相關的,即一個channel只能傳遞一種類型的值,須要在聲明channel時指定。能夠認爲channel是一種類型安全的管道。分佈式

• channel聲明語法

▶  語法以下

var chanName chan ElementType

▶  示例以下

var ch chan int            // int類型channel
var m map[string]chan bool // bool類型channel的map

• channel定義語法

▶  語法以下

        定義一個channel直接使用內置的函數make()便可。函數

// 聲明一個channel
var chanName chan ElementType

// 定義一個無緩衝的channel
chanName := make(chan ElementType)

// 定義一個帶緩衝的channel
chanName := make(chan ElementType, n)

• channel關閉語法

        關閉一個channel直接使用內置的函數close()便可。spa

        應該在生產者處關閉channel,而不是消費者處關閉channel,不然容易引發panic。code

// 聲明一個channel
var chanName chan ElementType

// 定義一個無緩衝的channel
chanName := make(chan ElementType)

// 定義一個帶緩衝的channel
chanName := make(chan ElementType, n)

// 關閉一個channel
close(chanName)

• channel讀寫語法

        向無緩衝的channel寫入數據會致使該goroutine阻塞,直到其餘goroutine從這個channel中讀取數據。blog

        向帶緩衝的且緩衝已滿的channel寫入數據會致使該goroutine阻塞,直到其餘goroutine從這個channel中讀取數據。進程

        向帶緩衝的且緩衝未滿的channel寫入數據不會致使該goroutine阻塞。

        從無緩衝的channel讀出數據,若是channel中無數據,會致使該goroutine阻塞,直到其餘goroutine向這個channel中寫入數據。

        從帶緩衝的channel讀出數據,若是channel中無數據,會致使該goroutine阻塞,直到其餘goroutine向這個channel中寫入數據。

        從帶緩衝的channel讀出數據,若是channel中有數據,該goroutine不會阻塞。

        總結:無緩衝的channel讀寫一般都會發生阻塞,帶緩衝的channel在channel滿時寫數據阻塞,在channel空時讀數據阻塞

// 聲明一個channel
var chanName chan ElementType

// 定義一個無緩衝的channel
chanName := make(chan ElementType)

// 定義一個帶緩衝的channel
chanName := make(chan ElementType, n) // 寫channel chanName <- value // 讀channel value := <-chanName

▶  range操做

        golang中的range經常和channel一塊兒使用,用來從channel中讀取全部值。

        range操做可以不斷讀取channel裏面的數據,直到該channel被顯式的關閉。

▪ 語法以下

for value := range chanName {
    // ...
}

▪ 示例以下

package main

import "fmt" func generateString(strings chan string) {
    strings <- "Monday"
    strings <- "Tuesday"
    strings <- "Wednesday"
    strings <- "Thursday"
    strings <- "Friday"
    strings <- "Saturday"
    strings <- "Sunday" close(strings)
}

func main() {
    strings := make(chan string) // 無緩衝channel
 go generateString(strings)

    for s := range strings {
        fmt.Println(s)
    }
}

▶  select操做

        golang中的select關鍵字用於處理異步IO,能夠與channel配合使用。

        golang中的select的用法與switch語言很是相似,不一樣的是select每一個case語句裏必須是一個IO操做。

        select會一直等待等到某個case語句完成才結束。

▪ 語法以下

select {
case <-chan1:
    // 若是chan1成功讀到數據,則進行該case處理語句
case chan2 <- 1:
    // 若是成功向chan2寫入數據,則進行該case處理語句
default:
    // 若是上面都沒有成功,則進入default處理流程
}

▪ 示例以下

package main

import "fmt" import "time" func main() {
    timeout := make(chan bool)

    go func() {
        time.Sleep(3 * time.Second) // sleep 3 seconds
        timeout <- true
    }()

    // 實現了對ch讀取操做的超時設置。
    ch := make(chan int)
    select {
    case <-ch:
    case <-timeout:
        fmt.Println("timeout!")
    }
}

▶  判斷channel關閉

        在讀取的時候使用多重返回值來判斷一個channel是否已經被關閉

▪ 語法以下

value, ok := <-chanName

if ok {
    // channel未關閉
} else {
    // channel已關閉
}

▪ 示例以下

package main

import "fmt" func generateString(strings chan string) {
    strings <- "Monday"
    strings <- "Tuesday"
    strings <- "Wednesday"
    strings <- "Thursday"
    strings <- "Friday"
    strings <- "Saturday"
    strings <- "Sunday" close(strings)
}

func main() {
    strings := make(chan string) // 無緩衝channel
 go generateString(strings)

    for {
        if s, ok := <-strings; ok {
            fmt.Println(s)
        } else {
            fmt.Println("channel colsed.")
            break
        }
    }
}

• 單向channel語法

▶  使用意義

        golang中假如一個channel只容許讀,那麼channel確定只會是空的,由於沒機會往裏面寫數據。

        golang中假如一個channel只容許寫,那麼channel最後只會是滿的,由於沒機會從裏面讀數據。

        單向channel概念,其實只是對channel的一種使用限制,即在將一個channel變量傳遞到一個函數時,能夠經過將其指定爲單向channel變量,從而限制該函數中能夠對此channel的操做,達到權限控制做用。

▶  聲明語法

var ch1 chan elementType   // ch1是一個正常的channel
var ch2 chan<- elementType // ch2是單向channel,只用於寫數據
var ch3 <-chan elementType // ch3是單向channel,只用於讀數據

▶  類型轉換

ch1 := make(chan elementType)
ch2 := <-chan elementType(ch1) // ch2是一個單向的讀取channel
ch3 := chan<- elementType(ch1) // ch3是一個單向的寫入channel

▶  示例以下

package main

import "fmt" func Parse(ch <-chan int) {
    for value := range ch {
        fmt.Println("Parsing value", value)
    }
}

func main() {
    var ch chan int
    ch = make(chan int)

    go func() {
        ch <- 1
        ch <- 2
        ch <- 3 close(ch)
    }()

    Parse(ch)
}

channel實際運用


• 主函數等待全部goroutine完成後返回

▶  使用意義

       咱們已經知道golang程序從main()函數開始執行,當main()函數返回時,程序結束且不等待其餘goroutine結束。若是main函數使用time.Sleep方式阻塞等待全部goroutine返回,那麼這個休眠時間勢必沒法控制精確。經過使用channel能夠很好解決這個問題。

▶  使用示例

package main

import "fmt" func MyRoutineFunc(ch chan int) {
    // 函數處理
    ch <- 1

    fmt.Println("MyRoutineFunc process finished.")
}

func main() {
    chs := make([]chan int, 10)

    for i := 0; i < 10; i++ {
        chs[i] = make(chan int)
        go MyRoutineFunc(chs[i])
    }

    for _, ch := range chs {
        <-ch
    }

    fmt.Println("All goroutine finished.")
}

• 實現IO超時機制

▶  使用意義

       golang沒有提供直接的超時處理機制,但咱們能夠利用select和channel結合來實現超時機制。

▶  使用示例

package main

import "fmt" import "time" func main() {
    // 實現並執行一個匿名的超時等待函數
    timeout := make(chan bool, 1)
    go func() {
        time.Sleep(3 * time.Second) // 等待3秒鐘
        timeout <- true
    }()

    // 而後結合使用select實現超時機制
    ch := make(chan int)
    select {
    case <-ch:
        // 從ch中讀取到數據
    case <-timeout:
        // 一直沒有從ch中讀取到數據,但從timeout中讀取到了數據
        fmt.Println("timeout!")
    }
}
相關文章
相關標籤/搜索