GoLang的并发和通道理解

发表时间:2022-08-30 08:31:41 作者:赛马牛

今天又累又困,那就写一篇划水文吧。来看一下GoLang的go函数和通道(chan)的使用。

func f1(c chan time.Time) {

// 在这里休眠了10s
time.Sleep(10000 * time.Millisecond)
time := time.Now()
c <- time
}


func f2(c chan time.Time) {
time := time.Now()
c <- time
}

func main() {
chan1 := make(chan time.Time)
chan2 := make(chan time.Time)
go f1(chan1)
go f2(chan2)
n1, n2 := <-chan1, <-chan2
fmt.Print(n1)
fmt.Print(n2)
}


大约经过10s之后,输出:


2022-08-30 15:52:53.673489 +0800 CST m=+10.024950265
2022-08-30 15:52:43.669836 +0800 CST m=+0.021597356

可以看到在f1加了一个10秒的sleep,之后才输出的内容,为什么会这样呢?因为要等待chan2通道的响应才能执行后面的操作。

我们还可以这么试:

func f1(c chan time.Time) {

        //  在这里休眠了10s
	time.Sleep(10000 * time.Millisecond)
	time := time.Now()
	c <- time
}


func f2(c chan time.Time) {
	time := time.Now()
	c <- time
}

func main() {
	chan1 := make(chan time.Time)
	chan2 := make(chan time.Time)
	go f1(chan1)
	go f2(chan2)
        n2 := <-chan2
        fmt.Print(n2)
n1 := <-chan1 fmt.Print(n1) }

输出:

2022-08-30 15:55:43.912786 +0800 CST m=+0.009478518
2022-08-30 15:55:53.917923 +0800 CST m=+10.014314637

可以看到第二个输出要延迟10s,第一个输出是立即输出的。

那如果不接收chan1数据呢?

func f1(c chan time.Time) {

        //  在这里休眠了10s
	time.Sleep(10000 * time.Millisecond)
	time := time.Now()
	c <- time
}


func f2(c chan time.Time) {
	time := time.Now()
	c <- time
}

func main() {
	chan1 := make(chan time.Time)
	chan2 := make(chan time.Time)
	go f1(chan1)
	go f2(chan2)
        n2 := <-chan2
        fmt.Print(n2)
	fmt.Print(time.Now())}


输出:


2022-08-30 16:11:38.089543 +0800 CST m=+0.026880760
2022-08-30 16:11:38.089732 +0800 CST m=+0.027068961

可以看到在不接收chan1通道的数据的情况下,两个输出是近乎同时输出的。

这是为什么呢?
我在官方document(https://golang.google.cn/ref/spec#Receive_operator)的找到了

这句话:


The capacity, in number of elements, sets the size of the buffer in the channel.If the capacity is zero or absent, the channel is unbuffered and communicationsucceeds only when both a sender and receiver are ready. Otherwise, the channelis buffered and communication succeeds without blocking if the bufferis not full (sends) or not empty (receives).Anilchannel is never ready for communication.

翻译过来就是说:


容量(以元素数为单位)设置通道中缓冲区的大小。如果容量为零或不存在,则信道无缓冲,只有当发送方和接收方都准备好时,通信才成功。否则,如果缓冲区不满(发送)或不为空(接收),则对信道进行缓冲,通信成功而不阻塞nil频道从未准备好进行通信。


后面一段话,我们可以明白这是说chan的发生方和接收方同时准备好,才会通信,如果你不使用接收者,那么通道是不会产生阻塞的。

那有人就可能疑惑,那不堵塞是不是就不执行f1方法了?那我们可以这么试一试:

func f1(c chan time.Time) {
        fmt.Print("I'm func f1 \n")	time := time.Now()
	c <- time
}

func f2(c chan time.Time) {
	time := time.Now()
	c <- time
}

func main() {
	chan1 := make(chan time.Time)
	chan2 := make(chan time.Time)
	go f1(chan1)
	go f2(chan2)
	n2 := <-chan2	fmt.Print(n2)
}


输出:


I'm func f1 
2022-08-30 16:36:44.409389 +0800 CST m=+0.012299705


可以看到f1方法和f2方法都是有运行的,阻塞只是在发送者和接收者都准备好的情况才会阻塞,如果定义了接受者,发送者没有发送回数据则会在接受者方永久阻塞。


我们再看前面那一段话,通道创建的时候,可以指定缓存区,那我们试一下吧。

func main() {
        // 缓冲区大小为2
        ch := make(chan int, 2)

        // 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
        // 而不用立刻需要去同步读取数据
        ch <- 1
        ch <- 2

        // 获取这两个数据
        fmt.Println(<-ch)
        fmt.Println(<-ch)
}


为什么要用缓冲区呢?可以看一下以下这段话:

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。


特别注意红色这段字,我就不写挂代码出来了。

复制