学习java的多线程的时候最经典的一个例子就是生产者消费者模型的例子,最近在研究go语言协程,发现go提供的sync包中有很多和java类似的锁工具,尝试着用锁工具配合协程实现一个“消费者”和“生产者”的例子:

其实go官方文档不建议我们使用"锁"的方式来实现同步的操作,文档描述是:

“sync包提供了基本的同步基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。”

原文连接:https://studygolang.com/static/pkgdoc/pkg/sync.htm

使用“同步锁”的方式

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. var (
  8. product = 0
  9. lock sync.Mutex
  10. cond = sync.NewCond(&lock)
  11. )
  12. func producer() {
  13. for {
  14. cond.L.Lock() // 先加锁
  15. for product > 10 {
  16. fmt.Println("生产完了!")
  17. cond.Wait()
  18. }
  19. fmt.Println("生产中...", product)
  20. product += 1
  21. cond.L.Unlock()
  22. cond.Broadcast()
  23. }
  24. }
  25. func consumer() {
  26. for {
  27. cond.L.Lock()
  28. for product <= 0 {
  29. fmt.Println("消费完了!")
  30. cond.Wait()
  31. }
  32. fmt.Println("消费中...", product)
  33. product -= 1
  34. cond.L.Unlock()
  35. cond.Broadcast()
  36. }
  37. }
  38. func main() {
  39. go producer()
  40. go consumer()
  41. time.Sleep(time.Second * 60)
  42. fmt.Println("主线程结束!")
  43. }

运行:go run main.go

输出:

  1. 生产中... 0
  2. 生产中... 1
  3. 生产中... 2
  4. 生产中... 3
  5. 生产中... 4
  6. 生产中... 5
  7. 生产中... 6
  8. 生产中... 7
  9. 生产中... 8
  10. 生产中... 9
  11. 生产中... 10
  12. 生产完了!
  13. 消费中... 11
  14. 消费中... 10
  15. 消费中... 9
  16. 消费中... 8
  17. 消费中... 7
  18. 消费中... 6
  19. 消费中... 5
  20. 消费中... 4
  21. 消费中... 3
  22. 消费中... 2
  23. 消费中... 1
  24. 消费完了!
  25. ...

可以看到输出符合我们的预期。

其实官方可给我们说了,使用锁是比较低级的一种方式,因为go天然就支持协程,在协程的情况下,我们还是用同步锁其实有点浪费协程的优势,按照官方的推荐使用channel来进行协程之间的通讯,实现类似的功能:

使用channel方式实现

