test chan


func TestChan(t *testing.T) {
    
     // 20200728
	runtime.GOMAXPROCS(4)
	ints := make(chan int, 1)
	go func() {
    
    
		tt := time.NewTicker(time.Second / 1)

		defer func() {
    
    
			if err := recover(); err != nil {
    
    
				t.Log("recover err :", err)
				return
			}
		}()
		for {
    
    
			t.Log(1, len(ints), cap(ints))
			select {
    
    
			case <-tt.C:
				ints <- time.Now().Second() send on closed channel
			}
		}
	}()
	go func() {
    
    
		for {
    
    
			t.Log(2, len(ints), cap(ints))
			select {
    
    
			case v := <-ints: // 关闭的chan 取出来的数据都是 0
				t.Log(v)
				if v == 0 {
    
    
					t.Log("is 0 ,return")
					return
				}
			}
		}
	}()

	//close(ints) // 向一个关闭的chan  写入数据会panic
	time.AfterFunc(5*time.Second, func() {
    
    
		t.Log("close ")
		close(ints)
	})
	time.Sleep(7 * time.Second)

}

Select-Cases
select块是为channel特殊设计的语法,它和switch语法非常相近。分支上它们都可以有多个case块和做多一个default块,但是也有很多不同

  1. select 到 括号{之间不得有任何表达式
  2. fallthrough关键字不能用在select里面
  3. 所有的case语句要么是channel的发送操作,要么就是channel的接收操作
  4. select里面的case语句是随机执行的,而不能是顺序执行的。设想如果第一个case语句对应的channel是非阻塞的话,case语句的顺序执行会导致后续的case语句一直得不到执行除非第一个case语句对应的channel里面的值都耗尽了。
  5. 如果所有case语句关联的操作都是阻塞的,default分支就会被执行。如果没有default分支,当前goroutine就会阻塞,当前的goroutine会挂接到所有关联的channel内部的协程队列上。 所以说单个goroutine是可以同时挂接到多个channel上的,甚至可以同时挂接到同一个channel的发送协程队列和接收协程队列上。当一个阻塞的goroutine拿到了数据接触阻塞的时候,它会从所有相关的channel队列中移除掉。

channel简单规则表
下标的活跃Channel表示即非空又非关闭的Channel

channel规则详细解释
空channel

  1. 关闭一个空channel会导致当前goroutine引发panic
  2. 向一个空channel发送值会导致当前的goroutine阻塞
  3. 从一个空channel接收值也会导致当前的goroutine阻塞
    在空channel上的调用len和cap函数都统一返回零。
    已关闭的Channel
  4. 关闭一个已关闭的channel会引发panic
  5. 向一个已关闭的channel发送值会引发panic。当这种send操作处于select块里面的case语句上时,它会随时导致select语句引发panic。
  6. 从一个已关闭的channel上接收值既不会阻塞也不能panic,它一直能成功返回。只是返回的第二个值ok永远是false,表示接收到的v是在channel关闭之后拿到的,对应得值也是相应元素类型的零值。可以无限循环从已关闭的channel上接收值。

活跃的Channel

  1. 关闭操作
    • 从channel的接收协程队列中移除所有的goroutine,并唤醒它们。
    • 从channel的接收协程队列中移除所有的goroutine,并唤醒它们。
    • 一个已关闭的channel内部的缓冲数组可能不是空的,没有接收的这些值会导致channel对象永远不会被垃圾回收。
  2. 发送操作
    • 如果是阻塞型channel,那就从channel的接收协程队列中移出第一个协程,然后把发送的值直接递给这个协程。
    • 如果是阻塞型channel,并且channel的接收协程队列是空的,那么当前的协程将会阻塞,并进入到channel的发送协程队列里。
    • 如果是缓冲型channel,并且缓冲数组里还有空间,那么将发送的值添加到数组最后,当前协程不阻塞。
    • 如果是缓冲型channel,并且缓冲数组已经满了,那么当前的协程将会阻塞,并进入到channel的发送协程队列中。
  3. 接收操作
    • 如果是缓冲型channel,并且缓冲数组有值,那么当前的协程不会阻塞,直接从数组中拿出第一个值。如果发送队列非空,还需要将队列中的第一个goroutine唤醒。
    • 如果是阻塞型channel,并且发送队列非空的话,那么唤醒发送队列第一个协程,该协程会将发送的值直接递给接收的协程。
    • 如果是缓冲型channel,并且缓冲数组为空,或者是阻塞型channel,并且发送协程队列为空,那么当前协程将会阻塞,并加入到channel的接收协程队列中。
      总结
      根据以上规则,我们可以得出以下结论
  4. 如果channel关闭了,那么它的接收和发送协程队列必然空了,但是它的缓冲数组可能还没有空。
  5. channel的接收协程队列和缓冲数组,同一个时间必然有一个是空的
  6. channel的缓冲数组如果未满,那么它的发送协程队列必然是空的
  7. 对于缓冲型channel,同一时间它的接收和发送协程队列,必然有一个是空的
  8. 对于非缓冲型channel,一般来说同一时间它的接收和发送协程队列,也必然有一个是空的,但是有一个例外,那就是当它的发送操作和接收操作在同一个select块里出现的时候,两个队列都不是空的。
    在这里插入图片描述

https://cloud.tencent.com/developer/article/1186462


channel 用完要及时让“生产者”关闭,避免发生内存泄漏

猜你喜欢

转载自blog.csdn.net/java_sparrow/article/details/107642549