Golang 笔记:channel

2015-01-05 阅读: Go

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

在Go中,channel要求操作双方必须明确数据类型,操作的一方并不关心另外一端操作者的数量。 可以把channel理解成一个队列,但有其特殊性,从同步和异步两种模式来理解:

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

带缓冲区的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) // 缓冲区中还有数据,读取数据不会阻塞

从channel中接收数据

  • 直接读取
 msg := <-ch
  • ok-idom,判断通道是否被关闭

从已经关闭的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

从nil通道接收数据将一直阻塞。

向channel中发送数据

向已经关闭的channel发送数据将panic;向nil通道发送数据将一直阻塞。

单向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()
}

msg0
msg1
msg2
  • 单向通道一般用来实现对通道更严谨的操作逻辑,因为不能在通道上做逆向操作。
  • close函数无法用于关闭接收端的通道<-chan
  • 无法将单向通道类型转换回普通通道

channel和select-case语句

select语句用于处理多个channel,会随机选择一个可用channel进行收发操作。

func main() {
	var wg sync.WaitGroup
	wg.Add(3)

	ch1 := make(chan string)
	ch2 := make(chan string)

	go func() {
		defer wg.Done()
		for {
			select {
			case x, ok := <-ch1:
				if !ok { // ch1已结关闭,设置为nil,从nil通道接收数据将一直阻塞
					ch1 = nil
					break
				}
				fmt.Println("ch1:", x)
			case x, ok := <-ch2:
				if !ok { // ch2已结关闭,设置为nil,从nil通道接收数据将一直阻塞
					ch2 = nil
					break
				}
				fmt.Println("ch2", x)
			}
			if ch1 == nil && ch2 == nil {
				return
			}
		}
	}()

	go func() {
		defer wg.Done()
		defer close(ch1)
		for i := 0; i < 4; i++ {
			ch1 <- fmt.Sprint("ch1", i)
		}
	}()

	go func() {
		defer wg.Done()
		defer close(ch2)
		for i := 0; i < 8; i++ {
			ch2 <- fmt.Sprint("ch2", i)
		}
	}()
	wg.Wait()
}

如果所有的case下的通道都不可用的话,会阻塞操作,此时可以使用default语句避开阻塞,但要注意处理外层循环避免产生空耗。

标题:Golang 笔记:channel
本文链接:https://blog.frognew.com/2015/01/go-channel-notes.html
转载请注明出处。

目录