使用 sync.Cond 来协调并发 goroutine 的访问共享资源

简介: 使用 sync.Cond 来协调并发 goroutine 的访问共享资源


使用 sync.Cond 解决并发访问共享资源问题


在并发编程中,当多个 goroutine 需要访问共享资源时,我们需要使用一些机制来协调它们的执行顺序,以避免竞态条件和数据不一致的问题。在 Go 语言中,sync.Cond 条件变量就是一种常用的机制,它可以用来等待和通知其他 goroutine


sync.Cond 和互斥锁的区别


互斥锁(sync.Mutex)用于保护临界区和共享资源,而 sync.Cond 则用于协调多个 goroutine 的执行顺序。互斥锁只能一个 goroutine 持有锁,其他 goroutine 必须等待锁被释放才能继续执行。而 sync.Cond 可以让等待的 goroutine 在条件满足时被唤醒,进而继续执行。


sync.Cond 的四个方法


sync.Cond 的定义如下:


// Each Cond has an associated Locker L (often a *Mutex or *RWMutex),
// which must be held when changing the condition and
// when calling the Wait method.
//
// A Cond must not be copied after first use.
type Cond struct {
        noCopy noCopy
        // L is held while observing or changing the condition
        L Locker
        notify  notifyList
        checker copyChecker
}


每个 Cond 实例都会关联一个锁 L(互斥锁 *Mutex,或读写锁 *RWMutex),当修改条件或者调用 Wait 方法时,必须加锁。


1. NewCond 创建实例


func NewCond(l Locker) *Cond


NewCond 方法用于创建一个 Cond 实例,并关联一个锁(互斥锁或读写锁)。


2. Broadcast 广播唤醒所有等待的 goroutine


// Broadcast wakes all goroutines waiting on c.
//
// It is allowed but not required for the caller to hold c.L
// during the call.
func (c *Cond) Broadcast()


Broadcast 方法用于唤醒所有等待条件变量 cgoroutine。它不需要持有锁来调用。


3. Signal 唤醒一个等待的 goroutine


// Signal wakes one goroutine waiting on c, if there is any.
//
// It is allowed but not required for the caller to hold c.L
// during the call.
func (c *Cond) Signal()


Signal 方法用于唤醒一个等待条件变量 cgoroutine。它不需要持有锁来调用。


4. Wait 等待条件变量满足


// Wait atomically unlocks c.L and suspends execution
// of the calling goroutine. After later resuming execution,
// Wait locks c.L before returning. Unlike in other systems,
// Wait cannot return unless awoken by Broadcast or Signal.
//
// Because c.L is not locked when Wait first resumes, the caller
// typically cannot assume that the condition is true when
// Wait returns. Instead, the caller should Wait in a loop:
//
//    c.L.Lock()
//    for !condition() {
//        c.Wait()
//    }
//    ... make use of condition ...
//    c.L.Unlock()
//
func (c *Cond) Wait()


Wait 方法会自动释放锁,并挂起当前的 goroutine,直到条件变量 cBroadcastSignal 唤醒。被唤醒后,Wait 方法会重新获得锁,并继续执行后续的代码。


使用示例


下面是一个使用 sync.Cond 的示例,实现了一个简单的读写同步机制:


package main
import (
    "fmt"
    "sync"
    "time"
)
var done = false
func read(str string, c *sync.Cond) {
    c.L.Lock()
    for !done {
        c.Wait()
    }
    fmt.Println(str, "start reading")
    c.L.Unlock()
}
func write(str string, c *sync.Cond) {
    fmt.Println(str, "start writing")
    time.Sleep(2 * time.Second)
    c.L.Lock()
    done = true
    c.L.Unlock()
    fmt.Println(str, "wake up all")
    c.Broadcast()
}
func main() {
    m := &sync.Mutex{}
    c := sync.NewCond(m)
    go read("reader1", c)
    go read("reader2", c)
    write("writer", c)
    time.Sleep(5 * time.Second)
}


在这个示例中,有两个读取协程(reader1reader2)和一个写入协程(writer)。写入协程在执行后会通知所有等待的读取协程,读取协程在条件满足时才能开始读取。


输出结果如下:


writer start writing
writer wake up all
reader2 start reading
reader1 start reading


通过使用 sync.Cond,我们可以很方便地实现多个 goroutine 之间的等待和通知机制,从而更好地协调并发访问共享资源的执行顺序。

相关文章
|
2月前
|
算法 Java
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
该博客文章综合介绍了Java并发编程的基础知识,包括线程与进程的区别、并发与并行的概念、线程的生命周期状态、`sleep`与`wait`方法的差异、`Lock`接口及其实现类与`synchronized`关键字的对比,以及生产者和消费者问题的解决方案和使用`Condition`对象替代`synchronized`关键字的方法。
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
|
27天前
|
数据采集 消息中间件 并行计算
进程、线程与协程:并发执行的三种重要概念与应用
进程、线程与协程:并发执行的三种重要概念与应用
47 0
|
5月前
|
监控
写一个线程来监控各线程是否发生阻塞
写一个线程来监控各线程是否发生阻塞
56 0
|
Java 调度 C++
C++并发与多线程(五)互斥量,atomic、与线程池(下)
C++并发与多线程(五)互斥量,atomic、与线程池(下)
|
5月前
|
安全 算法 Linux
Linux多线程【线程互斥与同步】
Linux多线程【线程互斥与同步】
76 0
|
安全 数据安全/隐私保护
线程互斥、同步(二)
线程互斥、同步
60 1
|
安全 程序员 编译器
线程互斥、同步(一)
线程互斥、同步
81 1
|
Java C++
C++并发与多线程(五)互斥量,atomic、与线程池(上)
C++并发与多线程(五)互斥量,atomic、与线程池(上)
128 0
|
机器学习/深度学习 算法
进程的同步和互斥(下)
进程的同步和互斥(下)
|
消息中间件 调度