关于golang:这一次彻底搞懂-Go-Cond

49次阅读

共计 2229 个字符,预计需要花费 6 分钟才能阅读完成。

hi,大家好,我是 haohongfan。

本篇文章会从源码角度去深刻分析下 sync.Cond。Go 日常开发中 sync.Cond 可能是咱们用的较少的管制并发的伎俩,因为大部分场景下都被 Channel 代替了。还有就是 sync.Cond 应用的确也蛮简单的。

比方上面这段代码:

package main

import (
    "fmt"
    "time"
)

func main() {done := make(chan int, 1)

    go func() {time.Sleep(5 * time.Second)
        done <- 1
    }()

    fmt.Println("waiting")
    <-done
    fmt.Println("done")
}

同样能够应用 sync.Cond 来实现

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {cond := sync.NewCond(&sync.Mutex{})
    var flag bool
    go func() {time.Sleep(time.Second * 5)
        cond.L.Lock()
        flag = true
        cond.Signal()
        cond.L.Unlock()}()

    fmt.Println("waiting")
    cond.L.Lock()
    for !flag {cond.Wait()
    }
    cond.L.Unlock()
    fmt.Println("done")
}

大部分场景下应用 channel 是比 sync.Cond 不便的。不过咱们要留神到,sync.Cond 提供了 Broadcast 办法,能够告诉所有的期待者。想利用 channel 实现这个办法还是不容易的。我想这应该是 sync.Cond 惟一有用武之地的中央。

先列进去一些问题吧,能够带着这些问题来浏览本文:

  1. cond.Wait 自身就是阻塞状态,为什么 cond.Wait 须要在循环内?
  2. sync.Cond 如何触发不能复制的 panic ?
  3. 为什么 sync.Cond 不能被复制?
  4. cond.Signal 是如何告诉一个期待的 goroutine ?
  5. cond.Broadcast 是如何告诉期待的 goroutine 的?

    源码分析

cond.Wait 是阻塞的吗?是如何阻塞的?

是阻塞的。不过不是 sleep 这样阻塞的。

调用 goparkunlock 解除以后 goroutine 的 m 的绑定关系,将以后 goroutine 状态机切换为期待状态。期待后续 goready 函数时候可能复原现场。

cond.Signal 是如何告诉一个期待的 goroutine ?

  1. 判断是否有没有被唤醒的 goroutine,如果都曾经唤醒了,间接就返回了
  2. 将已告诉 goroutine 的数量加 1
  3. 从期待唤醒的 goroutine 队列中,获取 head 指针指向的 goroutine,将其重新加入调度
  4. 被阻塞的 goroutine 能够继续执行

cond.Broadcast 是如何告诉期待的 goroutine 的?

  1. 判断是否有没有被唤醒的 goroutine,如果都曾经唤醒了,间接就返回了
  2. 将期待告诉的 goroutine 数量和曾经告诉过的 goroutine 数量设置成相等
  3. 遍历期待唤醒的 goroutine 队列,将所有的期待的 goroutine 都重新加入调度
  4. 所有被阻塞的 goroutine 能够继续执行

cond.Wait 自身就是阻塞状态,为什么 cond.Wait 须要在循环内?

咱们能留神到,调用 cond.Wait 的地位,应用的是 for 的形式来调用 wait 函数,而不是应用 if 语句。

这是因为 wait 函数被唤醒时,存在虚伪唤醒等状况,导致唤醒后发现,条件仍旧不成立。因而须要应用 for 语句来循环地进行期待,直到条件成立为止。

应用中留神点

1. 不能不加锁间接调用 cond.Wait

func (c *Cond) Wait() {c.checker.check()
    t := runtime_notifyListAdd(&c.notify)
    c.L.Unlock()
    runtime_notifyListWait(&c.notify, t)
    c.L.Lock()}

咱们看到 Wait 外部会先调用 c.L.Unlock(),来先开释锁。如果调用方不先加锁的话,会触发“fatal error: sync: unlock of unlocked mutex”。对于 mutex 的应用办法,举荐浏览下《这可能是最容易了解的 Go Mutex 源码分析》

2. 为什么不能 sync.Cond 不能复制?

sync.Cond 不能被复制的起因,并不是因为 sync.Cond 外部嵌套了 Locker。因为 NewCond 时传入的 Mutex/RWMutex 指针,对于 Mutex 指针复制是没有问题的。

次要起因是 sync.Cond 外部是保护着一个 notifyList。如果这个队列被复制的话,那么就在并发场景下导致不同 goroutine 之间操作的 notifyList.wait、notifyList.notify 并不是同一个,这会导致呈现有些 goroutine 会始终梗塞。

这里有留下一个问题,sync.Cond 外部是有一段代码 check sync.Cond 是不能被复制的,上面这段代码能触发这个 panic 吗?

package main

import (
    "fmt"
    "sync"
)

func main() {cond1 := sync.NewCond(new(sync.Mutex))
    cond := *cond1
    fmt.Println(cond)
}

有趣味的能够入手尝试下,以及尝试下如何能力触发这个 panic “sync.Cond is copied”。

正文完
 0