直接上代码:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. /*
  7. 使用channel完成消费者、生产者的例子,发现使用channel会非常的方便
  8. */
  9. func producer(intChan chan int) {
  10. for i := 0; i < cap(intChan); i++ {
  11. fmt.Println("生产者:", i)
  12. intChan <- i
  13. }
  14. // 写完后关闭掉
  15. close(intChan)
  16. }
  17. func consumer(intChan chan int, exitChan chan bool) {
  18. for {
  19. v, ok := <-intChan
  20. if ok {
  21. fmt.Println("消费者:", v)
  22. } else { // 读完了
  23. break
  24. }
  25. time.Sleep(time.Second)
  26. }
  27. exitChan <- true
  28. close(exitChan)
  29. }
  30. func main() {
  31. intChan := make(chan int, 10) // “生产者”和“消费者”之间互相通信的桥梁,这里假设生产的元素就是int类型的数字
  32. exitChan := make(chan bool, 1) // 退出的channel,因为仅做为一个标志所以空间为一个元素就够了
  33. go producer(intChan)
  34. go consumer(intChan, exitChan)
  35. // 1) for循环的等待判断
  36. // for {
  37. // _, ok := <-exitChan
  38. // if !ok {
  39. // break
  40. // }
  41. // }
  42. // 2) for range 阻塞,等待关闭close channel
  43. for ok := range exitChan {
  44. fmt.Println(ok)
  45. }
  46. fmt.Println("主线程结束!")

执行:go run main.go

输出:

  1. 生产者: 0
  2. 生产者: 1
  3. 生产者: 2
  4. 生产者: 3
  5. 生产者: 4
  6. 消费者: 0
  7. 生产者: 5
  8. 生产者: 6
  9. 生产者: 7
  10. 生产者: 8
  11. 生产者: 9
  12. 消费者: 1
  13. 消费者: 2
  14. 消费者: 3
  15. 消费者: 4
  16. 消费者: 5
  17. 消费者: 6
  18. 消费者: 7
  19. 消费者: 8
  20. 消费者: 9
  21. true
  22. 主线程结束!

channel在没有被关闭的时候被遍历,此时会被当前线程阻塞,利用这个特性来实现同步的效果,更加的灵活和方便。

看到这里可能有的小伙伴会有疑问了,既然channel可以解决使用同步锁的阻塞问题,但是你使用了channel还是会阻塞啊,这不是很矛盾么?

说的没错,是的,使用channel可以方便的实现了同步锁的功能,但是我们的程序其实因为同步的关系目前仍然还是会产生阻塞,不过既然go官方文档说了使用"锁"在go语言中是低级操作,那么官方肯定提供另外一种优雅的遍历的不阻塞的方法,是的,就是这样,这里我们引入一个关键字select,这个关键字的存在就是为了解决我们的疑问的。

使用select解决阻塞

还记得我们上面代码中被注释掉的for循环么,就是准备为select的登场使用的。话不多说,上代码:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. /*
  7. 使用channel完成消费者、生产者的例子,发现使用channel会非常的方便
  8. */
  9. func producer(intChan chan int) {
  10. for i := 0; i < cap(intChan); i++ {
  11. fmt.Println("生产者:", i)
  12. intChan <- i
  13. }
  14. // 写完后关闭掉
  15. close(intChan)
  16. }
  17. func consumer(intChan chan int, exitChan chan bool) {
  18. for {
  19. v, ok := <-intChan
  20. if ok {
  21. fmt.Println("消费者:", v)
  22. } else { // 读完了
  23. break
  24. }
  25. time.Sleep(time.Second)
  26. }
  27. exitChan <- true
  28. close(exitChan)
  29. }
  30. func main() {
  31. intChan := make(chan int, 10)
  32. exitChan := make(chan bool, 1)
  33. go producer(intChan)
  34. go consumer(intChan, exitChan)
  35. // 1) for循环的等待判断
  36. for {
  37. // _, ok := <-exitChan
  38. // if !ok {
  39. // break
  40. // }
  41. select {
  42. case _, ok := <-exitChan:
  43. if ok {
  44. fmt.Println("执行完毕!")
  45. break
  46. }
  47. default:
  48. fmt.Println("读不到,执行其他的!")
  49. time.Sleep(time.Second) // 此处添加Sleep才会看到效果,否则打印太多了找不到输出
  50. // break // break只是跳出select循环,可配合lable跳出
  51. // return
  52. }
  53. }
  54. // 2) for range 阻塞,等待关闭close channel
  55. // for ok := range exitChan {
  56. // fmt.Println(ok)
  57. // }
  58. fmt.Println("主线程结束!")
  59. }

执行:go run main.go

输出:

  1. 读不到,执行其他的!
  2. 生产者: 0
  3. 生产者: 1
  4. 生产者: 2
  5. 生产者: 3
  6. 生产者: 4
  7. 生产者: 5
  8. 生产者: 6
  9. 生产者: 7
  10. 生产者: 8
  11. 生产者: 9
  12. 消费者: 0
  13. 读不到,执行其他的!
  14. 消费者: 1
  15. 读不到,执行其他的!
  16. 消费者: 2
  17. 读不到,执行其他的!
  18. 消费者: 3
  19. 读不到,执行其他的!
  20. 消费者: 4
  21. 读不到,执行其他的!
  22. 消费者: 5
  23. 读不到,执行其他的!
  24. 消费者: 6
  25. 读不到,执行其他的!
  26. 消费者: 7
  27. 读不到,执行其他的!
  28. 消费者: 8
  29. 读不到,执行其他的!
  30. 消费者: 9
  31. 读不到,执行其他的!
  32. 执行完毕!

我们看到,当前如果没有完成的话不会阻塞,可以继续执行其他的业务逻辑,真正做到了"非阻塞",由此可见go语言的一些特性还是灰常好用的。

参考文献:

go中文社区:https://studygolang.com/

go语言实现"生产者"和"消费者"的例子的更多相关文章

  1. [Spark][kafka]kafka 生产者,消费者 互动例子

    [Spark][kafka]kafka 生产者,消费者 互动例子 # pwd/usr/local/kafka_2.11-0.10.0.1/bin 创建topic:# ./kafka-topics.sh ...

  2. JAVA并发框架之Semaphore实现生产者与消费者模型

    分类: Java技术      锁和信号量(Semaphore)是实现多线程同步的两种常用的手段.信号量需要初始化一个许可值,许可值可以大于0,也可以小于0,也可以等于0.      如果大于0,表示 ...

  3. Thread(生产者和消费者) wait、notify、notifyAll

    在java中,线程间的通信可以使用wait.notify.notifyAll来进行控制.从名字就可以看出来这3个方法都是跟多线程相关的,但是可能让你感到吃惊的是:这3个方法并不是Thread类或者是R ...

  4. java线程(2)——模拟生产者与消费者

    前言: 我们都听说过生产者和消费者的例子吧,现在来模拟一下.生产者生产面包,消费者消费面包.假定生产者将生成出来的面包放入篮子中,消费者从篮子中取.这样,当篮子中没有面包时,消费者不能取.当篮子满了以 ...

  5. 【C# Task】System.Threading.Channels 生产者和消费者模式

    前言 今天给大家分享一个微软官方的生产者/消费者方案的特性解决:Channel. Channel在% dotnet add package System.Threading.Channels 而在Co ...

  6. 通过生产者消费者模式例子讲解Java基类方法wait、notify、notifyAll

    wait(),notify()和notifyAll()都是Java基类java.lang.Object的方法. 通俗解释wait():在当前线程等待其它线程唤醒.notify(): 唤醒一个线程正在等 ...

  7. Windows下RabbitMQ 的下载、配置、Java实现生产者和消费者例子

    RabbitMQ是一个轻量级的消息代理中间件,支持多种消息通信协议,支持分布式部署,支持运行于多个操作系统,具有灵活.高可用等特性.RabbitMQ支持多种协议,其中最为重要的是高级消息队列协议(AM ...

  8. 使用LinkedBlockingQueue来实现生产者消费者的例子

    工作中,经常有将文件中的数据导入数据库的表中,或者将数据库表中的记录保存到文件中.为了提高程序的处理速度,可以设置读线程和写线程,这些线程通过消息队列进行数据交互.本例就是使用了LinkedBlock ...

  9. JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池

    /** * 多线程共享数据 * 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行. * 多线程共享数据的安全问题,使用同步解决. * 线程同步两 ...

随机推荐

  1. Redis学习笔记(十一) 服务器

    Redis服务器负责与多个客户端建立网络通信,处理客户端发送的命令请求,在数据库中保存客户端执行命令所产生的数据,并通过资源管理来维持服务器自身的运转. 命令请求过程 以set命令为例 1.客户端向服 ...

  2. Spring IoC createBean 方法详解

    前言 本篇文章主要分析 Spring IoC 的 createBean() 方法的流程,以及 bean 的生命周期. 下面是一个大致的流程图: 正文 AbstractAutowireCapableBe ...

  3. 201771010128王玉兰《面向对象与程序设计(Java)》第十七周学习总结

    第一部分:理论基础 线程的同步 多线程并发运行不确定性问题解决方案:引入线 程同步机制,使得另一线程要使用该方法,就只 能等待. 在Java中解决多线程同步问题的方法有两种: - Java SE 5. ...

  4. E. Alternating Tree 树点分治|树形DP

    题意:给你一颗树,然后这颗树有n*n条路径,a->b和b->a算是一条,然后路径的权值是 vi*(-1)^(i+1)  注意是点有权值. 从上头往下考虑是点分治,从下向上考虑就是树形DP, ...

  5. PAT-1060 Are They Equal (科学计数法)

    1060. Are They Equal  If a machine can save only 3 significant digits, the float numbers 12300 and 1 ...

  6. go语言依赖注入实现

    最近做项目中,生成对象还是使用比较原始的New和简单工厂的方式,使用过程中感觉不太爽快(依赖紧密,有点改动就比较麻烦),还是比较喜欢使用依赖注入的方式. 然后网上没有找到比较好用的依赖注入包,就自己动 ...

  7. 二,表格<table>的使用细节

    如有不足请不吝赐教!最先接触的布局表格,表格灵活性,加载速度都不如<div>灵活,但是对于萌新来说再定位某些,例如div里面套文字,而刚接触html+css又不懂得那么多使用<spa ...

  8. mysql排序,同样的语句 查询结果不一样的问题

    一个项目,某段数据顺序出现异常导致运行异常.早期没有问题,用开发版本也没有问题,同样的查询排序语句在mysql客户端执行也没有问题. 这个排序不一致问题,这里记录下. 如下查询语句,根据‘order’ ...

  9. Android调试非常有用的命令集1_adb&aapt&git&repo&scp&while

    Linux部分场景非常有用的命令集_1_持续更新 这里面也包含了对于开发调试有用的命令,也可以看看. 这里不做详细说明或截图,仅作为记录和简单说明.注:可能只针对某一命令部分功能,不包含整个功能,若要 ...

  10. Java中的集合(十四) Map的实现类LinkedHashMap

    Java中的集合(十四) Map的实现类LinkedHashMap 一.LinkedHashMap的简介 LinkedHashMap是Map接口的实现类,继承了HashMap,它通过重写父类相关的方法 ...