go语言实现"生产者"和"消费者"的例子
学习java的多线程的时候最经典的一个例子就是生产者消费者模型的例子,最近在研究go语言协程,发现go提供的sync包中有很多和java类似的锁工具,尝试着用锁工具配合协程实现一个“消费者”和“生产者”的例子:
其实go官方文档不建议我们使用"锁"的方式来实现同步的操作,文档描述是:
“sync包提供了基本的同步基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。”
原文连接:https://studygolang.com/static/pkgdoc/pkg/sync.htm
使用“同步锁”的方式
package main
import (
	"fmt"
	"sync"
	"time"
)
var (
	product = 0
	lock    sync.Mutex
	cond    = sync.NewCond(&lock)
)
func producer() {
	for {
		cond.L.Lock() // 先加锁
		for product > 10 {
			fmt.Println("生产完了!")
			cond.Wait()
		}
		fmt.Println("生产中...", product)
		product += 1
		cond.L.Unlock()
		cond.Broadcast()
	}
}
func consumer() {
	for {
		cond.L.Lock()
		for product <= 0 {
			fmt.Println("消费完了!")
			cond.Wait()
		}
		fmt.Println("消费中...", product)
		product -= 1
		cond.L.Unlock()
		cond.Broadcast()
	}
}
func main() {
	go producer()
	go consumer()
	time.Sleep(time.Second * 60)
	fmt.Println("主线程结束!")
}
运行:go run main.go
输出:
生产中... 0
生产中... 1
生产中... 2
生产中... 3
生产中... 4
生产中... 5
生产中... 6
生产中... 7
生产中... 8
生产中... 9
生产中... 10
生产完了!
消费中... 11
消费中... 10
消费中... 9
消费中... 8
消费中... 7
消费中... 6
消费中... 5
消费中... 4
消费中... 3
消费中... 2
消费中... 1
消费完了!
...
可以看到输出符合我们的预期。
其实官方可给我们说了,使用锁是比较低级的一种方式,因为go天然就支持协程,在协程的情况下,我们还是用同步锁其实有点浪费协程的优势,按照官方的推荐使用channel来进行协程之间的通讯,实现类似的功能:
使用channel方式实现
直接上代码:
package main
import (
	"fmt"
	"time"
)
/*
使用channel完成消费者、生产者的例子,发现使用channel会非常的方便
*/
func producer(intChan chan int) {
	for i := 0; i < cap(intChan); i++ {
		fmt.Println("生产者:", i)
		intChan <- i
	}
	// 写完后关闭掉
	close(intChan)
}
func consumer(intChan chan int, exitChan chan bool) {
	for {
		v, ok := <-intChan
		if ok {
			fmt.Println("消费者:", v)
		} else { // 读完了
			break
		}
		time.Sleep(time.Second)
	}
	exitChan <- true
	close(exitChan)
}
func main() {
	intChan := make(chan int, 10) // “生产者”和“消费者”之间互相通信的桥梁,这里假设生产的元素就是int类型的数字
	exitChan := make(chan bool, 1) // 退出的channel,因为仅做为一个标志所以空间为一个元素就够了
	go producer(intChan)
	go consumer(intChan, exitChan)
	// 1) for循环的等待判断
	// for {
	// 	_, ok := <-exitChan
	// 	if !ok {
	// 		break
	// 	}
	// }
	// 2) for range 阻塞,等待关闭close channel
	for ok := range exitChan {
		fmt.Println(ok)
	}
	fmt.Println("主线程结束!")
执行:go run main.go
输出:
生产者: 0
生产者: 1
生产者: 2
生产者: 3
生产者: 4
消费者: 0
生产者: 5
生产者: 6
生产者: 7
生产者: 8
生产者: 9
消费者: 1
消费者: 2
消费者: 3
消费者: 4
消费者: 5
消费者: 6
消费者: 7
消费者: 8
消费者: 9
true
主线程结束!
channel在没有被关闭的时候被遍历,此时会被当前线程阻塞,利用这个特性来实现同步的效果,更加的灵活和方便。
看到这里可能有的小伙伴会有疑问了,既然channel可以解决使用同步锁的阻塞问题,但是你使用了channel还是会阻塞啊,这不是很矛盾么?
说的没错,是的,使用channel可以方便的实现了同步锁的功能,但是我们的程序其实因为同步的关系目前仍然还是会产生阻塞,不过既然go官方文档说了使用"锁"在go语言中是低级操作,那么官方肯定提供另外一种优雅的遍历的不阻塞的方法,是的,就是这样,这里我们引入一个关键字select,这个关键字的存在就是为了解决我们的疑问的。
使用select解决阻塞
还记得我们上面代码中被注释掉的for循环么,就是准备为select的登场使用的。话不多说,上代码:
package main
import (
	"fmt"
	"time"
)
/*
使用channel完成消费者、生产者的例子,发现使用channel会非常的方便
*/
func producer(intChan chan int) {
	for i := 0; i < cap(intChan); i++ {
		fmt.Println("生产者:", i)
		intChan <- i
	}
	// 写完后关闭掉
	close(intChan)
}
func consumer(intChan chan int, exitChan chan bool) {
	for {
		v, ok := <-intChan
		if ok {
			fmt.Println("消费者:", v)
		} else { // 读完了
			break
		}
		time.Sleep(time.Second)
	}
	exitChan <- true
	close(exitChan)
}
func main() {
	intChan := make(chan int, 10)
	exitChan := make(chan bool, 1)
	go producer(intChan)
	go consumer(intChan, exitChan)
	// 1) for循环的等待判断
	for {
		// _, ok := <-exitChan
		// if !ok {
		// 	break
		// }
		select {
		case _, ok := <-exitChan:
			if ok {
				fmt.Println("执行完毕!")
				break
			}
		default:
			fmt.Println("读不到,执行其他的!")
			time.Sleep(time.Second) // 此处添加Sleep才会看到效果,否则打印太多了找不到输出
			// break // break只是跳出select循环,可配合lable跳出
			// return
		}
	}
	// 2) for range 阻塞,等待关闭close channel
	// for ok := range exitChan {
	// 	fmt.Println(ok)
	// }
	fmt.Println("主线程结束!")
}
执行:go run main.go
输出:
读不到,执行其他的!
生产者: 0
生产者: 1
生产者: 2
生产者: 3
生产者: 4
生产者: 5
生产者: 6
生产者: 7
生产者: 8
生产者: 9
消费者: 0
读不到,执行其他的!
消费者: 1
读不到,执行其他的!
消费者: 2
读不到,执行其他的!
消费者: 3
读不到,执行其他的!
消费者: 4
读不到,执行其他的!
消费者: 5
读不到,执行其他的!
消费者: 6
读不到,执行其他的!
消费者: 7
读不到,执行其他的!
消费者: 8
读不到,执行其他的!
消费者: 9
读不到,执行其他的!
执行完毕!
我们看到,当前如果没有完成的话不会阻塞,可以继续执行其他的业务逻辑,真正做到了"非阻塞",由此可见go语言的一些特性还是灰常好用的。
参考文献:
go中文社区:https://studygolang.com/
go语言实现"生产者"和"消费者"的例子的更多相关文章
- [Spark][kafka]kafka 生产者,消费者 互动例子
		[Spark][kafka]kafka 生产者,消费者 互动例子 # pwd/usr/local/kafka_2.11-0.10.0.1/bin 创建topic:# ./kafka-topics.sh ... 
- JAVA并发框架之Semaphore实现生产者与消费者模型
		分类: Java技术 锁和信号量(Semaphore)是实现多线程同步的两种常用的手段.信号量需要初始化一个许可值,许可值可以大于0,也可以小于0,也可以等于0. 如果大于0,表示 ... 
- Thread(生产者和消费者) wait、notify、notifyAll
		在java中,线程间的通信可以使用wait.notify.notifyAll来进行控制.从名字就可以看出来这3个方法都是跟多线程相关的,但是可能让你感到吃惊的是:这3个方法并不是Thread类或者是R ... 
- java线程(2)——模拟生产者与消费者
		前言: 我们都听说过生产者和消费者的例子吧,现在来模拟一下.生产者生产面包,消费者消费面包.假定生产者将生成出来的面包放入篮子中,消费者从篮子中取.这样,当篮子中没有面包时,消费者不能取.当篮子满了以 ... 
- 【C# Task】System.Threading.Channels 生产者和消费者模式
		前言 今天给大家分享一个微软官方的生产者/消费者方案的特性解决:Channel. Channel在% dotnet add package System.Threading.Channels 而在Co ... 
- 通过生产者消费者模式例子讲解Java基类方法wait、notify、notifyAll
		wait(),notify()和notifyAll()都是Java基类java.lang.Object的方法. 通俗解释wait():在当前线程等待其它线程唤醒.notify(): 唤醒一个线程正在等 ... 
- Windows下RabbitMQ 的下载、配置、Java实现生产者和消费者例子
		RabbitMQ是一个轻量级的消息代理中间件,支持多种消息通信协议,支持分布式部署,支持运行于多个操作系统,具有灵活.高可用等特性.RabbitMQ支持多种协议,其中最为重要的是高级消息队列协议(AM ... 
- 使用LinkedBlockingQueue来实现生产者消费者的例子
		工作中,经常有将文件中的数据导入数据库的表中,或者将数据库表中的记录保存到文件中.为了提高程序的处理速度,可以设置读线程和写线程,这些线程通过消息队列进行数据交互.本例就是使用了LinkedBlock ... 
- JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池
		/** * 多线程共享数据 * 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行. * 多线程共享数据的安全问题,使用同步解决. * 线程同步两 ... 
随机推荐
- 改进Page Object公共方法封装(base_page)
			import os from time import sleep from selenium import webdriver from selenium.webdriver.common.by im ... 
- 快手4-5月Java岗面经
			快手面试准备 我的牛客网帖子链接:https://www.nowcoder.com/discuss/429362 一面: 基础知识 1.java基本数据类型(8种) 1.基本数据类型有哪些,各占多少位 ... 
- 14.6 kafka
			14.6 kafka 为什么用消息队列 举例 比如在一个企业里,技术老大接到boss的任务,技术老大把这个任务拆分成多个小任务,完成所有的小任务就算搞定整个任务了. 那么在执行这些小任务的时候,可能有 ... 
- Django启动
			Django启动 (一)CMD中创建启动: 1.配置好django-admin.exe环境变量,切换到项目文件夹路径 切换磁盘:>>>E: 显示文件列表:>>>di ... 
- PAT 乙级-1025 链表反转
			给定一个常数K以及一个单链表L,请编写程序将L中每K个结点反转.例如:给定L为1→2→3→4→5→6,K为3,则输出应该为3→2→1→6→5→4:如果K为4,则输出应该为4→3→2→1→5→6,即最后 ... 
- 【MySQL】如何解决分库分表遇到的自增主键的问题?
			雪花算法 Redis生成主键 
- JSP+SSM+Mysql实现的图书馆预约占座管理系统
			项目简介 项目来源于:https://gitee.com/gepanjiang/LibrarySeats 因原gitee仓库无数据库文件且存在水印,经过本人修改,现将该仓库重新上传至个人gitee仓库 ... 
- 如何同时关联多个远程仓库,实现一次 push 多站提交(github + gitee)
			这两天做了简陋轮子,主要想放到npm上, Github: canvas-components Gitee: canvas-components github 上一份,gitee 上一份.(走过路过,s ... 
- [PHP学习教程 - 类库]001.全局唯一ID(GUID)
			GUID: 即Globally Unique Identifier(全球唯一标识符) 也称作 UUID(Universally Unique IDentifier) . GUID 是一个通过特定算法产 ... 
- 自定义值类型一定不要忘了重写Equals,否则性能和空间双双堪忧
			一:背景 1. 讲故事 曾今在项目中发现有同事自定义结构体的时候,居然没有重写Equals方法,比如下面这段代码: static void Main(string[] args) { var list ... 
