channel使用笔记

297 阅读4分钟

无缓冲channel

无缓冲channel就是在声明的时候不指定大小。

其特点是在使用的时候,读端和写端同时进行代码才不会阻塞,如下例子:

func main() {
	ch := make(chan int)
	go func(ch chan int) {
		time.Sleep(5 * time.Second)
		fmt.Println("读取到数据:", <-ch)
	}(ch)
	ch <- 1
	fmt.Println("channel写入完毕")
	time.Sleep(10 * time.Second)
}

---运行结果

开始写入channel: 2020-03-16 21:56:49.885752 +0800 CST m=+0.000144700
读取到数据: 1
channel写入完毕: 2020-03-16 21:56:54.885906 +0800 CST m=+5.000293927

开启一个协程后,main协程向ch写入1,但是此时没有其他协程读取ch,所以阻塞在这里,5s后子协程开始读取ch,main协程便成功写入ch。

如果没有阻塞的话,channel写入完成的时间与channel写入的时间差值一般都不超过1s。这里是5s,恰好为协程睡眠的时间。

func main() {
	ch := make(chan int)
	go func(ch chan int) {
		fmt.Println("子协程开启", time.Now())
		fmt.Println("读取到数据:", <-ch)
	}(ch)
	time.Sleep(5 * time.Second)
	fmt.Println("开始写入channel:", time.Now())
	ch <- 1
	fmt.Println("channel写入完毕:", time.Now())
	time.Sleep(10 * time.Second)
}

---运行结果

子协程开启 2020-03-16 21:58:20.298788 +0800 CST m=+0.000209336
开始写入channel: 2020-03-16 21:58:25.301491 +0800 CST m=+5.002907078
channel写入完毕: 2020-03-16 21:58:25.301541 +0800 CST m=+5.002957141
读取到数据: 1

开启一个协程后,协程便从channel读取数据,但是此时main协程处于sleep状态,没有其他协程写ch,所以子协程阻塞,main协程sleep完成后,向channel写入数据,子协程成功读取到数据。

如果没有阻塞的话,协程开启到协程,然后从channel读取,理想状态下应该输出0,但是这里阻塞了,并且还成功读取到休眠5s后的main协程写入的值。

有缓冲channel

有缓冲channel就是在声明的时候指定大小。如果在读端,channel内没有数据,阻塞;如果在写端,channel已经写满,阻塞。如下例子:

func main() {
	ch := make(chan int, 1)
	go func(ch chan int) {
		for value := range ch {
			fmt.Println("读取到整数:", value, time.Now())
			time.Sleep(5 * time.Second)
		}
	}(ch)
	fmt.Println("开始 ", time.Now())
	for i := 0; i < 3; i++ {
		ch <- i
	}
	fmt.Println("结束 ", time.Now())
	time.Sleep(30 * time.Second)
}

---运行结果

开始  2020-03-16 22:17:22.004602 +0800 CST m=+0.000092698
读取到整数: 0 2020-03-16 22:17:22.004818 +0800 CST m=+0.000308519
结束  2020-03-16 22:17:27.009252 +0800 CST m=+5.004710239
读取到整数: 1 2020-03-16 22:17:27.009179 +0800 CST m=+5.004637286
读取到整数: 2 2020-03-16 22:17:32.011119 +0800 CST m=+10.006545636

这里声明了缓冲区大小为1的channel。开启子协程后,main协程向channel内写入数据。

子协程读取到数据后开始休眠5秒,这时主协程继续向channel写入数据,由于channel大小只有1,且子协程还处于休眠状态,所以写入整数1后,channel已经满了,2不能再写入channel,main协程便阻塞。

5s后,子协程休眠完毕,再次消费channel,缓冲区又空起来,2得以写入。所以从开始到结束花费将近5s。

for...range和channel

for...range能够遍历出channel的值,不论是无缓冲channel还是有缓冲channel

func main() {
	ch := make(chan int, 10)
	go func(ch chan int) {
		for value := range ch {
			fmt.Println(value, time.Now())
		}
		fmt.Println("goroutine exit.")
	}(ch)
	for i := 0; i < 10; i++ {
		if i == 5 {
			close(ch)
			break
		}
		ch <- i
	}
	time.Sleep(5 * time.Second)
	for value := range ch {
		fmt.Println(value)
	}
	time.Sleep(10 * time.Second)
}

当通道被关闭后,for...range会自动退出

select和channel close

select中如果遇上channel关闭,如果不做处理,则会一直处于该case中

func main() {
	ch := make(chan struct{})
	go func(ch chan struct{}) {
		for {
			select {
			case value := <-ch:
				fmt.Println("value:", value)
			default:
				fmt.Println("default")
				time.Sleep(2 * time.Second)
			}
		}
	}(ch)
	for i := 0; i < 10; i++ {
		if i == 5 {
			close(ch)
			break
		}
		ch <- struct{}{}
		time.Sleep(1 * time.Second)
	}
	time.Sleep(10 * time.Second)
}

--- 运行结果

value: {}
default
value: {}
default
value: {}
default
value: {}
default
value: {}
value: {}
value: {}
value: {}
value: {}
value: {}
value: {}
···

可以通过 value,ok:=<-ch 然后判断ok的值解决

func main() {
	ch := make(chan int, 10)
	go func(ch chan int) {
		for {
			select {
			case value, ok := <-ch:
				if !ok {
					fmt.Println("通道关闭")
					return
				}
				fmt.Println("value:", value)
			default:
				fmt.Println("default")
				time.Sleep(2 * time.Second)
			}
		}
	}(ch)
	for i := 0; i < 10; i++ {
		if i == 5 {
			close(ch)
			break
		}
		ch <- i
		time.Sleep(1 * time.Second)
	}
	time.Sleep(10 * time.Second)
}