生产者消费者问题

方法1. 用go内置的channel

代码:

  1. 主go程(main)结束,子go程随之退出。
  2. go程相当于在进程里创建的,当主go程结束后,进程也就结束了
package main

import (
	"fmt"
	"runtime"
)

func consumer(get <-chan int ){
	for j := 0; j<8; j++{
		<- get
		fmt.Printf("消费者从channel中拿出了第%d个产品\n",j)
		runtime.Gosched()	// 让出当前时间片,使输出结果更明显
	}

}
func main(){
	// 调用 runtime.GOMAXPROCS() 用来设置可以并行计算的CPU核数的最大值
	// 并返回之前的设置的值(如果之前没有设置,则返回系统默认值)
	// 设置单处理器
	runtime.GOMAXPROCS(1)
	channel_product := make(chan int,10)

	// 子go程消费者
	go consumer(channel_product)

	// 主go程做生产者
	for i := 0; i<8; i++{
		channel_product <- i
		fmt.Printf("生产者将第%d个产品放入channel中\n",i)
		runtime.Gosched()	// 让出当前时间片,使输出结果更明显
	}

}

输出结果:

case1:未设置单处理器,由于电脑是多核,且I/O操作较慢,所以可能出现一些“异常情况”)

异常情况:没来得及输出被分配的时间片就用完了

比如下面结果中消费者先拿出了第零个产品是因为生产者没来得及输出”放入“,时间片就用完了,在方法2中我将用另外一个方法解决这个问题

GOROOT=D:\Go #gosetup
GOPATH=C:\GoProject #gosetup
D:\Go\bin\go.exe build -o C:\Users\think\AppData\Local\Temp\___go_build_a_newtest_go.exe C:\GoProject\src\a_newtest.go #gosetup
C:\Users\think\AppData\Local\Temp\___go_build_a_newtest_go.exe #gosetup
消费者从channel中拿出了第0个产品
生产者将第0个产品放入channel中
生产者将第1个产品放入channel中
消费者从channel中拿出了第1个产品
生产者将第2个产品放入channel中
生产者将第3个产品放入channel中
消费者从channel中拿出了第2个产品
消费者从channel中拿出了第3个产品
消费者从channel中拿出了第4个产品
生产者将第4个产品放入channel中
生产者将第5个产品放入channel中
消费者从channel中拿出了第5个产品
生产者将第6个产品放入channel中
生产者将第7个产品放入channel中
消费者从channel中拿出了第6个产品
消费者从channel中拿出了第7个产品

Process finished with exit code 0

case2:设置单处理器

runtime.GOMAXPROCS(1)

结果正常多了,这与Golang中channel的特性有关

如果channel中无数据,len(channel)=0,则拿的goroutine会被阻塞

如果channel中数据达到了cap(channel),则写的goroutine会被阻塞

GOROOT=D:\Go #gosetup
GOPATH=C:\GoProject #gosetup
D:\Go\bin\go.exe build -o C:\Users\think\AppData\Local\Temp\___go_build_a_newtest_go.exe C:\GoProject\src\a_newtest.go #gosetup
C:\Users\think\AppData\Local\Temp\___go_build_a_newtest_go.exe #gosetup
生产者将第0个产品放入channel中
消费者从channel中拿出了第0个产品
生产者将第1个产品放入channel中
消费者从channel中拿出了第1个产品
生产者将第2个产品放入channel中
消费者从channel中拿出了第2个产品
生产者将第3个产品放入channel中
消费者从channel中拿出了第3个产品
生产者将第4个产品放入channel中
消费者从channel中拿出了第4个产品
生产者将第5个产品放入channel中
消费者从channel中拿出了第5个产品
生产者将第6个产品放入channel中
消费者从channel中拿出了第6个产品
生产者将第7个产品放入channel中
消费者从channel中拿出了第7个产品

Process finished with exit code 0

方法2.使用golang中标准库sync中的Cond(条件变量)

注:这里之所以取生产者=消费者=5,是因为如果生产者数量大于消费者数量输出结果很可能会使得channel一直处于满的状态,输出结果观感不好,生产者数量小于于消费者数量同理,所以这里取生产者=消费者=5

代码:

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)
var cond_full sync.Cond			// 条件变量
var cond_empty sync.Cond       // 条件变量

/*
一个生产者可以不断生产
上来第一步先加锁
如果channel满了,我可以后面用wait把我加的锁解开,再把锁交给别人(消费者)
如果channel没满我就继续操作
若加锁不成功则在第一步就阻塞
*/
func producer(input chan<- int,idx int){
	// 一个生产者可以不断生产产品
	for{
		cond_empty.L.Lock()					// 条件变量对应互斥锁加锁

		for len(input) == cap(input){		// 产品区满 等待消费者消费
			cond_empty.Wait()				// 挂起当前协程, 等待条件变量满足,被消费者唤醒
		}
		product :=rand.Intn(1000)		// 产生产品(用随机数表示)
		input <- product					// 将产品写入单向读channel product中
		fmt.Printf("%dth 生产者,产生数据 %3d, 公共区剩余%d个数据\n", idx, product, len(input))
		cond_empty.L.Unlock()				// 解开互斥锁
		cond_full.Signal()					// 唤醒被阻塞的消费者,全部唤醒
		time.Sleep(time.Second)				// 休眠,给其他goroutine执行的机会

	}
}
// 消费者,同上理,这里不做赘述
func consumer(output <-chan int, idx int ) {
	for {
		cond_full.L.Lock()
		for len(output) == 0 {
			cond_full.Wait()
		}
		product := <-output
		fmt.Printf("---- %dth 消费者, 消费数据 %3d,公共区剩余%d个数据\n", idx, product, len(output))
		cond_full.L.Unlock()
		cond_empty.Signal()					// 唤醒被阻塞的消费者
		time.Sleep(time.Second)
	}
}

