Golang最大的一个特性就是其并发的设计。所有Go程序都跑在goroutine里,main函数也是一个goroutine。一般情况下 goroutine的通信采用读写channel来完成。偶然看到一道题面试题,要求Go来实现生产者消费者模型,刚好也没手写过借此机会写一写。
概述
生产者消费者问题是经典的并发同步问题。描述有若干生产者和若干消费者,生产者负责生产产品并将产品放入盒子(缓冲区),而消费者则消费盒子的产品。
问题的关键为消费者之间、生产者之间、消费者与生产者之间的竞争,以及在“盒子”满了,应该停止(阻塞)生产者的生产,在“盒子”空了应该停止(阻塞)消费者的消费。
传统的方式,我们需要用锁,信号量等来处理,而Go并不需要,通过Go的goroutine以及channel可以很方便的实现生产者消费者模型。
定义
定义三个结构体,分别表示生产者、消费者、产品,每个结构体只定义一个属性,可自行扩展。
// 产品
type Product struct {
No string // 产品编号
}
// 生产者
type Producer struct {
No int
}
// 消费者
type Comsumer struct {
No int
}
“盒子”
“盒子”为是goroutine通信的方式,用channel
本题中定义有缓冲区和无缓冲区的channel其实都可以。因为并不会在一个goroutine中同时读写,即生产者不会进行消费,消费者不会进行生产。只是如果定义为无缓冲的channel,那每次生产一个产品就必须等消费者消费后才能继续生产,相当于盒子只能放一个产品。本次我们定义一个有缓冲的channel。
var Box = make(chan Product, 1024)
生产
func (p Producer) func work() {
for {
// 生产产品(用一个uuid作为产品的唯一编号)
product := Product{ No: uuid.NewV4().String() }
// 将产品放置盒子
Box <- product
fmt.Printf("生产者-%d 生产了 产品-%s\n", p.No, product.No)
// 休息一会
time.Sleep(time.Millisecond * 200)
}
}
消费
func (c Comsumer) func buy() {
for {
// 从盒子中取出
product := <-Box
fmt.Printf("消费者-%d 消费了 产品-%s\n", c.No, product.No)
time.Sleep(time.Millisecond * 1000)
}
}
主函数
func main() {
for i := 1; i <= 1; i++ {
p := Producer{ No: i }
go p.work()
}
for i := 1; i <= 5; i++ {
c := Consumer{ No: i }
go c.buy()
}
// 用来阻塞main函数,不让其结束
wait := make(chan struct{})
<-wait
}
执行结果
