我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Golang 中的 条件变量(sync.Cond)详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Golang 中的 条件变量(sync.Cond)详解

本篇文章面向的读者: 已经基本掌握Go中的 协程(goroutine)通道(channel)互斥锁(sync.Mutex)读写锁(sync.RWMutex) 这些知识。如果对这些还不太懂,可以先回去把这几个知识点解决了。

首先理解以下三点再进入正题:

  • Go中的一个协程 可以理解成一个独立的人,多个协程是多个独立的人
  • 多个协程都需要访问的 共享资源(比如共享变量) 可以理解成 多人要用的某种公共社会资源
  • 上锁 其实就是加入到某个共享资源的争抢组中上锁完成 就是从争抢组中被选出,得到了期待的共享资源;解锁 就是退出某个共享资源的争抢组。 

假如有这样一个现实场景:在一个公园中有一个公共厕所,这个厕所一次只能容纳一个人上厕所,同时这个厕所中有个放卷纸的位置,其一次只能放一卷纸,一卷纸的总长度是 5 米,而每个人上一次厕所需要用掉 1 米的纸。而当一卷纸用完后,公园管理员要负责给厕所加上一卷新纸,以便大家可以继续使用厕所。 那么对于这个单人公共厕所,大家只能排队上厕所,当每个人进到厕所的时候,当然会把厕所门锁好,以便任何人都进不来(包括管理员)。管理员若要进到厕所查看用纸情况并加卷纸,也需要排队(因为插队总是不文明对吧)。

那么怎么用 Golang 去模拟上述场景呢?

首先我们先不用 sync.Cond,看如何实现?那么请看下面这段代码:

package main
 
import (
    "fmt"
    "time"
    "sync"
)
 
var 卷纸 int
var m sync.Mutex
var wg sync.WaitGroup
 
func 上厕所(姓名 string){
    m.Lock()
    defer func(){
        m.Unlock()
        wg.Done()
    }()
    fmt.Printf("%s 进到厕所\t",姓名)
    if 卷纸 >= 1 {  // 进到厕所第一件事是看还有没有纸
        fmt.Printf("正在拉屎中...\n")
        time.Sleep(time.Second)
        卷纸 -= 1
        fmt.Printf("%s 已用完厕所,正在离开\n",姓名)
        return
    }
    fmt.Printf("发现纸用完了,无奈先离开厕所\n")
}
 
func 加厕纸(){
    m.Lock()
    defer func(){
        m.Unlock()
        wg.Done()
    }()
    fmt.Printf("公园管理员 进到厕所\t")
    if 卷纸 <= 0 { // 管理员进到厕所是看纸有没有用完
        fmt.Printf("公园管理员 正在加新纸...\n")
        time.Sleep(time.Millisecond*500)
        卷纸 = 5
        fmt.Printf("公园管理员 已加上新厕纸,正在离开\n")
    }else{
        fmt.Printf("发现纸还没用完,先离开厕所\n")
    }
}
 
func main() {
    卷纸 = 5 // 厕所一开始就准备好了一卷纸,长度5米
    要排队上厕所的人 := [...]string{"老王","小李","老张","小刘","阿明","欣欣","西西","芳芳"}
    for _,谁 := range 要排队上厕所的人 {
        wg.Add(1)
        go 上厕所(谁)
    }
    wg.Add(1)
    go 加厕纸()
    wg.Wait()
}
 

上面的代码已经能看出一些效果,但还是有问题:最后三个人因为厕纸用完,都直接离开厕所后就没有后续了?应该是他们离开厕所后再次尝试排队,直到需求解决,就离开厕所不再参与排队了,否则要不断去排队上厕所。而公园管理员呢,他要一直去排队进到厕所里看还有没有纸,而不是看一次就再也不管了。 那么请看下面的完善代码:

package main
 
import (
    "fmt"
    "sync"
    "time"
)
 
var (
    卷纸    int
    m     sync.Mutex
    wg    sync.WaitGroup
    厕所的排队 chan string
)
 
func 上厕所(姓名 string) {
    m.Lock() // 该语句的调用只说明本执行体(可理解成该姓名所指的那个人)加入到了厕所资源的争抢组中;
             // 而该语句的完成调用,才代表了从争抢组中脱颖而出,抢到了厕所;在完成调用之前,会一直阻塞在这里(可理解为这个人正在争抢中)
    defer func() {
        m.Unlock()
        wg.Done()
    }()
    fmt.Printf("%s 进到厕所\t", 姓名)
    if 卷纸 >= 1 { // 进到厕所第一件事是看还有没有纸
        fmt.Printf("正在拉屎中...\n")
        time.Sleep(time.Second)
        卷纸 -= 1
        fmt.Printf("%s 已用完厕所,正在离开\n", 姓名)
        return
    }
    fmt.Printf("发现纸用完了,无奈先离开厕所\n")
    厕所的排队 <- 姓名 // 再次加入厕所排队,期望下次可以成功如厕
}
 
func 加厕纸() {
    m.Lock()
    defer m.Unlock()
    fmt.Printf("公园管理员 进到厕所\t")
    if 卷纸 <= 0 { // 管理员进到厕所是看纸有没有用完
        fmt.Printf("公园管理员 正在加新纸...\n")
        time.Sleep(time.Millisecond * 500)
        卷纸 = 5
        fmt.Printf("公园管理员 已加上新厕纸,正在离开\n")
    } else {
        fmt.Printf("发现纸还没用完,先离开厕所\n")
    }
}
 
func main() {
    卷纸 = 5                                                                // 厕所一开始就准备好了一卷纸,长度5米
    要上厕所的人 := [...]string{"老王", "小李", "老张", "小刘", "阿明", "欣欣", "西西", "芳芳"} // 这里只是举几个人名例子,假设此处有源源不断的人去上厕所(读者可以随意改造人名来源)
    厕所的排队 = make(chan string, len(要上厕所的人))
    for _, 谁 := range 要上厕所的人 {
        厕所的排队 <- 谁
    }
    go func() { // 在这个执行体中,会不断从 厕所排队 中把人加入到 对厕所资源的争抢组中
        for 谁 := range 厕所的排队 {
            wg.Add(1)
            go 上厕所(谁)
        }
    }()
    wg.Add(1)
    go func() { // 在这个执行体中,代表公园管理员的个人时间线,他会每隔一段时间去加入争抢组进到厕所,检查纸还有没有
        for {
            time.Sleep(time.Millisecond * 1200)
            加厕纸()
        }
    }()
    wg.Wait()
}
 

上面这个代码在功能上基本是完善了,成功模拟了上述 多人上公厕 的场景。但仔细一想,这个场景其实有些地方是不合常理的:如果有个人进到厕所发现没纸,难道他会出来紧接着再去排队吗?如果排了三次五次甚至十次还是没有纸,还要这样不断地反复排队进去出来又排队?而公园管理员,要是这样不断反复排队进厕所查看,那么他这一天其他啥事都干不了。

所以更合理实际的情况应该是:如果一个人进到厕所发现没纸,他应该先去在旁边歇着或在附近干别的,当公园管理员加完纸后,会通过喇叭吆喝一声:“新纸已加上”。这样,附近所有因为没厕纸而歇着的人就会听到这个通知,此时,他们再去尝试排队进厕所;而公园管理员也不用不断去排队进厕所检查纸用完了没有,因为经过升级,厕所加装了一个功能,有一个纸用尽的报警按钮装在纸盒旁边,当上完厕所的人发现纸用完的时候,他会先按下这个报警按钮,再离开厕所。这个报警的声音在整个公园的各处都可以听到,所以管理员无论在哪里干啥,他都能收到这个纸用尽的报警信号,然后他才去进厕所加纸。

其实这种被动通知的模式就是 sync.Cond 的核心思想,它会减少资源消耗,达到更优的效果,下面就是改良为 sync.Cond 的实现代码:

package main
 
import (
    "fmt"
    "math"
    "strconv"
    "sync"
    "time"
)
 
var (
    卷纸   int
    m    sync.Mutex
    cond = sync.NewCond(&m)
)
 
func 上厕所(姓名 string) {
    m.Lock() // 该语句的调用只说明本执行体(可理解成该姓名所指的那个人)加入到了厕所资源的争抢组中;
             // 而该语句的完成调用,才代表了从争抢组中脱颖而出,抢到了厕所;在完成调用之前,会一直阻塞在这里(可理解为这个人正在争抢中)
    defer m.Unlock()
    fmt.Printf("%s 进到厕所\t", 姓名)
    for 卷纸 < 1 { // 进到厕所第一件事是看还有没有纸
        fmt.Printf("发现纸用完了,先离开厕所在附近歇息等待信号\n")
        cond.Wait() // 该语句的调用 相当于调用了 m.Unlock() 也就是退出了争抢组,而是先歇着等待纸加上的信号;
                    // 当收到纸加上的信号后,该语句会自动执行 m.Lock(),也就是会重新加入到厕所的争抢组中;
                    // 该语句的完成调用说明已经再次成功争抢到了厕所;
        fmt.Printf("%s 等到了厕纸已加的信号,并去再次抢到了厕所\t", 姓名)
    }
    fmt.Printf("正在拉屎中...\n")
    time.Sleep(time.Second)
    卷纸 -= 1
    fmt.Printf("%s 已用完厕所\t", 姓名)
    if 卷纸 < 1 { // 注意这里:在他用完厕所离开前,他需要看是不是纸已经用完了,如果用完了,就按下纸用尽的报警按钮,给公园管理员发送信号
        cond.Broadcast() // 想想,这里为什么不用 Signal() ?因为 Signal 只能通知到一个等待者,这样就有可能通知不到 公园管理员。可以试着把这里换成 Signal() 试下
        fmt.Printf("发现厕纸已用完,并按下了报警\t")
    }
    fmt.Printf("正在离开厕所\n")
}
 
func 加厕纸() {
    m.Lock()
    defer m.Unlock()
    fmt.Printf("公园管理员 进到厕所\t")
    for 卷纸 > 0 { // 管理员进到厕所是看纸有没有用完
        fmt.Printf("发现纸还没用完,先离开厕所在等纸用尽的报警消息\n")
        cond.Wait() // 如果纸没用完,就先去干其他工作,等纸用尽的报警消息
        fmt.Printf("公园管理员 等到了纸用尽的报警消息,并再次抢到了厕所\n")
    }
    fmt.Printf("公园管理员 正在加新纸...\n")
    time.Sleep(time.Millisecond * 500)
    卷纸 = 5
    cond.Broadcast() // 注意:公园管理员加完新纸后,要通过喇叭喊一声 “纸已加上” 的消息通知所有 因没纸而等待上厕所的人
    fmt.Printf("公园管理员 已加上新厕纸,并通过喇叭通知了该消息,并正在离开厕所\n")
}
 
func main() {
    卷纸 = 5  // 厕所一开始就准备好了一卷纸,长度5米
    要上厕所的人 := [...]string{"老王", "小李", "老张", "小刘", "阿明", "欣欣", "西西", "芳芳"} // 上厕所的人名模板
    go func() { // 在这个执行体中,代表厕所及厕所队列的时间线,厕所永远运营下去
        for i := 0; i < math.MaxInt; i++ { // 此循环通过编号加上上面的姓名模板来 创建源源不断 上厕所的人
            for _, 人名模板 := range 要上厕所的人 {
                谁 := 人名模板 + strconv.Itoa(i)
                go 上厕所(谁)
                time.Sleep(time.Millisecond * 500) // 平均每半秒有一个人去上厕所
            }
            fmt.Printf("\n====================>> 屏幕停止输出后,请按Enter键继续 <<====================\n\n")
            fmt.Scanln()
        }
    }()
    go func() { // 在这个执行体中,代表公园管理员的个人时间线,管理员永不退休
        for {
            // 注意:相比上个版本,此处不用再加 Sleep 函数了,因为 加厕纸() 函数中的 cond.Wait() 会在有纸的时候等待信号
            加厕纸()
        }
    }()
    end := make(chan bool)
    <-end
}
 

用了 sync.Cond 的代码显然要精简了很多,而且还节省了计算资源,只会在收到通知的时候 才去抢公共厕所,而不是不断地反复去抢公共厕所。通过这个对现实场景的模拟,我们就很容易从使用者的角度理解 sync.Cond 是什么,它的字面意思就是 “条件”,这就已经点出了这东西的核心要义,就是满足条件才执行,条件是什么,信号其实就是条件,当一个执行体收到信号之后,它才去争抢共享资源,否则就会挂起等待(这种等待底层其实会让出线程,所以这种等待并不会空耗资源),比起不断轮寻去抢资源,这种方式要节省得多。

最后留给读者一个思考的问题:就是上面最后一版的代码,为什么 当纸用完后按报警按钮通知 公园管理员 要用 sync.Broadcast() 方法去广播通知?不是只通知管理员一个人吗,单独通知他不就行了,用 sync.Signal() 为什么不行?

到此这篇关于Golang 中的 条件变量(sync.Cond)详解的文章就介绍到这了,更多相关Golang 中的 条件变量(sync.Cond)内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Golang 中的 条件变量(sync.Cond)详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Golang 中的 条件变量(sync.Cond)详解

这篇文章主要介绍了Golang 中的 条件变量(sync.Cond)详解的相关资料,需要的朋友可以参考下
2022-12-15

GO中的条件变量sync.Cond详解

条件变量是基于互斥锁的,它必须基于互斥锁才能发挥作用,条件变量的初始化离不开互斥锁,并且它的方法有点也是基于互斥锁的,这篇文章主要介绍了GO的条件变量sync.Cond,需要的朋友可以参考下
2023-01-09

GoLang并发编程中条件变量sync.Cond的使用

Go标准库提供Cond原语的目的是,为等待/通知场景下的并发问题提供支持,本文主要介绍了Go并发编程sync.Cond的具体使用,具有一定的参考价值,感兴趣的可以了解一下
2023-01-10

Go语言文档解析:sync.Cond函数实现条件变量

在Go语言中,sync包中提供了一个非常实用的工具函数——sync.Cond。本文针对该函数进行详细解析,并提供具体示例代码,以帮助读者更好地了解和应用该函数。一、什么是sync.Cond函数?在Go语言中,sync.Cond函数用于实现条
Go语言文档解析:sync.Cond函数实现条件变量
2023-11-04

Golang函数中变量作用域的详解

Golang函数中变量作用域的详解在Golang中,函数是基本的代码块,用于完成某个特定的任务或计算。函数内部定义的变量有其特定的作用域,即变量在哪些代码段中可见和可用。本文将详细讨论Golang函数中变量的作用域,并提供具体的代码示例。局
Golang函数中变量作用域的详解
2023-12-23

详解Golang函数中的变量作用域

Golang函数中的变量作用域详解在Golang中,变量的作用域指的是变量的可访问范围。了解变量的作用域对于代码的可读性和维护性非常重要。在本文中,我们将深入探讨Golang函数中的变量作用域,并提供具体的代码示例。在Golang中,变
详解Golang函数中的变量作用域
2024-01-18

详解Golang变量的定义方式

Golang变量的定义方法详解,需要具体代码示例在Golang编程语言中,变量是程序中存储和操作数据的基本单元。变量的定义是程序设计中最基础的操作之一。本文将详细介绍Golang中变量的定义方法,并提供具体的代码示例。变量的定义方法:在
详解Golang变量的定义方式
2024-01-18

详解Golang中变量的不同声明方式

详解Golang中变量的不同声明方式在Golang中,变量的声明方式多种多样,每一种声明方式都有其特点和用途。下面将详细介绍Golang中变量的不同声明方式,并附上相应的代码示例。var声明法:var 是Golang中最常见的变量声明方式之
详解Golang中变量的不同声明方式
2023-12-23

SAP 子查询中的条件变量

一行答案 - 不允许子查询。但是如果您需要处理您的场景,那么您可以执行以下操作:将整个查询封装到存储过程中,然后使用该存储过程。创建视图。创建视图是为了处理主查询或子查询。创建一个表级变量并将视图的最终结果存储在其中,然后继续在主查
2023-10-22

图解Java ReentrantLock的条件变量Condition机制

想必大家都使用过wait()和notify()这两个方法把,他们主要用于多线程间的协同处理。而RenentrantLock也支持这样条件变量的能力,而且相对于synchronized 更加强大,能够支持多个条件变量,本文就来详细说说
2022-11-13

详解Rust中的变量与常量

大多数尝试过Rust的人都希望继续使用它。但是如果你没有使用过它,你可能会想——什么是Rust,如何理解Rust中的变量与常量,感兴趣的朋友跟随小编一起看看吧
2022-11-13

Golang中变量的存储机制及运作原理详解

Golang中变量的存储机制及运作原理详解Golang作为一门高效、并发性能优越的编程语言,在变量的存储机制和运作原理方面有其独特之处。本文将针对Golang中变量的存储机制进行详细探讨,并结合具体的代码示例,帮助读者更好地理解。变量声
Golang中变量的存储机制及运作原理详解
2024-02-28

怎么理解C++11 中的线程及锁和条件变量

今天就跟大家聊聊有关怎么理解C++11 中的线程及锁和条件变量,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。线程类std::thread代表一个可执行线程,使用时必须包含头文件
2023-06-17

C++11中线程、锁和条件变量的介绍

这篇文章主要介绍“C++11中线程、锁和条件变量的介绍”,在日常操作中,相信很多人在C++11中线程、锁和条件变量的介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++11中线程、锁和条件变量的介绍”的疑
2023-06-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录