如何在Golang中使用sync实现并发控制_互斥锁和条件变量

发布时间 - 2025-12-26 00:00:00    点击率:
sync.Mutex 用于保护临界区确保互斥访问,sync.Cond 需配合 Mutex 实现条件等待与通知;二者协同解决“谁可以进”和“等什么再进”的问题,使用时须遵循锁→检查→等待/修改→通知→解锁流程。

在 Go 中,sync 包提供了基础的并发原语,其中 互斥锁(sync.Mutex条件变量(sync.Cond 是控制共享资源访问、协调 goroutine 执行的关键工具。它们不单独使用,而是配合协作:Mutex 保证临界区安全,Cond 在满足特定条件时唤醒等待的 goroutine。

用 sync.Mutex 保护共享数据

Mutex 是最常用的同步机制,用于确保同一时刻只有一个 goroutine 能进入临界区。它不关心“为什么等”,只负责“谁可以进”。

  • 定义一个 sync.Mutex 字段(通常作为结构体成员),避免复制;若必须传递,传指针
  • mu.Lock() 进入临界区,mu.Unlock() 离开 —— 推荐用 defer mu.Unlock() 防止遗漏
  • 不要在持有锁时做耗时操作(如网络请求、大量计算),否则会阻塞其他 goroutine

示例:安全地累加计数器

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *Counter) Load() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

用 sync.Cond 实现条件等待与通知

sync.Cond 本身不提供互斥能力,必须和一个已有的 sync.Locker(通常是 *sync.Mutex)绑定。它解决的是“等某个条件成立再继续”的问题,比如“队列非空才取元素”或“缓冲区有空位才写入”。

  • 创建 Cond:用 sync.NewCond(&mu),其中 &mu 是指向 Mutex 的指针
  • 等待条件:先加锁,检查条件是否满足;不满足则调用 cond.Wait() —— 它会自动释放锁并挂起 goroutine;被唤醒后自动重新加锁,所以需再次检查条件(防止虚假唤醒)
  • 通知等待者:用 cond.Signal() 唤醒一个,或 cond.Broadcast() 唤醒全部;通知前通常要修改共享状态,并确保在锁保护下完成

示例:实现一个线程安全的阻塞队列

type BlockingQueue struct {
    mu       sync.Mutex
    cond     *sync.Cond
    items    []int
    capacity int
}

func NewBlockingQueue(cap int) *BlockingQueue {
    q := &BlockingQueue{
        capacity: cap,
        items:    make([]int, 0),
    }
    q.cond = sync.NewCond(&q.mu)
    return q
}

func (q *BlockingQueue) Push(x int) {
    q.mu.Lock()
    defer q.mu.Unlock()
    // 等待有空位
    for len(q.items) >= q.capacity {
        q.cond.Wait()
    }
    q.items = append(q.items, x)
    q.cond.Broadcast() // 通知可能等待读取的 goroutine
}

func (q *BlockingQueue) Pop() int {
    q.mu.Lock()
    defer q.mu.Unlock()
    // 等待非空
    for len(q.items) == 0 {
        q.cond.Wait()
    }
    x := q.items[0]
    q.items = q.items[1:]
    q.cond.Broadcast() // 通知可能等待写入的 goroutine
    return x
}

常见误区与注意事项

使用 Cond 时容易出错,关键点在于逻辑顺序和状态一致性:

  • Wait 必须在循环中调用:因为可能存在虚假唤醒(spurious wakeup),不能只靠一次判断就进入等待
  • 所有对共享状态的读写都必须在锁内完成:包括通知前的状态更新和 Wait 前的条件检查
  • Cond 不是替代 channel 的通用方案:对于简单的生产者-消费者模型,channel 更简洁、更符合 Go 的惯用法;Cond 更适合需要精细控制唤醒策略或复用锁的复杂同步场景
  • 不要复制 Cond 或 Mutex:它们包含运行时状态,复制会导致未定义行为;始终传递指针

对比 channel:何时选 Cond?

Go 推崇 “通过通信共享内存”,所以大多数场景优先用 channel。但 Cond 仍有其适用位置:

  • 多个 goroutine 等待**同一个条件**,且该条件由多个不同事件共同影响(例如:任务完成 + 资源就绪 + 权限验证通过)
  • 需要在已有锁结构上扩展等待逻辑(如封装在复杂对象内部,锁已存在)
  • 性能敏感场景下,避免 channel 的额外内存分配和调度开销(极少数情况)

简单说:能用 channel 就别硬上 Cond;需要用 Cond 时,一定配好 Mutex,且严格遵循“锁 → 检查 → 等待 / 修改 → 通知 → 解锁”流程。


# go  # golang  # app  # 工具  # ai  # 权限验证  # 同步机制  # 为什么  # 有锁  # 封装  # 结构体  # 循环  # 指针  # signal  # 线程  # 并发  # channel  # 对象  # 事件  # 多个  # 新和  # 互斥  # 解锁  # 加锁  # 的是  # 已有  # 只有一个  # 仍有  # 它会 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: JavaScript模板引擎Template.js使用详解  如何在橙子建站上传落地页?操作指南详解  Laravel如何集成Inertia.js与Vue/React?(安装配置)  活动邀请函制作网站有哪些,活动邀请函文案?  Laravel事件监听器怎么写_Laravel Event和Listener使用教程  如何解决hover在ie6中的兼容性问题  php增删改查怎么学_零基础入门php数据库操作必知基础【教程】  Laravel如何实现API速率限制?(Rate Limiting教程)  PythonWeb开发入门教程_Flask快速构建Web应用  详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点  HTML5建模怎么导出为FBX格式_FBX格式兼容性及导出步骤【指南】  Laravel如何编写单元测试和功能测试?(PHPUnit示例)  php结合redis实现高并发下的抢购、秒杀功能的实例  手机钓鱼网站怎么制作视频,怎样拦截钓鱼网站。怎么办?  Laravel如何安装使用Debugbar工具栏_Laravel性能调试与SQL监控插件【步骤】  实例解析Array和String方法  Android okhttputils现在进度显示实例代码  Bootstrap整体框架之CSS12栅格系统  企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?  lovemo网页版地址 lovemo官网手机登录  Python函数文档自动校验_规范解析【教程】  Laravel怎么写单元测试_PHPUnit在Laravel项目中的基础测试入门  如何快速搭建高效可靠的建站解决方案?  Firefox Developer Edition开发者版本入口  如何挑选高效建站主机与优质域名?  悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤  音乐网站服务器如何优化API响应速度?  黑客如何通过漏洞一步步攻陷网站服务器?  javascript中数组(Array)对象和字符串(String)对象的常用方法总结  如何生成腾讯云建站专用兑换码?  JavaScript中如何操作剪贴板_ClipboardAPI怎么用  七夕网站制作视频,七夕大促活动怎么报名?  Laravel Debugbar怎么安装_Laravel调试工具栏配置指南  Windows驱动无法加载错误解决方法_驱动签名验证失败处理步骤  如何在景安服务器上快速搭建个人网站?  iOS正则表达式验证手机号、邮箱、身份证号等  移动端脚本框架Hammer.js  如何快速搭建自助建站会员专属系统?  ,怎么在广州志愿者网站注册?  LinuxCD持续部署教程_自动发布与回滚机制  阿里云高弹*务器配置方案|支持分布式架构与多节点部署  Laravel如何实现多表关联模型定义_Laravel多对多关系及中间表数据存取【方法】  Laravel如何使用Eloquent ORM进行数据库操作?(CRUD示例)  Laravel N+1查询问题如何解决_Eloquent预加载(Eager Loading)优化数据库查询  Laravel如何实现多级无限分类_Laravel递归模型关联与树状数据输出【方法】  Laravel怎么调用外部API_Laravel Http Client客户端使用  Laravel如何使用Telescope进行调试?(安装和使用教程)  Laravel Telescope怎么调试_使用Laravel Telescope进行应用监控与调试  Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程  Windows家庭版如何开启组策略(gpedit.msc)?(安装方法)