Go语言sync.Once和sync.Cond的实现

Written by

in

文章目录
  • Once(单次执行) 用途:确保某个操作只执行一次(如初始化配置) 核心方法:Do(f func()):保证 f只执行一次 package main import ( “fmt” “sync” ) var ( config map[string]string once sync.Once wg sync.WaitGroup ) func loadConfig() { once.Do(func() { fmt.Println(“Loading config…”) config = map[string]string{“key”: “value”} }) } func main() { for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() // 确保 goroutine 结束时减少计数器 loadConfig() }() } wg.Wait() // 等待所有 goroutine 完成 } 可以确保这个协程只进行一次
  • sync.Cond 是 golang 标准库提供的并发协调器,用于支援开放人员在指定条件下阻塞和唤醒协程的操作. Wait():释放锁并阻塞,直到被唤醒。 Signal():唤醒一个等待的 goroutine。 Broadcast():唤醒所有等待的 goroutine。
  • 目录
    • 一.sync.Once
    • 二.sync.Cond
      • 2.1 数据结构与构造器方法
      • 2.2 Cond.Wait
      • 2.3 Cond.Signal
      • 2.4 Cond.BroadCast
      • 2.5 使用案例

    Once(单次执行)

    用途:确保某个操作只执行一次(如初始化配置)

    核心方法:Do(f func()):保证 f只执行一次

    package main
     
    import (
    	"fmt"
    	"sync"
    )
     
    var (
    	config map[string]string
    	once   sync.Once
    	wg     sync.WaitGroup
    )
     
    func loadConfig() {
    	once.Do(func() {
    		fmt.Println("Loading config...")
    		config = map[string]string{"key": "value"}
    	})
    }
     
    func main() {
    	for i := 0; i < 5; i++ {
    		wg.Add(1)
    		go func() {
    			defer wg.Done() // 确保 goroutine 结束时减少计数器
    			loadConfig()
    		}()
    	}
    	wg.Wait() // 等待所有 goroutine 完成
    }

    可以确保这个协程只进行一次

    sync.Cond 是 golang 标准库提供的并发协调器,用于支援开放人员在指定条件下阻塞和唤醒协程的操作.

    • Wait():释放锁并阻塞,直到被唤醒。
    • Signal():唤醒一个等待的 goroutine。
    • Broadcast():唤醒所有等待的 goroutine。

    type Cond struct {
        // 不可以对其进行值拷贝
        noCopy noCopy
        // 一个自旋锁
        L Locker
        // 一个队列,存放阻塞的goroutine
        notify  notifyList
        checker copyChecker
    }
     
    // NewCond returns a new Cond with Locker l.
    func NewCond(l Locker) *Cond {
        return &Cond{L:l}
    }

    (1)成员变量 noCopy + checker 是一套组合拳,保证 Cond 在第一次使用后不允许被复制;

    (2)核心变量 L,一把锁,用于实现阻塞操作;

    (3)核心变量 notify,阻塞链表,分别存储了调用 Cond.Wait() 方法的次数、goroutine 被唤醒的次数、一把系统运行时的互斥锁以及链表的头尾节点.

    type notifyList struct {
        wait   uint32
        notify uint32
        lock   uintptr // key field of the mutex
        head   unsafe.Pointer
        tail   unsafe.Pointer
    }

    作用:把当前这个持有锁的goroutine,释放锁,陷入一个被动阻塞的状态,加入阻塞队列里面。

    什么时候被唤醒呢?

    当有其他的goroutine也持有这个Cond的引用,使用Signal函数的时候,会首先唤醒队首的goroutine

    通过这个机制就可以实现异步goroutine的协调,比如需要某一个goroutine实现了某一个动作,另外一个goroutine才可以继续执行的一个场景。

    使用的前置条件

    看下面的代码我们会发现,它有一个解锁的操作,所以在调用他之前,必须是加锁的状态,只有这样才可以执行,然后陷入被动阻塞的状态,阻塞唤醒之后,才会重新加锁。

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

    (1)检查 Cond 是否在使用过后被拷贝,是则 panic;

    (2)该 Cond 阻塞链表 wait 统计数加 1;

    (3)当前协程释放锁,因为接下来即将被 操作系统 park;

    (4)将当前协程包装成节点,添加到 Cond 的阻塞队列当中,并调用 park 操作将当前协程挂起;

    (5)协程被唤醒后,重新尝试获取锁.

    作用:就是唤醒队首的goroutine唤醒

    func (c *Cond) Signal() {
        c.checker.check()
        runtime_notifyListNotifyOne(&c.notify)
    }

    (1)检查 Cond 是否在首次使用后被拷贝,是则 panic;

    (2)该 Cond 阻塞链表 notify 统计数加 1;

    (3)从头开始遍历阻塞链表,唤醒一个等待时间最长的 goroutine.

    作用:就是将阻塞队列里面的所有goroutine都进行唤醒。

    func (c *Cond) Broadcast() {
        c.checker.check()
        runtime_notifyListNotifyAll(&c.notify)
    }

    (1)检查 Cond 是否在首次使用后被拷贝,是则 panic;

    (2)取 wait 值赋值给 notify;

    (3)唤醒阻塞链表所有节点.

    package main
     
    import (
    	"fmt"
    	"sync"
    	"time"
    )
     
    func main() {
    	var mu sync.Mutex
    	cond := sync.NewCond(&mu)
    	
    	// 共享状态
    	isReady := false
     
    	// 等待条件的goroutine
    	go func() {
    		fmt.Println("等待者: 等待条件满足...")
    		cond.L.Lock()
    		defer cond.L.Unlock()
    		
    		// 使用循环防止虚假唤醒
    		for !isReady {
    			cond.Wait() // 释放锁并阻塞,唤醒时会重新获得锁
    			fmt.Println("等待者: 被唤醒,检查条件")
    		}
    		fmt.Println("等待者: 条件已满足!")
    	}()
     
    	// 改变条件的goroutine
    	go func() {
    		time.Sleep(2 * time.Second) // 模拟耗时操作
    		fmt.Println("触发者: 准备改变条件...")
    		
    		cond.L.Lock()
    		isReady = true
    		cond.L.Unlock()
    		
    		fmt.Println("触发者: 发送通知")
    		cond.Signal() // 唤醒一个等待的goroutine
    	}()
     
    	time.Sleep(3 * time.Second) // 等待所有goroutine完成
    }

    到此这篇关于Go语言sync.Once和sync.Cond的实现的文章就介绍到这了,更多相关Go语言sync.Once和sync.Cond内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • Go语言标准库sync.Once使用场景及性能优化详解
    • Go语言并发编程 sync.Once
    • Go语言sync.Cond使用方法详解
    • Go语言sync.Cond基本使用及原理示例详解
    • 一文带你深入理解Go语言中的sync.Cond
    • Go语言中sync.Cond使用详解

    站内搜索