关键词

channelpanicselect

导语

ChannelGolangGoroutinechannelchannelpanicchannelselect

channel通道发送与接收操作的特点

  • 发送操作与接收操作都是互斥的
goroutinechannelgoroutinechannelchannelchannelchannel

对通道的发送与接收操作,什么时候会阻塞?

nil
channelmakechannelnil
package main

import (
    "fmt"
    "time"
)

func main()  {
    var ch chan int
    go func() {
        ch <- 1
        fmt.Println("goroutine finish")
    }()
    time.Sleep(10*time.Second)
}
// main goroutine 会等待10s,不会打印goroutine finish,说明一直阻塞
  • 缓冲通道,已满,进行发送操作,发送操作阻塞
package main

import (
    "fmt"
    "time"
)

func main() {
    var ch = make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3
    fmt.Println("channel is fill")
    go func() {
        ch <- 4
        fmt.Println("fill channel can  receive a number?")
    }()
    time.Sleep(5 * time.Second)
}
  • 缓冲通道,已空,进行接收操作,接收操作阻塞
  • 非缓冲通道,只有当发送和接收都准备好(其实就是同步),才不会阻塞。
package main

func main() {
    // 示例1。
    ch1 := make(chan int, 1)
    ch1 <- 1
    //ch1 <- 2 // 通道已满,因此这里会造成阻塞。

    // 示例2。
    ch2 := make(chan int, 1)
    //elem, ok := <-ch2 // 通道已空,因此这里会造成阻塞。
    //_, _ = elem, ok
    ch2 <- 1

    // 示例3。
    var ch3 chan int
    //ch3 <- 1 // 通道的值为nil,因此这里会造成永久的阻塞!
    //<-ch3 // 通道的值为nil,因此这里会造成永久的阻塞!
    _ = ch3
}
panic
channel
package main

func main()  {
    var ch = make(chan int, 2)
    close(ch)
    ch <- 1
}
  • 再次关闭已经关闭的通道
package main

func main()  {
    var ch = make(chan int, 2)
    close(ch)
    //ch <- 1
    close(ch)
}

一个正常的收发channel示例

package main

import "fmt"

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

    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println("sender channel a val: ", i)
            ch <- i
        }
        fmt.Println("sender finished, will close the channel")
        close(ch)
    }()

    for {
        elem, ok := <- ch
        if !ok {
            fmt.Println("channel has already closed.")
            break
        } else {
            fmt.Println("receive a val from channel, ", elem)
        }
    }
    fmt.Println("End.")
}

select

基础概念

selectchannelselectcase
selectchannelI/O
// 给定几个通道,哪个通道不为空,便执行相应语句

// 准备好几个通道。
intChannels := [3]chan int{
  make(chan int, 1),
  make(chan int, 1),
  make(chan int, 1),
}
// 随机选择一个通道,并向它发送元素值。
index := rand.Intn(3)
fmt.Printf("The index: %d\n", index)
intChannels[index] <- index
// 哪一个通道中有可取的元素值,哪个对应的分支就会被执行。
select {
case <-intChannels[0]:
  fmt.Println("The first candidate case is selected.")
case <-intChannels[1]:
  fmt.Println("The second candidate case is selected.")
case elem := <-intChannels[2]:
  fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
default:
  fmt.Println("No candidate case is selected!")
}

注意点

selectcaseselectdefaultcaseselectselectdefaultevaluatedcasedefaultselectdefaultcaseselectcaseselectcaseforselectcasebreakforselectcaseselect
func example2() {
    intChan := make(chan int, 1)
    // 一秒后关闭通道。
    time.AfterFunc(time.Second, func() {
        close(intChan)
    })
    select {
    case _, ok := <-intChan:
        if !ok {
            fmt.Println("The candidate case is closed.")
            break
        }
        fmt.Println("The candidate case is selected.")
    }
}
// result
The candidate case is closed.
selectcaseintChan1sokfalseThe candidate case is closed.
casedefault
package main

import "fmt"

var channels = [3]chan int{
    nil,
    make(chan int),
    nil,
}

var numbers = []int{0, 1, 2}

func main() {
    select {
    case getChan(0) <- getNumber(0):
        fmt.Println("The first candidate case is selected.")
    case getChan(1) <- getNumber(1):
        fmt.Println("The second candidate case is selected.")
    case getChan(2) <- getNumber(2):
        fmt.Println("The third candidate case is selected")
    default:
        fmt.Println("No candidate case is selected!")
    }
}

func getNumber(i int) int {
    fmt.Printf("numbers[%d]\n", i)
    return numbers[i]
}

func getChan(i int) chan int {
    fmt.Printf("channels[%d]\n", i)
    return channels[i]
}

// result
No candidate case is selected!
casenildefault
case
package main

import "fmt"

var channels = [3]chan int{
    nil,
    make(chan int),
    nil,
}

var numbers = []int{0, 1, 2}

func main() {
    go func() {
        for {
            <- getChan(1)
        }
    }()
    select {
    case getChan(0) <- getNumber(0):
        fmt.Println("The first candidate case is selected.")
    case getChan(1) <- getNumber(1):
        fmt.Println("The second candidate case is selected.")
    case getChan(2) <- getNumber(2):
        fmt.Println("The third candidate case is selected")
    default:
        fmt.Println("No candidate case is selected!")
    }
    fmt.Println("finish")
}

func getNumber(i int) int {
    fmt.Printf("numbers[%d]\n", i)
    return numbers[i]
}

func getChan(i int) chan int {
    fmt.Printf("channels[%d]\n", i)
    return channels[i]
}
goroutine

参考资料

1、极客时间《Go语言核心36讲》