Golang的chan阻塞测试

在《The way to go - 14.2 协程间的信道》教程中看到了关于chan阻塞的相关内容,自己写代码对通道的阻塞特性做了一点测试。

贴之前先复习一下知识点。以下是我自己的总结,如果不对还请网友指正。

1,golang的通道(chan)可分为不带缓存的通道和带缓存的通道。用make函数创建通道的时候,如果不指定缓存大小,创建的就是不带缓存的通道。

2,通道是一个类型化消息队列。这句话有两个意思,一是在创建通道时,需要指定通道中传输什么类型的数据。例如var c chan int,这句代码声明了一个名字叫c的通道,这个通道只能传输int类型的数据。int也可以换成空接口(interface{})类型,但数据接收端在使用数据时就需要进行数据类型判定。这句话的第二个意思是通道是一个先进先出(FIFO)的结构,放入通道的数据具有时序性。

3,向通道中放入数据的一端可以称为生产者,从通道获取数据的一端可以称为消费者。               

c := make(chan int)    // 不带缓存的通道
c <- 100               // 生产
recv := <- c           // 消费

4,不带缓存的通道,在通道任一一端没有准备就绪之前,另一端就会产生阻塞。下面用The way to go的例子来说明。

package main

import (
	"fmt"
)

func f1(in chan int) {
	fmt.Println(<-in)
}

func main() {
	out := make(chan int)
	out <- 2
	go f1(out)
}

上面这段代码在执行的时候会产生死锁。在main函数中先执行向out通道中放入数据,再启动go协程。在向out通道放入数据时,通道的消费者还未准备就绪(粗暴一点解释就是,还没有执行到消费数据的代码),所以程序会一直在out <- 2这个地方一直阻塞,不会执行go f1(out)。不执行go f1(out)的结果就是,go协程中的消费者端就永远不会就绪。这段代码只需要将启动go协程和向通道放入数据的代码前后调换下位置,就可以解决死锁。

接下来就用两个稍微复杂一点的程序来说明通道阻塞。

例1:


func main() {

    // 创建不带缓存的通道
	c := make(chan int)

    // 启动go协程并将通道c作为参数传入
	go myRoutine(c)

    // 向通道中写入数据,并在写入成功后打印写入成功的消息
	for i := 0; i < 10; i++ {
		c <- i
		fmt.Printf("Sent %v to chan\n", i)
	}

    // 打印程序结束时间
	fmt.Printf("End process at %v", time.Now())
}

func myRoutine(c chan int) {

    // 打印go协程启动时间
	fmt.Printf("Start goroutine at %v\n", time.Now())
	var msg int

	for {

		time.Sleep(4 * 1e9)

        // 从通道中接收数据
		msg = <-c

		time.Sleep(1 * 1e9)

        // 打印接收到的数据和接收时间
		fmt.Printf("Get msg %d from chan at %v\n", msg, time.Now())
	}
}

程序输出如下:

Start goroutine at 2021-10-01 10:41:45.9047405 +0800 CST m=+0.002146401
Sent 0 to chan at main method.
Get msg 0 from chan at 2021-10-01 10:41:50.918935 +0800 CST m=+5.016340901
Sent 1 to chan at main method.
Get msg 1 from chan at 2021-10-01 10:41:55.9351083 +0800 CST m=+10.032514201
Sent 2 to chan at main method.
Get msg 2 from chan at 2021-10-01 10:42:00.9482089 +0800 CST m=+15.045614801
Sent 3 to chan at main method.
Get msg 3 from chan at 2021-10-01 10:42:05.9627741 +0800 CST m=+20.060180001
Sent 4 to chan at main method.
Get msg 4 from chan at 2021-10-01 10:42:10.9670808 +0800 CST m=+25.064486701
Sent 5 to chan at main method.
Get msg 5 from chan at 2021-10-01 10:42:15.9917451 +0800 CST m=+30.089151001
Sent 6 to chan at main method.
Get msg 6 from chan at 2021-10-01 10:42:21.0252843 +0800 CST m=+35.122690201
Sent 7 to chan at main method.
Get msg 7 from chan at 2021-10-01 10:42:26.0522605 +0800 CST m=+40.149666401
Sent 8 to chan at main method.
Get msg 8 from chan at 2021-10-01 10:42:31.0667841 +0800 CST m=+45.164190001
Sent 9 to chan at main method.
End process at 2021-10-01 10:42:35.0850043 +0800 CST m=+49.182410201

从上面的输出可以看出在执行到msg = <-c这句代码之前(消费者端就绪之前),生产者端是处于发送等待状态(阻塞)的。消费者端一旦准备就绪,生产者端马上向通道中写入数据。

例2:

func main() {

	c := make(chan int)

	go myRoutine(c)

	time.Sleep(5 * 1e9)

	for i := 0; i < 5; i++ {
		c <- i
		fmt.Printf("Sent %v to chan at main method.\n", i)
		time.Sleep(5 * 1e9)
	}

	fmt.Printf("End process at %v", time.Now())
}

func myRoutine(c chan int) {

	fmt.Printf("Start goroutine at %v\n", time.Now())
	var msg int

	for {
		fmt.Printf("Ready to get msg from chan at %v\n", time.Now())
		msg = <-c
		fmt.Printf("Get msg %d from chan at %v\n", msg, time.Now())
	}
}

程序输出如下:

Start goroutine at 2021-10-01 10:55:51.5484438 +0800 CST m=+0.002574401
Ready to get msg from chan at 2021-10-01 10:55:51.5609746 +0800 CST m=+0.015105201
Sent 0 to chan at main method.
Get msg 0 from chan at 2021-10-01 10:55:56.5649773 +0800 CST m=+5.019107901
Ready to get msg from chan at 2021-10-01 10:55:56.5649773 +0800 CST m=+5.019107901
Sent 1 to chan at main method.
Get msg 1 from chan at 2021-10-01 10:56:01.5794729 +0800 CST m=+10.033603501
Ready to get msg from chan at 2021-10-01 10:56:01.5794729 +0800 CST m=+10.033603501
Sent 2 to chan at main method.
Get msg 2 from chan at 2021-10-01 10:56:06.5900095 +0800 CST m=+15.044140101
Ready to get msg from chan at 2021-10-01 10:56:06.5900095 +0800 CST m=+15.044140101
Sent 3 to chan at main method.
Get msg 3 from chan at 2021-10-01 10:56:11.6087279 +0800 CST m=+20.062858501
Ready to get msg from chan at 2021-10-01 10:56:11.6087279 +0800 CST m=+20.062858501
Sent 4 to chan at main method.
Get msg 4 from chan at 2021-10-01 10:56:16.6128717 +0800 CST m=+25.067002301
Ready to get msg from chan at 2021-10-01 10:56:16.6128717 +0800 CST m=+25.067002301
End process at 2021-10-01 10:56:21.6246286 +0800 CST m=+30.078759201

从上面的输出可以看出在执行到main函数中c <- i这句代码之前(生产者端就绪之前),消费者端是处于发送等待状态(阻塞)的。生产者端一旦准备就绪,消费者端马上从通道中取得数据,并开始执行之后的打印语句。

你可能感兴趣的:(Go语言,golang)