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

 更新时间:2025年07月24日 10:43:57   作者:月忆364  
本文主要介绍了Go语言sync.Once和sync.Cond的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一.sync.Once

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

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

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

2.1 数据结构与构造器方法

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
}

2.2 Cond.Wait

作用:把当前这个持有锁的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)协程被唤醒后,重新尝试获取锁.

2.3 Cond.Signal

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

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

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

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

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

2.4 Cond.BroadCast

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

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

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

(2)取 wait 值赋值给 notify;

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

2.5 使用案例

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语言中go mod vendor使用方法

    Go语言中go mod vendor使用方法

    go mod vendor的功能是将新增的依赖包自动写入当前项目的 vendor目录,下面这篇文章主要给大家介绍了关于Go语言中go mod vendor使用的相关资料,需要的朋友可以参考下
    2022-10-10
  • 使用Gin框架处理表单数据的操作步骤

    使用Gin框架处理表单数据的操作步骤

    在 Web 应用开发中,表单是用户与服务器交互的重要手段,Gin 框架对表单处理提供了高效便捷的支持,包括数据绑定、验证等功能,在本篇博客中,我们将详细介绍如何使用 Gin 框架处理表单数据,涵盖基础操作与进阶技巧,帮助初学者全面掌握表单功能,需要的朋友可以参考下
    2024-11-11
  • Golang上下文Context的常见应用场景

    Golang上下文Context的常见应用场景

    Golang context主要用于定义超时取消,取消后续操作,在不同操作中传递值。本文通过简单易懂的示例进行说明,感兴趣的可以了解一下
    2023-04-04
  • go MethodByName()不能获取私有方法的解决

    go MethodByName()不能获取私有方法的解决

    本文主要介绍了go MethodByName()不能获取私有方法的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • GoLang内存泄漏原因排查详解

    GoLang内存泄漏原因排查详解

    内存溢出是指程序在申请内存时,没有足够的内存空间供其使用,简单点说就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出出现out of memory异常
    2022-12-12
  • golang实现微信小程序商城后台系统(moshopserver)

    golang实现微信小程序商城后台系统(moshopserver)

    这篇文章主要介绍了golang实现微信小程序商城后台系统(moshopserver),本文通过截图实例代码的形式给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-02-02
  • Golang中的参数传递示例详解

    Golang中的参数传递示例详解

    参数传递是指在程序的传递过程中,实际参数就会将参数值传递给相应的形式参数,然后在函数中实现对数据处理和返回的过程,下面这篇文章主要给大家介绍了关于Golang中参数传递的相关资料,需要的朋友可以参考下。
    2017-09-09
  • 浅谈Go 语言中逃逸分析是怎么进行的

    浅谈Go 语言中逃逸分析是怎么进行的

    Go 语言的逃逸分析是在编译阶段进行的静态分析过程,用于确定变量的内存分配位置,本文就来介绍一下逃逸分析是怎么进行的,具有一定的参考价值,感兴趣的可以了解一下
    2025-10-10
  • VSCode配置Go插件和第三方拓展包的详细教程

    VSCode配置Go插件和第三方拓展包的详细教程

    这篇文章主要介绍了VSCode配置Go插件和第三方拓展包的详细教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • Golang使用cobra实现命令行程序的示例代码

    Golang使用cobra实现命令行程序的示例代码

    Cobra 是 Go 语言中一个强大的命令行应用库,它提供了创建命令行工具所需的基本结构和功能,被许多开发者用于构建各种命令行工具和应用程序,本文将给大家介绍Golang使用cobra实现命令行程序,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2024-02-02

最新评论