用缓冲通道实现阻塞队列

x33g5p2x  于2021-11-15 转载在 其他  
字(3.0k)|赞(0)|评价(0)|浏览(449)

当通道(channel)没有指定最大长度(cap)时,它的最大长度是0,也就意味着发送操作将会阻塞,直到另一个goroutine在对应的通道上执行了接收操作,倒过来说也是成立的,接收操作将会阻塞,直到另一个goroutine在对应的通道上执行发送操作。

也就是说,基于无缓冲队列的发送和接收是同步进行的,而基于缓冲通道的原理是,如果通道没有满,发送操作会立刻完成,如果通道内还有消息,接收操作也会立刻完成,否则,它们才会阻塞。

创建缓冲通道的方法

make(chan int, 3)

上面的代码创建了一个chan int类型的、cap为3的缓冲通道。

通过 cap(ch) 可以获取通道的最大长度,通过len(ch)可以获取通道中消息的数量。

下面来看一个经典的生产着与消费者的例子

func consume(ch <-chan int) {
	for x := range ch {
		time.Sleep(time.Second * 2)
		fmt.Printf("consume %d at %v\n", x, time.Now())
	}
}

func produce(ch chan<- int) {
	for i := 0; i < 20; i++ {
		ch <- i
		fmt.Printf("produce %d at %v\n", i, time.Now())
	}
}

func main() {
	var blockQueue = make(chan int, 3)
	go consume(blockQueue)
	go consume(blockQueue)
	go consume(blockQueue)
	produce(blockQueue)
}

输出结果如下

produce 0 at 2021-11-10 11:35:43.4634 +0800 CST m=+0.000095155
produce 1 at 2021-11-10 11:35:43.463613 +0800 CST m=+0.000307367
produce 2 at 2021-11-10 11:35:43.463618 +0800 CST m=+0.000312590
produce 3 at 2021-11-10 11:35:43.463621 +0800 CST m=+0.000315444
produce 4 at 2021-11-10 11:35:43.463623 +0800 CST m=+0.000317893
produce 5 at 2021-11-10 11:35:43.463626 +0800 CST m=+0.000320241

consume 2 at 2021-11-10 11:35:45.467221 +0800 CST m=+2.003891597
consume 1 at 2021-11-10 11:35:45.46725 +0800 CST m=+2.003920815
consume 0 at 2021-11-10 11:35:45.467194 +0800 CST m=+2.003865144
produce 6 at 2021-11-10 11:35:45.467431 +0800 CST m=+2.004101556
produce 7 at 2021-11-10 11:35:45.467519 +0800 CST m=+2.004189837
produce 8 at 2021-11-10 11:35:45.467532 +0800 CST m=+2.004203272

consume 4 at 2021-11-10 11:35:47.472159 +0800 CST m=+4.008806751
consume 3 at 2021-11-10 11:35:47.472227 +0800 CST m=+4.008873837
produce 9 at 2021-11-10 11:35:47.472252 +0800 CST m=+4.008899632
consume 5 at 2021-11-10 11:35:47.472196 +0800 CST m=+4.008842888
produce 10 at 2021-11-10 11:35:47.472335 +0800 CST m=+4.008982669
produce 11 at 2021-11-10 11:35:47.472393 +0800 CST m=+4.009040183

consume 8 at 2021-11-10 11:35:49.472462 +0800 CST m=+6.009085066
produce 12 at 2021-11-10 11:35:49.472487 +0800 CST m=+6.009110372
consume 6 at 2021-11-10 11:35:49.47247 +0800 CST m=+6.009093455
produce 13 at 2021-11-10 11:35:49.472509 +0800 CST m=+6.009131934
consume 7 at 2021-11-10 11:35:49.472481 +0800 CST m=+6.009104714
produce 14 at 2021-11-10 11:35:49.472526 +0800 CST m=+6.009149533

consume 11 at 2021-11-10 11:35:51.473476 +0800 CST m=+8.010075752
produce 15 at 2021-11-10 11:35:51.47353 +0800 CST m=+8.010129885
consume 9 at 2021-11-10 11:35:51.473534 +0800 CST m=+8.010133276
produce 16 at 2021-11-10 11:35:51.473595 +0800 CST m=+8.010194908
consume 10 at 2021-11-10 11:35:51.473523 +0800 CST m=+8.010122515
produce 17 at 2021-11-10 11:35:51.473615 +0800 CST m=+8.010214367

consume 14 at 2021-11-10 11:35:53.478403 +0800 CST m=+10.014978929
consume 12 at 2021-11-10 11:35:53.478446 +0800 CST m=+10.015022028
produce 18 at 2021-11-10 11:35:53.478453 +0800 CST m=+10.015028340
produce 19 at 2021-11-10 11:35:53.478477 +0800 CST m=+10.015053279
consume 13 at 2021-11-10 11:35:53.478425 +0800 CST m=+10.015001341

从输出可以看出来,由于通道的cap是3,并且有三个消费者,所以生产者可以很快地生产6个消息而无需阻塞(每个消费者各拿走1个,剩下的3个占满通道)。

此时,通道已满,消费者需要2秒钟才能消费完一个消息。2秒钟以后,三个消费者开始下次消费,由于通道有3个消息,所以它们可以无阻塞地各取一个,此时通道有了空间,生产者可以继续生产,直到把通道全部占满,所以生产者又生产了3个消息。

以此重复,直到主线程的消费者退出,main函数结束。

发送操作在通道的尾部插入一个元素,接收者从通道的头部移除另一个元素,符合队列这个数据结构的特征,再加上它会伴随着阻塞,所以这种实现在java中被称为阻塞队列。很显然,在golang中的实现更简单。

相关文章