今天写个经典模式 生成者-消费者,开始之前简单梳理下, 这个模式需要注意什么,
- 生产者 - 消费者之间的桥梁 ,
- 生产者 与 消费者同进程,使用Go中的channel作为通信的桥梁,也可以说channel是个消息队列。
- 生产者 与 消费者跨进程,需要使用一个消息队列服务,比如rabbitMQ,kafka等来进行通信。
- 考虑多协程,
- Go中一般不会用线程,而是协程,在多协程的情况下 ,要考虑 同时写,造成写覆盖的情况。
废话不多说,直接上代码,
模式示例-代码
单协程版本
package main
import (
"fmt"
"sync"
"time"
)
// 消费者结构体,包含一个用于接收整数的缓冲通道
type Consumer struct {
Buffer chan int
}
// 消费者的Consume方法
// 从Buffer通道读取数据并处理
func (c *Consumer) Consume(wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case n := <-c.Buffer: // 尝试从Buffer通道读取数据
fmt.Println("consume %d", n) // 打印消费的值
default:
fmt.Println("still not receive channel message, sleep 1 second ... ")
time.Sleep(1000 * time.Millisecond)
}
}
}
// 生产者结构体,包含一个用于发送整数的缓冲通道
type Producer struct {
Buffer chan int
}
// 生产者的Produce方法
// 向Buffer通道写入整数
func (p *Producer) Produce(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 10; i++ {
p.Buffer <- i // 生产10个整数
}
}
var wg sync.WaitGroup
func main() {
wg.Add(2)
ch := make(chan int, 10)
c := Consumer{Buffer: ch}
p := Producer{Buffer: ch}
p.Produce(&wg)
c.Consume(&wg)
wg.Wait()
}
多协程版本
这部分代码和上面代码的主要差异点,就在与加锁了,mu.Lock() 和 mu.Lock()。
同时你可以观察到,
- 如果去掉锁,由于多协程造成的写覆盖 ,counter的值比预期偏小。
- 加锁后,写入的时间 time cost 由于锁住的原因,会变大。
package main
import (
"fmt"
"sync"
"time"
)
var counter int = 0
var mu sync.Mutex
type Consumer struct {
Buffer chan int
}
func (c *Consumer) Consume(wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case n := <-c.Buffer:
if n == -1 {
fmt.Println("receive -1, stop consume")
return
} else {
fmt.Println("consume ", n)
}
default:
fmt.Println("still not receive channel message, sleep 1 second ... ")
time.Sleep(1 * time.Second)
}
}
}
type Producer struct {
Buffer chan int
}
func (p *Producer) Produce(wg *sync.WaitGroup) {
defer wg.Done()
startTime := time.Now()
for i := 0; i < 10000; i++ {
// counter++ 是非原子性操作,
// 其可以拆解为 counter = counter + 1
// 多个协程同时修改counter变量时,可能读到相同的counter,做完累加后再写回。
// 比如 当前counter为1, 两个协程同时操作counter++后,预期counter应该为3,但实际可能发生如下情况
// 协程1读到 counter = 1,
// 协程2读到 counter = 1,
// 协程1累加后写回 counter = 2,
// 协程2累加后写回 counter = 2
// 最终counter = 2,而不是预期的3
// 为了解决这个问题,可以使用互斥锁来保护临界区
// 保护临界区
mu.Lock()
counter++
mu.Unlock()
p.Buffer <- i
}
endTime := time.Now()
fmt.Println("time cost: ", endTime.Sub(startTime))
}
func main() {
var wg sync.WaitGroup
fmt.Println("start")
ch := make(chan int, 10000000)
defer close(ch)
c := Consumer{Buffer: ch}
p := Producer{Buffer: ch}
// 启动协程1 开始生产
wg.Add(1)
go p.Produce(&wg)
// 启动协程2 开始生产
wg.Add(1)
go p.Produce(&wg)
wg.Add(1)
go c.Consume(&wg)
// 等待生产和消费完毕
time.Sleep(3 * time.Second)
// 生产者生产完毕后,向通道发送-1,通知消费者停止消费
ch <- -1
wg.Wait()
fmt.Println("produce counter ", counter)
fmt.Println("end")
}