[golang note] 協程通信


channel基本語法


• channel介紹

        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()即可。

        應該在生產者處關閉channel,而不是消費者處關閉channel,否則容易引起panic。

// 聲明一個channel
var chanName chan ElementType

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

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

// 關閉一個channel
close(chanName)

• channel讀寫語法

        向無緩沖的channel寫入數據會導致該goroutine阻塞,直到其他goroutine從這個channel中讀取數據。

        向帶緩沖的且緩沖已滿的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!")
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM