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{
;
}
}
思考: 那如果是一对多, 或者多对多的关系, 上述代码有问题么?
一个人走的太慢,一群人才能走的更远。
最后,分享不易,喜欢大叔的文章,记得分享、点赞、在看
、三连支持!