func main(){
	rand.Seed(time.Now().UnixNano())		// 设置随机数种子
	Exit := make(chan int)


	product := make(chan int ,10)			// 存取产品的公共区
	cond_full.L = new(sync.Mutex)
	cond_empty.L = new(sync.Mutex)
	for i:=0 ;i<5;i++{						// 生产者*5
		go producer(product , i+1)
	}

	for i:=0; i<5; i++{						// 消费者*5
		go consumer(product,i+1)
	}
	
	<- Exit									// 主协程阻塞 不结束
}

执行结果(由于是无限循环这里只取输出结果的一部分),为了显示效果省去中间一部分的输出结果

生产者=消费者=5

GOROOT=D:\Go #gosetup
GOPATH=C:\GoProject #gosetup
D:\Go\bin\go.exe build -o C:\Users\think\AppData\Local\Temp\___5go_build_a_newtest_go.exe C:\GoProject\src\a_newtest.go #gosetup
C:\Users\think\AppData\Local\Temp\___5go_build_a_newtest_go.exe #gosetup
---- 5th 消费者, 消费数据 240,公共区剩余0个数据
1th 生产者,产生数据 240, 公共区剩余1个数据
5th 生产者,产生数据 989, 公共区剩余1个数据
---- 1th 消费者, 消费数据 989,公共区剩余0个数据
2th 生产者,产生数据 895, 公共区剩余1个数据

...

2th 生产者,产生数据 643, 公共区剩余1个数据
---- 4th 消费者, 消费数据 643,公共区剩余0个数据
1th 生产者,产生数据 359, 公共区剩余1个数据
5th 生产者,产生数据 722, 公共区剩余1个数据
4th 生产者,产生数据 314, 公共区剩余2个数据
---- 1th 消费者, 消费数据 359,公共区剩余0个数据
---- 2th 消费者, 消费数据 722,公共区剩余2个数据
---- 5th 消费者, 消费数据 314,公共区剩余1个数据
3th 生产者,产生数据 941, 公共区剩余3个数据
---- 3th 消费者, 消费数据 941,公共区剩余0个数据
3th 生产者,产生数据  59, 公共区剩余1个数据

生产者=5,消费者=4

**

GOROOT=D:\Go #gosetup
GOPATH=C:\GoProject #gosetup
D:\Go\bin\go.exe build -o C:\Users\think\AppData\Local\Temp\___4go_build_a_newtest_go.exe C:\GoProject\src\a_newtest.go #gosetup
C:\Users\think\AppData\Local\Temp\___4go_build_a_newtest_go.exe #gosetup
1th 生产者,产生数据 595, 公共区剩余1个数据
3th 生产者,产生数据 477, 公共区剩余2个数据
---- 4th 消费者, 消费数据 595,公共区剩余1个数据
2th 生产者,产生数据 836, 公共区剩余2个数据
4th 生产者,产生数据 661, 公共区剩余3个数据

...
1th 生产者,产生数据 958, 公共区剩余10个数据
---- 2th 消费者, 消费数据 637,公共区剩余9个数据
3th 生产者,产生数据 699, 公共区剩余10个数据
---- 3th 消费者, 消费数据 456,公共区剩余9个数据
4th 生产者,产生数据 872, 公共区剩余10个数据
---- 1th 消费者, 消费数据 519,公共区剩余9个数据
2th 生产者,产生数据 629, 公共区剩余10个数据
---- 4th 消费者, 消费数据 725,公共区剩余9个数据
5th 生产者,产生数据 887, 公共区剩余10个数据
3th 生产者,产生数据 372, 公共区剩余10个数据
---- 1th 消费者, 消费数据 808,公共区剩余9个数据
1th 生产者,产生数据 117, 公共区剩余10个数据

生产者=4,消费者=5

---- 5th 消费者, 消费数据 704,公共区剩余0个数据
1th 生产者,产生数据 704, 公共区剩余1个数据
---- 2th 消费者, 消费数据 469,公共区剩余0个数据
3th 生产者,产生数据 469, 公共区剩余1个数据
2th 生产者,产生数据 314, 公共区剩余1个数据
---- 3th 消费者, 消费数据 314,公共区剩余0个数据
4th 生产者,产生数据 739, 公共区剩余1个数据
---- 4th 消费者, 消费数据 739,公共区剩余0个数据
---- 2th 消费者, 消费数据 867,公共区剩余0个数据
1th 生产者,产生数据 867, 公共区剩余1个数据
2th 生产者,产生数据 162, 公共区剩余1个数据
---- 5th 消费者, 消费数据 162,公共区剩余0个数据
3th 生产者,产生数据 492, 公共区剩余1个数据
---- 4th 消费者, 消费数据 492,公共区剩余0个数据
4th 生产者,产生数据 637, 公共区剩余1个数据
---- 3th 消费者, 消费数据 637,公共区剩余0个数据
2th 生产者,产生数据  75, 公共区剩余1个数据
---- 1th 消费者, 消费数据  75,公共区剩余0个数据
1th 生产者,产生数据 633, 公共区剩余1个数据
---- 3th 消费者, 消费数据 633,公共区剩余0个数据
posted @ 2020-11-16 09:26  TR_Goldfish  阅读(158)  评论(0编辑  收藏  举报