当前位置: 首页 > article >正文

Go 语言 sync 包使用教程

Go 语言 sync 包使用教程

Go 语言的 sync 包提供了基本的同步原语,用于在并发编程中协调 goroutine 之间的操作。

1. 互斥锁 (Mutex)

互斥锁用于保护共享资源,确保同一时间只有一个 goroutine 可以访问。

特点:

  • 最基本的同步原语,实现互斥访问共享资源
  • 有两个方法:Lock()Unlock()
  • 不可重入,同一个 goroutine 重复获取会导致死锁
  • 没有超时机制,锁定后必须等待解锁
  • 不区分读写操作,所有操作都是互斥的
  • 适用于共享资源竞争不激烈的场景
  • 性能高于 channel 实现的互斥机制
  • 不保证公平性,可能导致饥饿问题
import ("fmt""sync""time"
)func main() {var mutex sync.Mutexcounter := 0for i := 0; i < 1000; i++ {go func() {mutex.Lock()defer mutex.Unlock()counter++}()}time.Sleep(time.Second)fmt.Println("计数器:", counter)
}

2. 读写锁 (RWMutex)

当多个 goroutine 需要读取而很少写入时,读写锁比互斥锁更高效。

特点:

  • 针对读多写少场景优化的锁
  • 提供四个方法:RLock()RUnlock()Lock()Unlock()
  • 允许多个读操作并发进行,但写操作是互斥的
  • 写锁定时,所有读操作都会被阻塞
  • 有读锁定时,写操作会等待所有读操作完成
  • 写操作优先级较高,防止写饥饿
  • 内部使用 Mutex 实现
  • 比 Mutex 有更多开销,但在读多写少场景下性能更高
var rwMutex sync.RWMutex
var data map[string]string = make(map[string]string)// 读取操作
func read(key string) string {rwMutex.RLock()defer rwMutex.RUnlock()return data[key]
}// 写入操作
func write(key, value string) {rwMutex.Lock()defer rwMutex.Unlock()data[key] = value
}

3. 等待组 (WaitGroup)

等待组用于等待一组 goroutine 完成执行。

特点:

  • 用于协调多个 goroutine 的完成
  • 提供三个方法:Add()Done()Wait()
  • Add() 增加计数器,参数可为负数
  • Done() 等同于 Add(-1),减少计数器
  • Wait() 阻塞直到计数器归零
  • 计数器不能变为负数,会导致 panic
  • 可以重用,计数器归零后可以再次增加
  • 非常适合"扇出"模式(启动多个工作 goroutine 并等待全部完成)
  • 不包含工作内容信息,仅表示完成状态
  • 轻量级,开销很小
