vlambda博客
学习文章列表

Go语言多线程同步与生产者消费问题详细解读!

哈喽,大家好,我是 Go大叔,专注分享 Go 语言知识,一起进入 Go 的大门。

大叔和身边一群大牛都无限看好 Go 语言,现在开始搞 Go 语言,过两年大概就是第一批吃螃蟹的人。

欢迎大家来到『Go 语言入门到精通』这个专栏,今天是专栏第 25 篇,大叔主要和大家分享一下 Go语言多线程同步与生产者消费相关的知识点


多线程同步问题

  • 互斥锁
    • 互斥锁的本质是当一个goroutine访问的时候, 其它goroutine都不能访问
    • 这样就能实现资源同步, 但是在避免资源竞争的同时也降低了程序的并发性能. 程序由原来的并发执行变成了串行
  • 案例:
    • 有一个打印函数, 用于逐个打印字符串中的字符, 有两个人都开启了goroutine去打印
    • 如果没有添加互斥锁, 那么两个人都有机会输出自己的内容
    • 如果添加了互斥锁, 那么会先输出某一个的, 输出完毕之后再输出另外一个人的
package main
import (
 "fmt"
 "sync"
 "time"
)
// 创建一把互斥锁
var lock sync.Mutex

func printer(str string)  {
 // 让先来的人拿到锁, 把当前函数锁住, 其它人都无法执行
 // 上厕所关门
 lock.Lock()
 for _, v := range str{
  fmt.Printf("%c", v)
  time.Sleep(time.Millisecond * 500)
 }
 // 先来的人执行完毕之后, 把锁释放掉, 让其它人可以继续使用当前函数
 // 上厕所开门
 lock.Unlock()
}
func person1()  {
 printer("hello")
}
func person2()  {
 printer("world")
}
func main() {
 go person1()
 go person2()
 for{
  ;
 }
}

生产者消费者问题

  • 所谓的生产者消费者模型就是
    • 某个模块(函数)负责生产数据, 这些数据由另一个模块来负责处理
    • 一般生产者消费者模型包含三个部分"生产者"、"缓冲区"、"消费者"
  • 为什么生产者消费者模型要含三个部分?  直接生产和消费不行么?
  • 一个案例说明一切
    • 生产者好比现实生活中的某个人
    • 缓冲区好比现实生活中的邮箱
    • 消费者好比现实生活中的邮递员
  • 如果只有生产者和消费者, 那么相当于只有写信的人和邮递员, 那么如果将来过去的邮递员离职了, 你想邮寄信件必须想办法结识新的邮递员(消费者发生变化, 会直接影响生产者, 耦合性太强)
  • 如果在生产者和消费者之间添加一个缓冲区, 那么就好比有了邮箱, 以后邮寄信件不是找邮递员, 只需把信件投递到邮箱中即可, 写信的人不需要关心邮递员是谁(解耦)
  • 如果只有生产者和消费者, 那么每个人邮寄信件都需要直接找邮递员(1对1关系), 如果有10个人要邮寄信件, 那么邮递员只能依次找到每个人, 然后才能取件(效率低下)
  • 如果在生产者和消费者之间添加一个缓冲区, 那么所有的人只需要将信件投递到邮箱即可, 邮递员不用关心有多少人要邮寄信件, 也不用依次取件, 只需要找到邮箱从邮箱中统一取件即可(效率提高)
  • 如果只有生产者和消费者, 那么如果邮寄信件太多邮递员无法一次拿走, 这个时候非常难办
  • 如果在生产者和消费者之间添加一个缓冲区, 那么如果信件太多可以先拿走一部分, 剩下的继续放到邮箱中下次再拿
  • ... ...

生产者和消费者资源竞争问题

  • 例如生产比较慢, 而消费比较快, 就会导致消费者消费到错误数据
package main

import (
 "fmt"
 "math/rand"
 "sync"
 "time"
)
// 创建一把互斥锁
var lock = sync.Mutex{}

// 定义缓冲区
var sce []int = make([]int, 10)

// 定义生产者
func producer(){
 // 加锁, 注意是lock就是我们的锁, 全局公用一把锁
 lock.Lock()
 rand.Seed(time.Now().UnixNano())
 for i:=0;i<10;i++{
  num := rand.Intn(100)
  sce[i] = num
  fmt.Println("生产者生产了: ", num)
  time.Sleep(time.Millisecond * 500)
 }
 // 解锁
 lock.Unlock()
}
// 定义消费者
func consumer()  {
 // 加锁, 注意和生产者中用的是同一把锁
 // 如果生产者中已加过了, 则阻塞直到解锁后再重新加锁
 lock.Lock()
 for i:=0;i<10;i++{
  num := sce[i]
  fmt.Println("---消费者消费了", num)
 }
 lock.Unlock()
}

func main() {
 go producer()
 go consumer()
 for{
  ;
 }
}
  • 思考: 那如果是一对多, 或者多对多的关系, 上述代码有问题么?

一个人走的太慢,一群人才能走的更远。

最后,分享不易,喜欢大叔的文章,记得分享、点赞、在看、三连支持!