【Go - 模式示例 - 5分钟写个 生产者/消费者 】

今天写个经典模式 生成者-消费者,开始之前简单梳理下, 这个模式需要注意什么,

  • 生产者 - 消费者之间的桥梁 ,
    • 生产者 与 消费者同进程,使用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")
}

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值