golang多线程生产者消费者模型

1、场景描述

  生产者:多个生产者,生产速度高于消费者消费速度

  消费者:多个消费者

  数据同步:程序中止信号发出,生产者暂停生产并退出线程,消费者继续消费,直到缓存数据被消费完。

2、模型代码

package main

import (
	"fmt"
	"strconv"
	"sync"
	"time"
)

//暂停标志
var bStop = false

//模拟异常/超时等使程序停止
func makeStop(){
	time.Sleep(time.Second*4)
    bStop = true
}

//生产者
func producer(threadId int, wg *sync.WaitGroup, ch chan string){
    count := 0

    //标志位为false,不断写入数据
    for !bStop {
    	//模拟生产数据的耗时
		time.Sleep(time.Second * 2)
		count++
		data := strconv.Itoa(threadId) + "+++++++++" + strconv.Itoa(count)
		fmt.Println("producer:", data)
		ch <- data
	}

	wg.Done()
}

//消费者
func consumer(wg *sync.WaitGroup, ch chan string){

	//不断读取,直到通道关闭
	for data:= range ch {
		time.Sleep(time.Second * 2)
		fmt.Println("consumer:", data)
	}

    wg.Done()
}

func work(){
	//缓存:模拟生产者完成生产,消费者未完成消费
	chanStream := make(chan string,30)

	//生产者和消费者计数器
	wgPd := new(sync.WaitGroup)
	wgCs := new(sync.WaitGroup)

	//producer
	for i := 0; i < 3; i++ {
		wgPd.Add(1)
		go producer(i, wgPd, chanStream)
	}

	//consumer
	for j := 0; j < 2; j++ {
		wgCs.Add(1)
		go consumer(wgCs, chanStream)
	}

	go makeStop()

	wgPd.Wait()

	//生产完成,关闭通道
	close(chanStream)
	wgCs.Wait()
}

  执行结果:

producer: 1+++++++++1
producer: 2+++++++++1
producer: 0+++++++++1
producer: 0+++++++++2
consumer: 1+++++++++1
producer: 1+++++++++2
consumer: 2+++++++++1
producer: 2+++++++++2
consumer: 0+++++++++2
consumer: 0+++++++++1
consumer: 2+++++++++2
consumer: 1+++++++++2

Process finished with exit code 0

  该程序可以保证生产者数据全部被消费者消费。

  重点解析:

  消费者通过range读取数据

       golang 通道读取方式有select和range,本场景更适合range读取数据,因为for循环中,如果通道未被关闭,线程会堵塞读取,即使数据为空,除非通道被关闭,才会退出循环。

       生产者完成之后,需要关闭通道,消费者才能正常退出

wgPd.Wait()
//生产完成,关闭通道
close(chanStream)
wgCs.Wait()

  当生产完成之后,关闭通道,消费者发现通道关闭,对应线程才会退出,即生产者完成生产,通过关闭通道告诉消费者,我已完成生产,你消费完剩余数据就退出吧。

  makeStop的作用

  模拟程序收到暂停信号,或者超时信号,一般情况是通过通道来进行标志,本文通过bool来进行标志,因为通道适用于单线程select模型,本文中生产者为多线程,如果通过放多个数据(数据个数=生产者个数)到通道,也可解决,但是多维护一份数据的一致性没有必要。

        生产者数量>消费者数量以及通道加缓存

        生产者数量>消费者数量:模拟生产速度大于消费速度的情况,同时生产者和消费者加休眠时间模拟数据处理的耗时;通道加缓存是为了模拟,生产者生产了一定数量数据到通道,并停止生产,验证消费者是否将缓存数据消费。

 

posted @ 2021-04-25 11:25  Memset  阅读(1241)  评论(0编辑  收藏  举报