func main() {var wg sync.WaitGroupfor i := 0; i < 5; i++ {wg.Add(1) // 增加计数器go func(id int) {defer wg.Done() // 完成时减少计数器fmt.Printf("工作 %d 完成\n", id)}(i)}wg.Wait() // 等待所有 goroutine 完成fmt.Println("所有工作已完成")
}

4. 一次性执行 (Once)

Once 确保一个函数只执行一次,无论有多少 goroutine 尝试执行它。

特点:

  • 确保某个函数只执行一次
  • 只有一个方法:Do(func())
  • 即使在多个 goroutine 中调用也只执行一次
  • 常用于单例模式或一次性初始化
  • 内部使用互斥锁和一个标志位实现
  • 非常轻量级,几乎没有性能开销
  • 如果传入的函数 panic,视为已执行
  • 不能重置,一旦执行就不能再次执行
  • 传入不同的函数也不会再次执行
var once sync.Once
var instance *singletonfunc getInstance() *singleton {once.Do(func() {instance = &singleton{}})return instance
}

5. 条件变量 (Cond)

条件变量用于等待或宣布事件的发生。

特点:

  • 用于等待或通知事件发生
  • 需要与互斥锁结合使用:sync.NewCond(&mutex)
  • 提供三个方法:Wait()Signal()Broadcast()
  • Wait() 自动解锁并阻塞,被唤醒后自动重新获取锁
  • Signal() 唤醒一个等待的 goroutine
  • Broadcast() 唤醒所有等待的 goroutine
  • 适合生产者-消费者模式
  • 可以避免轮询,提高性能
  • 使用相对复杂,容易出错
  • 等待必须在获取锁后调用
var mutex sync.Mutex
var cond = sync.NewCond(&mutex)
var ready boolfunc main() {go producer()// 消费者mutex.Lock()for !ready {cond.Wait() // 等待条件变为真}fmt.Println("数据已准备好")mutex.Unlock()
}func producer() {time.Sleep(time.Second) // 模拟工作mutex.Lock()ready = truecond.Signal() // 通知一个等待的 goroutine// 或使用 cond.Broadcast() 通知所有等待的 goroutinemutex.Unlock()
}

6. 原子操作 (atomic)

对于简单的计数器或标志,可以使用原子操作包而不是互斥锁。

特点:

  • 底层的原子操作,无锁实现
  • 适用于简单的计数器或标志位
  • 比互斥锁性能更高,开销更小
  • 提供多种原子操作:AddLoadStoreSwapCompareAndSwap
  • 支持多种数据类型:int32、int64、uint32、uint64、uintptr 和指针
  • 可用于实现自己的同步原语
  • 不适合复杂的共享状态
  • 在多 CPU 系统上可能导致缓存一致性开销
  • Go 1.19 引入了新的原子类型
import ("fmt""sync/atomic""time"
)func main() {var counter int64 = 0for i := 0; i < 1000; i++ {go func() {atomic.AddInt64(&counter, 1)}()}time.Sleep(time.Second)fmt.Println("计数器:", atomic.LoadInt64(&counter))
}

7. Map (sync.Map)

Go 1.9 引入的线程安全的 map。

特点:

  • Go 1.9 引入的线程安全的哈希表
  • 无需额外加锁即可安全地并发读写
  • 提供五个方法:StoreLoadLoadOrStoreDeleteRange
  • 内部使用分段锁和原子操作优化性能
  • 适用于读多写少的场景
  • 不保证遍历的顺序
  • 不支持获取元素数量或判断是否为空
  • 不能像普通 map 那样直接使用下标语法
  • 性能比加锁的普通 map 更好,但单线程下比普通 map 慢
  • 内存开销较大
var m sync.Mapfunc main() {// 存储键值对m.Store("key1", "value1")m.Store("key2", "value2")// 获取值value, ok := m.Load("key1")if ok {fmt.Println("找到键:", value)}// 如果键不存在则存储m.LoadOrStore("key3", "value3")// 删除键m.Delete("key2")// 遍历所有键值对m.Range(func(key, value interface{}) bool {fmt.Println(key, ":", value)return true // 返回 false 停止遍历})
}

8. Pool (sync.Pool)

对象池用于重用临时对象,减少垃圾回收压力。

特点:

  • 用于缓存临时对象,减少垃圾回收压力
  • 提供两个方法:Get()Put()
  • 需要提供 New 函数来创建新对象
  • 对象可能在任何时候被垃圾回收,不保证存活
  • 在 GC 发生时会清空池中的所有对象
  • 不适合管理需要显式关闭的资源(如文件句柄)
  • 适合于频繁创建和销毁的对象
  • 没有大小限制,Put 总是成功的
  • 每个 P(处理器)有自己的本地池,减少竞争
  • Go 1.13 后大幅提升了性能
var bufferPool = sync.Pool{New: func() interface{} {return new(bytes.Buffer)},
}func process() {// 获取缓冲区buffer := bufferPool.Get().(*bytes.Buffer)buffer.Reset() // 清空以便重用// 使用缓冲区buffer.WriteString("hello")// 操作完成后放回池中bufferPool.Put(buffer)
}

9. 综合示例

下面是一个综合示例,展示了多个同步原语的使用:

package mainimport ("fmt""sync""time"
)type SafeCounter struct {mu sync.Mutexwg sync.WaitGroupcount int
}func main() {counter := SafeCounter{}// 启动 5 个 goroutine 增加计数器for i := 0; i < 5; i++ {counter.wg.Add(1)go func(id int) {defer counter.wg.Done()for j := 0; j < 10; j++ {counter.mu.Lock()counter.count++fmt.Printf("Goroutine %d: 计数器 = %d\n", id, counter.count)counter.mu.Unlock()// 模拟工作time.Sleep(100 * time.Millisecond)}}(i)}// 等待所有 goroutine 完成counter.wg.Wait()fmt.Println("最终计数:", counter.count)
}

最佳实践

  1. 使用 defer 解锁:确保即使发生错误也能解锁

    mu.Lock()
    defer mu.Unlock()
    
  2. 避免锁的嵌套:容易导致死锁

  3. 保持临界区简短:锁定时间越短越好

  4. 基准测试比较

    • 对于大多数简单操作,atomic 比 Mutex 快
    • RWMutex 在读操作远多于写操作时优于 Mutex
    • sync.Map 在高并发下比加锁的 map 性能更好
  5. 内存对齐

    • 原子操作需要内存对齐
    • 不正确的内存对齐会严重影响性能
    • 特别是在 32 位系统上使用 64 位原子操作
  6. 超时控制

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()done := make(chan struct{})
    go func() {// 执行可能耗时的操作mu.Lock()// ...mu.Unlock()done <- struct{}{}
    }()select {
    case <-done:// 操作成功完成
    case <-ctx.Done():// 操作超时
    }
    

相关文章:

Go 语言 sync 包使用教程

Go 语言 sync 包使用教程 Go 语言的 sync 包提供了基本的同步原语&#xff0c;用于在并发编程中协调 goroutine 之间的操作。 1. 互斥锁 (Mutex) 互斥锁用于保护共享资源&#xff0c;确保同一时间只有一个 goroutine 可以访问。 特点&#xff1a; 最基本的同步原语&#x…...

约束文件SDC常用命令

约束文件SDC常用命令 定义时钟create_clock -name CLK-period 2 [get_ports_clk]告诉工具主时钟周期是2ns(频率500MHz),从clk端口输入 输入信号延迟set_input_delay 0.5 -clock CLK [get_ports data_in]数据进芯片前,外部电路已消耗0.5ns,综合要预留这段“堵车时间”。 输出…...

信而泰PFC/ECN流量测试方案:打造智能无损网络的关键利器

导语&#xff1a; AI算力爆发的背后&#xff0c;如何保障网络“零丢包”&#xff1f; 在当今数据中心网络中&#xff0c;随着AI、高性能计算&#xff08;HPC&#xff09;和分布式存储等应用的飞速发展&#xff0c;网络的无损传输能力变得至关重要。PFC&#xff08;基于优先级的…...

golang不使用锁的情况下,对slice执行并发写操作,是否会有并发问题呢?

背景 并发问题最简单的解决方案加个锁,但是,加锁就会有资源争用,提高并发能力其中的一个优化方向就是减少锁的使用。 我在之前的这篇文章《开启多个协程,并行对struct中的每个元素操作,是否会引起并发问题?》中讨论过多协程场景下struct的并发问题。 Go语言中的slice在…...

Android 底部EditView输入时悬浮到软键盘上方

1. 修改 Activity 的 Manifest 配置 确保你的 Activity 在 AndroidManifest.xml 中有以下配置&#xff1a; <activityandroid:name".YourActivity"android:windowSoftInputMode"adjustResize|stateHidden" /> 关键点&#xff1a; adjustResize 是…...

CNN和LSTM的计算复杂度分析

前言&#xff1a;今天做边缘计算的时候&#xff0c;在评估模型性能的时候发现NPU计算的大部分时间都花在了LSTM上&#xff0c;使用的是Bi-LSTM&#xff08;耗时占比98%&#xff09;&#xff0c;CNN耗时很短&#xff0c;不禁会思考为什么LSTM会花费这么久时间。 首先声明一下实…...

UniApp 表单校验两种方式对比:命令式与声明式

目录 前言1. 实战2. Demo 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 以下主要针对Demo讲解&#xff0c;从实战中的体会 何为命令式 何为声明式 命令式的体验&#xff0c;随时都会有提交的按钮&#xff…...

【树莓派Pico FreeRTOS】-Mutex(互斥体)

Mutex(互斥体) 文章目录 Mutex(互斥体)1、硬件准备2、软件准备3、FreeRTOS的Mutex介绍4、完整示例RP2040 由 Raspberry Pi 设计,具有双核 Arm Cortex-M0+ 处理器和 264KB 内部 RAM,并支持高达 16MB 的片外闪存。 广泛的灵活 I/O 选项包括 I2C、SPI 和独特的可编程 I/O (P…...

LCR 187. 破冰游戏(python3解法)

难度&#xff1a;简单 社团共有 num 位成员参与破冰游戏&#xff0c;编号为 0 ~ num-1。成员们按照编号顺序围绕圆桌而坐。社长抽取一个数字 target&#xff0c;从 0 号成员起开始计数&#xff0c;排在第 target 位的成员离开圆桌&#xff0c;且成员离开后从下一个成员开始计数…...

【漏洞修复】为了修复ARM64 Android10系统的第三方库漏洞,将ARM64 Android16的系统库直接拷贝到Android10系统如何?

直接替换系统库的风险分析 将高版本Android&#xff08;如Android 16&#xff09;的系统库直接拷贝到低版本系统&#xff08;如Android 10&#xff09;可能会导致以下问题&#xff1a; 符号与依赖不兼容 高版本库可能依赖更高版本的NDK或Bionic libc&#xff08;Android的C库&…...

Flutter环境配置

配置环境变量 PUB_HOSTED_URLhttps://pub.flutter-io.cnFLUTTER_STORAGE_BASE_URLhttps://storage.flutter-io.cn 这个命令是用来配置 Flutter 的镜像源地址&#xff0c;主要是为了解决在中国大陆地区访问 Flutter 官方资源较慢的问题。 具体的操作如下&#xff1a; 右键点…...

centOS 7.9 65bit 修复Openssh漏洞

一、背景&#xff1a; 在使用centos 7.9 64bit版本操作系统时有扫描出如下的漏洞&#xff1a; 二、修复openssh漏洞操作 升级注意事项 (一下所有的操作默认都是root或者管理员权限&#xff0c;如果遇到权限问题每个指令以及指令组合都要在前面加sudo) 1、查看CentOS操作系统信…...

金融级密码管理器——生物特征密钥绑定方案

目录 金融级密码管理器 —— 生物特征密钥绑定方案一、模块概述与设计目标1.1 模块背景与意义1.2 设计目标二、系统架构设计2.1 系统模块划分2.2 系统架构图(Mermaid示意图)三、核心算法与安全原理3.1 生物特征数据预处理3.2 密钥生成算法3.3 安全认证与密钥绑定验证3.4 密钥…...

JDBC-添加数据

文章目录 准备数据库添加数据引入数据库依赖包 准备数据库 自行安装软件&#xff0c;利用小皮内嵌的数据 添加数据 引入数据库依赖包 结构 drivercom.mysql.cj.jdbc.Driver urljdbc:mysql://127.0.0.1:3308/yanyuuserroot passwordrootpackage com.yanyu;import java.sql.*;…...

衡石科技HENGSHI SENSE异构数据关联技术深度解析:揭秘5-8倍性能提升背后的“异构过滤“架构

引言&#xff1a;多源数据关联的行业痛点 在大数据时代&#xff0c;企业数据通常分散在多个异构系统中——关系型数据库、NoSQL、数据仓库、湖仓一体平台等。根据Forrester调研&#xff0c;超过78%的企业需要同时访问5种以上不同类型的数据源进行分析&#xff0c;但传统ETL和跨…...

基于Netlify + Localtunnel 实现本地项目“无服务器”部署上线

基于Netlify Localtunnel 实现本地项目“无服务器”部署上线 1. 先看效果图2. 实现步骤2.1 分两步走2.2 netlify 部署前端资源2.3 Localtunnel 映射 localhost 服务 3. 其它工具内网穿透工具对比4. 总结5. 参考资料 1. 先看效果图 地址&#xff1a;zqchat 2. 实现步骤 2.1 …...

C#从入门到精通(3)

目录 第九章 窗体 &#xff08;1&#xff09;From窗体 &#xff08;2&#xff09;MDI窗体 &#xff08;3&#xff09;继承窗体 第十章 控件 &#xff08;1&#xff09;控件常用操作 &#xff08;2&#xff09;Label控件 &#xff08;3&#xff09;Button控件 &…...

设计模式之创建型5种

设计模式 为什么设计模式是23种创建型 对象创建为什么设计模式是23种 设计模式之所以被归纳为23种,而非其他数量,源于GoF(Gang of Four)在1994年的系统性总结和分类。这一数量的确定并非偶然,而是基于以下核心原因: 他们遵循“大三律”(Rule of Three),即只有经过三个…...

Java + LangChain 实战入门,开发大语言模型应用!

在 Baeldung 上看到了一篇介绍基于 Java LangChain 开发大语言模型应用的基础入门文章&#xff0c;写的非常不错&#xff0c;非常适合初学者。于是&#xff0c;我抽空翻译了一下。 原文地址&#xff1a;https://www.baeldung.com/java-langchain-basics翻译&#xff1a; Java…...

el-date-picker时间范围 编辑回显后不能修改问题

el-date-picker daterange时间范围 编辑回显后不能修改 <el-form-item:label"LABELS.gplanRecordDateLabel"prop"gplanRecordDate"><el-date-pickerstyle"width: 300px"v-model"formData.gplanRecordDate"type"daterang…...

Java多线程与高并发专题—— CyclicBarrier 和 CountDownLatch 有什么异同?

引入 上一篇我们了解CountDownLatch的原理和常见用法&#xff0c;在CountDownLatch的源码注释中&#xff0c;有提到&#xff1a; 另一种典型用法是将一个问题分解为 N 个部分&#xff0c;用一个Runnable描述每个部分&#xff0c;该Runnable执行相应部分的任务并对闭锁进行倒计…...

leetcode543.二叉树的直径

当前顶点作为拐点时&#xff0c;求左子树加上右子树的高度可以求出该通过该顶点的直径大小&#xff0c;再对该顶点和左右子节点作为拐点时直径大小进行比对&#xff0c;返回最大值 缺点是递归了多次 /*** Definition for a binary tree node.* public class TreeNode {* …...

Java EE 进阶:MyBatis案例练习

表白墙 首先我们先准备一下数据库的数据 创建一个信息表 DROP TABLE IF EXISTS message_info;CREATE TABLE message_info (id INT ( 11 ) NOT NULL AUTO_INCREMENT,from VARCHAR ( 127 ) NOT NULL,to VARCHAR ( 127 ) NOT NULL,message VARCHAR ( 256 ) NOT NULL,delete_fla…...

Dubbo 全面解析:从 RPC 核心到服务治理实践

一、分布式系统与 RPC 框架概述 在当今互联网时代&#xff0c;随着业务规模的不断扩大&#xff0c;单体架构已经无法满足高并发、高可用的需求&#xff0c;分布式系统架构成为主流选择。而在分布式系统中&#xff0c;远程服务调用&#xff08;Remote Procedure Call&#xff0…...

路由选型终极对决:直连/静态/动态三大类型+华为华三思科配置差异,一张表彻底讲透!

路由选型终极对决&#xff1a;直连/静态/动态三大类型华为华三思科配置差异&#xff0c;一张表彻底讲透&#xff01; 一、路由&#xff1a;互联网世界的导航系统二、路由类型深度解析三者的本质区别 三、 解密路由表——网络设备的GPS华为&#xff08;Huawei&#xff09;华三&a…...

[微信小程序]对接sse接口

[微信小程序]对接sse接口 在uni开发中&#xff0c;在微信小程序中实现sse接口请求 相关连接 微信小程序对接SSE接口记录 uni中实现sse代码 注意的坑点 接收的并不是字符串&#xff0c;而是ArrayBuffer模拟流推送并不是流推送&#xff0c;会有data:字符扰乱推送并不是完全按照…...

01 相机标定与相机模型介绍

学完本文,您将了解不同相机模型分类、内参意义,及对应的应用代码模型 标定的意义 建模三维世界点投影到二维图像平面的过程。标定输出的是相机模型。 相机模型 相机模型可以解理解为投影模型 +...

【商城实战(72)】解锁用户评价与晒单功能开发秘籍

【商城实战】专栏重磅来袭&#xff01;这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建&#xff0c;运用 uniapp、Element Plus、SpringBoot 搭建商城框架&#xff0c;到用户、商品、订单等核心模块开发&#xff0c;再到性能优化、安全加固、多端适配&#xf…...

2025.03.27【基因分析新工具】| MAST:解锁基因表达差异分析与网络构建

文章目录 1. MAST工具简介&#xff1a;探索生物信息分析的新利器1.1 什么是MAST工具&#xff1f;1.2 MAST工具的优势1.3 MAST工具的应用场景 2. MAST的安装方法&#xff1a;轻松入门的第一步2.1 安装R语言环境2.2 安装MAST包2.3 安装依赖库 3. MAST常用命令&#xff1a;掌握数据…...

浅谈WebSocket-FLV

FLV是一种视频数据封装格式&#xff0c;这种封装被标准通信协议HTTP-FLV和RTMP协议应用。 而WebSocket-FLV是一种非标的FLV封装数据从后端发送到前端的一种方式。 在WebSocket的url请求中&#xff0c;包含了需要请求设备的视频相关信息&#xff0c;在视频数据到达时&#xff0c…...