Condition variables in Golang difference between Signal and Broadcast

前端之家收集整理的这篇文章主要介绍了Condition variables in Golang difference between Signal and Broadcast前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

Golang 标准库中提供了sync.Mutex 用于多线程之间的同步。
同时也提供了条件变量配合Mutex结合使用。
条件变量主要的使用场景是:

当线程要执行A操作时,条件B未满足,而无法执行A操作,此时使当前线程进入睡眠状态(进入睡眠状态前会释放锁),等待条件B,当条件B出现时,发送通知唤醒因等待条件B而进入沉睡的线程。

实例说明

一条专门用于读取文件A的线程T1, 一条专门用于写入文件A的线程T2。通过锁M1做读写的同步操作。当T1获取锁M1时,读取文件A,当读取到EOF时,不希望就此结束,而是希望等待,当T2向文件A写入内容后,又可以继续读出内容。此时T1应当释放锁并且通过条件变量进入睡眠状态,然后T2获取锁,对文件进行写入。写完之后释放锁,并且发送一个通知用于唤醒T1,T1被唤醒后,试图获取锁,获取成功后继续进行读操作。

上面的例子中,通过条件变量唤醒睡眠的线程,有两种方式,Signal和Broadcast,如果是Signal会在因当前条件变量而进入睡眠的线程中随机选取一条线程唤醒,然后该线程试图获取锁,如果获取成功,则执行之后的代码逻辑,如果未获取成功则会一直等待,直到获取锁。如果是Broadcast,会将所有因当前条件变量而进入睡眠的线程全部唤醒,所有的线程一起去试图获取锁,哪一条线程先获取到,哪一条线程先执行,其余的线程则继续等待,直到上一次抢到锁的线程释放锁时,再一次开始对于锁的争抢。(注意这里的线程被条件变量唤醒之后,即使未抢到锁,也不再需要条件变量对其进行再一次的通知唤醒)

下面通过代码后打印结果来更深刻的体会

package main

import (
    "sync"
    "fmt"
    "time"
    "math/rand"
)

type T struct {
    l *sync.Mutex // 锁
    c *sync.Cond  //条件变量
}

func main() {

    wg := sync.WaitGroup{}
    wg.Add(1)
    var t *T = new(T)
    t.l = new(sync.Mutex)
    // 使用条件变量前,必须将其与一个锁绑定
    t.c = sync.NewCond(t.l)

    //启动10条协程,当协程执行conditions()发现满足条件时,打印出processed,代表该协程处理结束
    //当不满足条件时,通过条件变量进入睡眠状态,每次从睡眠状态醒来并且获取锁之后,打印出一条wait。
    for i := 0; i < 10; i++ {
        go func() {
            t.l.Lock()
            defer t.l.Unlock()
            //不满足条件时通过条件变量进入沉入
            //t.c.Wait() 首先会释放与该条件变量绑定的锁,然后在进入睡眠状态
            for !conditions() {
                t.c.Wait()
                fmt.Println("wait")
            }
            fmt.Println("processed")

        }()
    }

    // 启动一条协程 每隔两秒发送一次通知
    go func() {
        for {
            time.Sleep(time.Second * 2)
            //fmt.Println("\n\n\n\nBroadcast")
            //t.c.Broadcast()
            fmt.Println("\n\n\n\nSignal")
            t.c.Signal()
        }

    }()
    wg.Wait()

}

//生成随机数, 当生成的数为0时,则为满足条件返回true
func conditions() bool {
    i := rand.Intn(10)
    fmt.Println(i)
    if i == 0 {
        return true
    } else {
        return false
    }
}

打印结果

10条协程,在第一次执行时,两条随机出了0,处理完毕,剩余8条协程,通过条件变量进入睡眠状态。
1
7
7
9
1
8
5
0
processed
6
0
processed

过两秒之后发出第一次广播,其中一条协程获取了锁,并且打印了wait进入conditions()随机出了7,为满足条件继续通过条件变量进入睡眠。
Signal
wait
7

同上
Signal
wait
8

再一次唤醒协程,此次conditions()随机出了0满足了条件,打印出processed,处理完毕。
Signal
wait
0
processed

从上面的打印结果可以看出每一次只唤醒了一条携程

接下来将代码中的

fmt.Println("\nSignal")
t.c.Signal()

替换为

fmt.Println("\nBroadcast")
t.c.Broadcast()

再来打印输出结果进行分析

第一次打印出的结果和上面的一样主要看发送广播时的输出
1
7
7
9
1
8
5
0
processed
6
0
processed

停了两秒发送了广播,此时所有关联在条件变量的协程都被唤醒。
从结果可以看出,只发送了一次唤醒广播,所有的协程都打印了一遍wait,说明这些协程都被唤醒,并且依次获取了锁,然后执行。当然一个时间段内只能有一个协程获取到锁。
Broadcast
wait
7
wait
8
wait
0
processed
wait
5
wait
1
wait
8
wait
7
wait
1
原文链接:https://www.f2er.com/go/190141.html

猜你在找的Go相关文章