Golang sync库--Cond

熟悉Go编程的朋友都不会对sync库感到陌生,这个语言内置库提供了很多常见的处理并发编程的工具,今天就从最为小众,使用最少的sync.Cond库说起。

Cond是什么?

按照文档的描述:

Cond implements a condition variable, a rendezvous point for goroutines waiting for or announcing the occurrence of an event.

大概意思就是Cond实现了一种条件变量,用于描述事件(event)的发生或者作为goroutine的交汇点。
听起来似乎有点抽象,下面我们使用实际代码来说明。

现在引入一个实际问题,如何当一个变量为指定值时做一些特定的行为?比如,当同时登陆用户大于100时,做一些额外的动作。

首先,我们可以在变量产生变化的时候主动做检查,看是否触发了条件(这种方法固然可行,但是不在今天的考虑范畴)。其次,我们可以通过轮询的方式,来检查值是否达到了指定的条件,这种方式的好处在于,处理变量变化的动作与额外触发的行为可以完全分隔开,有利于代码解耦。
一个简单的轮询逻辑很容易实现:

for {
    mu.RLock()
    func() {
        defer mu.RUnlock()
        if len(users) >= 100 {
            funcDoSomething()
        }
    }()
}

不出意外的,这段代码在非抢占调度下会占用一整个处理器的资源(因为是一个无限死循环且没有syscall去主动让出处理器控制器)。

一个最容易想到的简单优化是,在for循环中加上一个sleep时间,这样对于cpu的占用就会低很多。
更理想的行为是,当且仅当条件发生变化的时候(这里的条件是users长度),才做后续的工作。
使用Cond在代码实现上大概是:

cond.L.Lock()
for len(users) < 100 {
    cond.Wait()
}
funcDoSomething()
cond.L.Unlock()

// in some other goroutine:
cond.L.Lock()
users = append(users, newUser)
cond.L.Unlock()
cond.Signal()

不难发现,其实上面的代码可以通过channel做简单实现:

select{
    case <-ch:
        if len(users)>=100{
             funcDoSomething()
        }
    default:
}

// in some other goroutine:
mu.Lock()
users = append(users, newUser)
mu.Unlock()
ch<-struct{}{}

上面两段代码在逻辑上其实是等价的。这么看来,Cond并没有解决什么复杂问题嘛,怪不得大家都不关注它。其实Cond主要有用的并不是Signal模式,而是Broadcast模式。

其实在API的使用上面,SignalBroadcast并无不同。两者主要解决的场景差异在于:Signal会唤醒任意一个处于Wait状态的goroutine;而Broadcast则会唤醒所有处于Wait状态的goroutine。我们在使用channel做类似模式的时候,很容易可以通过一个send操作来模仿Signal,而Broadcast就不那么容易(直接close channel是一个方案,问题在于close动作只能发生一次,而Broadcast则可以发生多次)。

用channel实现Cond模式(约束在于Broadcast只能实现一次):

type Cond struct {
    L  sync.Mutex // used by caller
    ch chan struct{}
}

func (c *Cond) Wait() {
    ch := c.ch
    c.L.Unlock()
    <-ch
    c.L.Lock()
}

func (c *Cond) Signal() {
    select {
    case c.ch <- struct{}{}:
    default:
    }
}

func (c *Cond) Broadcast() {
    close(c.ch)
    c.ch = make(chan struct{})
}

Cond的实际实现是:

type Cond struct {
    noCopy noCopy
    // L is held while observing or changing the condition
    L Locker
    notify  notifyList
    checker copyChecker
}
func NewCond(l Locker) *Cond {
    return &Cond{L: l}
}
func (c *Cond) Wait() {
    c.checker.check()
    t := runtime_notifyListAdd(&c.notify)
    c.L.Unlock()
    runtime_notifyListWait(&c.notify, t)
    c.L.Lock()
}
func (c *Cond) Signal() {
    c.checker.check()
    runtime_notifyListNotifyOne(&c.notify)
}
func (c *Cond) Broadcast() {
    c.checker.check()
    runtime_notifyListNotifyAll(&c.notify)
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容

  • 更多精彩文章:https://deepzz.com Desc:Go sync 包的使用方法,sync.Mutex,...
    deepzz阅读 1,980评论 3 9
  • 本文从上下文Context、同步原语与锁、Channel、调度器四个方面介绍Go语言是如何实现并发的。本文绝大部分...
    彦帧阅读 1,569评论 1 3
  • 同步原语和锁 Golang作为一个原生支持用户态的语言,当提到并发进程,多线程的时候,是离不开锁的,锁是一种并发编...
    LegendGo阅读 2,241评论 0 3
  • 指针 指针就是地址,指针变量就是存储地址的变量 *p : 解引用,间接引用 栈帧:用来给函数运行提供内存空间,取内...
    雪上霜阅读 298评论 0 0
  • Select select 可见监听 Channel 上的数据流动; select 结构与 switch 的结构类...
    hellomyshadow阅读 203评论 0 0