channel用法和源码
源码分析https://mp.weixin.qq.com/s/eCwZMwGjU2yoXu6K2nGF3g
1 当chan用close关闭时,无论是有缓存的还是无缓存的,返回已缓冲数据或零值,如果重复用close关闭chan或对关闭的chan写入值都会报错,
https://blog.csdn.net/qq_41065919/article/details/107729571
https://www.cnblogs.com/pluse/p/12167537.html
struct{}类型好像只有一种具体实例即struct{}{},chan中如果有这种类型,只能写入它的零值struct{}{},所以类型为struct{}的chan无论是写入还是读出都时零值,
func main() { m := make(chan struct{}, 1) // 从空的m中一直读取值会一直阻塞,所以会报死锁的错误,all goroutines are asleep - deadlock! //b := <- m //fmt.Println(b) // 如果这里就关闭了协程,下面会直接输出chan中的零值,而不会走default, //close(m) //m <- struct{}{} go func() { for{ select { case a := <- m: fmt.Println(a) time.Sleep(time.Second) default: fmt.Println("chan为空,读取的时候会阻塞") time.Sleep(time.Second) } } }() // 加这个是为了主协程等待里面的子协程,否则的话主协程直接执行完了,观察不到子协程的具体输出, time.Sleep(time.Second * 5) }
a 如果close无缓存channel,
1> close前没有因为写入阻塞,则读出的是channe里类型的零值,
2> 如果写入阻塞了,则第一次读出的是写入的值,之后读出的是零值,
func main() { m := make(chan int) go func() { m <- 555 // 这里虽然close了,但之后第一次读出的仍是第一次写入的值, close(m) // 保证close在读之前 time.Sleep(time.Second * 3) }() go func() { for{ select { case a := <- m: if a != 0{ fmt.Println("第一次从channel中读出的值为colse前写入的值", a) } else{ fmt.Println("之后读出的值为int类型的零值", a) } time.Sleep(time.Second) default: fmt.Println("chan为空,读取的时候会阻塞") time.Sleep(time.Second) } } }() time.Sleep(time.Second * 4) }
b 如果close有缓存channel,
1> close前没有写入,则读出的是channel里类型的零值,
2> 如果close前写入了,则会按队列的顺序把数据依次取出来,
func main() { m := make(chan int, 3) m <- 111 m <- 222 close(m) go func() { for{ select { case a := <- m: if a != 0{ fmt.Println("读出的值是之前写入的值", a) } else{ fmt.Println("之后读出的值为int类型的零值", a) } time.Sleep(time.Second) default: fmt.Println("chan为空,读取的时候会阻塞") time.Sleep(time.Second) } } }() time.Sleep(time.Second * 4) }
待学习:https://i6448038.github.io/2019/04/11/go-chan
反作弊解封代码
func main(){ goroutine_count := 2 ch := make(chan int, goroutine_count) var wg sync.WaitGroup wg.Add(1024) for i:=0; i<1024; i++{ ch <- i go func(i int) { dosomething() // 必须要写这个, <- ch wg.Done() }(i) } wg.Wait() } func dosomething() { }