33

go channel 使用及机制流程汇总

 4 years ago
source link: https://studygolang.com/articles/25783
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

协程间通讯的普通使用, 发送值给 channel , 外部 channel 接收.

func t1() {
	ch := make(chan int)
	go func() {
		ch <- 1
	}()

	// <-ch // 导致下面语句阻塞
	fmt.Println("channel int", <-ch)
}
复制代码
channel int 1
复制代码

channel 支持缓冲区

func t2() {
	ch := make(chan int, 3)
	go func() {
		ch <- 1
		ch <- 2
		ch <- 3
		ch <- 4 // 会阻塞写不入, 除非ch已接收
		close(ch) // 关闭后不能再写入, 并且缓冲区内为空时则返回零值
	}()
	fmt.Println("buffer channel int",
		<-ch,
		<-ch,
		<-ch,
		<-ch,
		<-ch)
}
复制代码
buffer channel int 1 2 3 4 0
复制代码

select 是专门给管道定制

func t3() {
	ch1 := make(chan int)
	ch2 := make(chan struct{})

	go func() {
		ch1 <- 2
	}()

	select {
	case <-ch1:
		fmt.Println("select here is ch1")
	case <-ch2:
		fmt.Println("select here is ch2")
	}
}
复制代码
select here is ch1
复制代码

使用 for

func t4() {
	ch := make(chan int, 5)

	go func() {
		for i:=1; i<=5; i++ {
			time.Sleep(time.Millisecond * 10)
			ch <- i
		}
	}()

	for v := range ch {	// 会阻塞
		fmt.Println("for channel ", v)
		if v == 5 {
			break
		}
	}
}
复制代码
for channel  1
for channel  2
for channel  3
for channel  4
for channel  5
复制代码

同时使用 selectfor

func t5() {
	chPrint := make(chan struct{}) 
	chStop := make(chan struct{}) 

	go func(){
		time.Sleep(time.Second * 1)
		chPrint <- struct{}{}

		time.Sleep(time.Second * 1)
		chPrint <- struct{}{}

		time.Sleep(time.Second * 1)
		chStop <- struct{}{}
	}()

	var sum int
	for {
		time.Sleep(time.Millisecond)

		select {
		case <-chPrint:
			fmt.Println("for+select now is", sum)
		case <-chStop:
			fmt.Println("for+select stop, result is", sum)
			return
		default:
			if sum == 10000 {
				fmt.Println("for+select end, result is", sum)
				return
			}
			sum += 1
		}
	}
}
复制代码
for+select now is 766
for+select now is 1540
for+select stop, result is 2309
复制代码

判断管道是否关闭

func t6() {
	ch := make(chan struct{})

	go func() {
		close(ch)
		//ch <- struct{}{} // 只运行这句, 输出OK
	}()
	if _, ok := <-ch; ok {
		fmt.Println("if channel is ok")
		return
	}
	fmt.Println("if channel is bad")
}
复制代码
if channel is bad
复制代码

以只发送或只接收为传递参数, 同理也可以为返回值

func t7() {
	ch := make(chan struct{})
	chExit := make(chan struct{})

	go func(chRecv <-chan struct{}) {
		fmt.Println("recv channel")
		<-chRecv
		chExit<- struct{}{}
	}(ch)

	go func(chSend chan<- struct{}) {
		fmt.Println("send channel")
		chSend <- struct{}{}
	}(ch)

	<-chExit
}
复制代码
send channel
recv channel
复制代码

channel机制流程汇总

makechan() 初始化 hchan 结构体, 如果没有缓冲区即分配 hchanSize 大小的内存并返回;而有缓冲区的情况下, 则计算管道元素类型大小并分配 hchanSize +( elem.size * size )大小的内存(缓冲区是一个环形的结构设计), 最后返回 hchan .

3MRfE3N.png!web

chansend()channel 发送数据, 首先锁住当前协程, 有如下几种情况, 按顺序判断:

  1. 有正在等待的接收者, 就立即转发给该接收者, 释放锁并退出.
  2. 有可用的缓冲区就将该值移到目标缓冲区等待被接收, 释放锁并退出.
  3. 是非阻塞(用于select)就退出, 释放锁并退出.
  4. 阻塞当前协程, 并将当前协程放到发送等待队列中并释放锁, 唤醒后清理sudog. A3Qfyan.png!web

chanrecv() 接收 channel 的数据, 首先锁住当前协程, 有如下几种情况, 按顺序判断:

  1. channel 关闭并没有缓冲数据, 接收者接收的值将会是零值, 释放锁并退出.
  2. 发送队列有发送者, 就立即接收该数据, 释放锁并退出.
  3. 有缓冲数据就将该数据复制给接收者, 释放锁并退出.
  4. 是非阻塞(用于select)就退出, 释放锁并退出.
  5. 阻塞当前协程, 并将当前协程放到发送等待队列中并释放锁, 唤醒后清理sudog. 跟 chansend 差不多, 多了个已关闭并没有缓冲数据的判断. N3Mfmqy.png!web

closechan 关闭 channel , 首先也要获取锁, 关闭管道并释放所有接收和发送的队列并清醒所有sudog. 但缓冲区的数据不会清理, 随时等待被接收.

niYfMna.png!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK