Golang的channel

Go推荐的并发模型为CSP(Communicating Sequential Process),Channel是CSP的核心。

CSP并发模型不同于传统的多线程并发通过共享内存来通信,CSP是以通信的方式来共享内存。

Go并发的核心哲学是尽量通过消息传递取代共享内存来尽可能的避免显示锁。

在Go中,channel要求操作双方必须明确数据类型,操作的一方并不关心另外一端操作者的数量。

可以把channel理解成一个队列,但有其特殊性,从同步和异步两种模式来理解:

  • 同步模式:要求channel的发送方和接收方成对出现,如果一方没有就绪,则另一方将阻塞。

  • 异步模式:异步模式下将抢夺channel的缓冲区,发送方要求有缓冲区有空位可写入,如果没有则发送方阻塞;接收方要求有缓存数据可读取,如果没有则接收方阻塞。

1.带缓冲区的channel

c := make(chan string, 3)
c <- "hello"
c <- "world"                //异步通道缓冲区未满,写入数据不会阻塞
fmt.Println(len(c), cap(c)) // 2 3 len和cap分别为已经缓冲数量和缓冲区大小
fmt.Println(<-c)
fmt.Println(<-c) // 缓冲区中还有数据,读取数据不会阻塞

2.从channel中接收数据
第一种直接读取:

msg := <-ch

从已经关闭的channel中接收数据,可能返回已经缓冲的数据或者零值,可使用ok-idom s, ok := <-ch判断通道是否被关闭。

func main() {
    done := make(chan struct{})
    ch := make(chan string)
    go func() {
        defer close(done)
        for {
            s, ok := <-ch
            if !ok { // 判断通道是否被关闭
                fmt.Println("the channel ch is closed")
                return
            }
            fmt.Println(s)
        }
    }()
    ch <- "a"
    ch <- "b"
    close(ch)
    <-done
}

输出:
a
b
the channel ch is closed

ok为true的时候,表示从channel中读取出了数据,此时channel有可能还没关闭,也有可能已经关闭了(读取的是缓冲区的数据);ok为false的时候,则通道一定是关闭了。

func main() {
    ch := make(chan string, 3)
    ch <- "hello"
    ch <- "world"
    close(ch)
    for i := 0; i < cap(ch); i++ {
        s, ok := <-ch
        fmt.Println(i, ":", s, ok)
    }
}

输出:
0 : hello true
1 : world true
2 : false

3.向channel中发送数据

  • 向已经关闭的channel发送数据将 panic .

  • 向nil通道发送数据将一直阻塞。

4.单向channel
channel默认是双向的,不区分发送端和接收端。 可以通过单向channel限制收发方向。

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    ch := make(chan string)
    go func(c <-chan string) {
        defer wg.Done()
        for x := range c {
            fmt.Println(x)
        }
    }(ch)
    go func(c chan<- string) {
        defer wg.Done()
        defer close(c)
        for i := 0; i < 3; i++ {
            c <- fmt.Sprint("msg", i)
        }
    }(ch)
    wg.Wait()
}
  • 单向通道一般用来实现对通道更严谨的操作逻辑,因为不能在通道上做逆向操作。

  • close函数无法用于关闭接收端的通道<-chan

  • 无法将单向通道类型转换回普通通道

5.channel和select-case语句
select-case语句可以用来监听多个channel,会随机选择一个可用channel进行接收操作。

select {
    case v1 := <-ch1:
        log.Println("recieve from ch1: ", v1)
    case v2 := <-ch2:
        log.Println("recieve from ch2: ", v2)
}

上面的代码会从ch1ch2中随机选择一个可以接收数据的channel接收数据,否则将一直阻塞。

可以为该代码加上超时控制:

select {
    case v1 := <-ch1:
        log.Println("recieve from ch1: ", v1)
    case v2 := <-ch2:
        log.Println("recieve from ch2: ", v2)
    case <-time.After(time.Second * 2):
        log.Println("time out")
}