Go语言sync包使用指南

本文围绕 Go 语言中 sync 包展开,对其各类同步原语的使用方法进行介绍。

sync.Mutex

Mutex用于实现互斥锁,用于保护多个 goroutine 并发访问的共享资源。它可以防止数据竞争,确保只有一个 goroutine 能访问临界区代码。

结构

type Mutex struct {state int32sema  uint32
}

方法

  • Lock() 获取锁
  • TryLock() 尝试获取锁
  • Unlock() 释放锁

sync.RWMutex

RWMutex是读写互斥锁,允许同时多个读操作或一次写操作(写操作为独占锁,不允许同时有其他读写操作)。

结构

type RWMutex struct {w           Mutex        // held if there are pending writerswriterSem   uint32       // semaphore for writers to wait for completing readersreaderSem   uint32       // semaphore for readers to wait for completing writersreaderCount atomic.Int32 // number of pending readersreaderWait  atomic.Int32 // number of departing readers
}

方法

  • RLock() 获取读锁
  • RUnlock() 释放读锁
  • Lock() 获取写锁
  • Unlock() 释放写锁

使用示例

// Cache 定义一个简单的缓存结构体
type Cache struct {data map[string]string// 使用读写锁来保护缓存数据rwMutex sync.RWMutex
}// Get 从缓存中获取数据
func (c *Cache) Get(key string) string {// 加读锁,允许多个读操作同时进行c.rwMutex.RLock()// 函数返回时自动释放读锁defer c.rwMutex.RUnlock()// 从缓存中获取数据return c.data[key]
}func (c *Cache) Create(book *Object) error {ms.Lock()defer ms.Unlock()//中间省略...return nil
}

sync.Once

Once用于确保某个函数只被执行一次。提供Do方法,接收一个无参数、无返回值的函数f,并保证无论有多少个gorountine同时调用Do方法,函数f都只会被调用一次。适宜单例模式、资源初始化等场景。

方法

  • func (o *Once) Do(f func()) 方法接收一个无参数、无返回值的函数 f 作为参数,该方法会确保 f 只被执行一次。

代码实现

// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
func (o *Once) Do(f func()) {// Note: Here is an incorrect implementation of Do://// if o.done.CompareAndSwap(0, 1) {//    f()// }//// Do guarantees that when it returns, f has finished.// This implementation would not implement that guarantee:// given two simultaneous calls, the winner of the cas would// call f, and the second would return immediately, without// waiting for the first's call to f to complete.// This is why the slow path falls back to a mutex, and why// the o.done.Store must be delayed until after f returns.if o.done.Load() == 0 {// Outlined slow-path to allow inlining of the fast-path.o.doSlow(f)}
}func (o *Once) doSlow(f func()) {o.m.Lock()defer o.m.Unlock()if o.done.Load() == 0 {defer o.done.Store(1)f()}
}

使用示例

package mainimport ("fmt""sync"
)var (dbConnection stringonce         sync.Once
)func initDB() {dbConnection = "Connected to database"fmt.Println("Database connection is initialized.")
}func GetDBConnection() string {once.Do(initDB)return dbConnection
}func main() {var wg sync.WaitGroupfor i := 0; i < 4; i++ {wg.Add(1)go func() {defer wg.Done()conn := GetDBConnection()fmt.Println(conn)}()}wg.Wait()
}

sync.Oncefunc

sync.OnceFunc 允许你将一个普通的 func() 变成仅执行一次的函数,而不用显式地使用 sync.Once 结构体。

方法

  • func OnceFunc(f func()) func() 传入一个 func(),返回一个新的 func(),这个返回的函数只会执行一次,无论被调用多少次
  • func OnceValue[T any](f func() T) func() T sync.OnceValue 用于包装一个计算一次的函数,并返回一个单个值。
  • func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) sync.OnceValues 和 sync.OnceValue 类似,但支持返回多个值(包括 error)

代码示例

OnceFunc

package mainimport ("fmt""sync"
)func main() {// 只执行一次,返回一个值onceGetID := sync.OnceValue(func() int {fmt.Println("Generating ID...")return 42})fmt.Println(onceGetID()) // 生成 IDfmt.Println(onceGetID()) // 直接返回缓存的值
}

OnceValue

package mainimport ("fmt""sync"
)func main() {// 只执行一次,返回两个值onceFetchData := sync.OnceValues(func() (string, error) {fmt.Println("Fetching data from database...")return "User Data", nil})data, err := onceFetchData()fmt.Println(data, err) // 第一次调用,执行函数data, err = onceFetchData()fmt.Println(data, err) // 第二次调用,直接返回缓存值
}

sync.WaitGroup

用于等待一组goroutine完成它们的任务。

方法

  • Add(delta int) 用于设置或修改等待的 goroutine 数量,delta 可以是正数、负数或零
  • Done goroutine完成任务后,调用Done方法,内部执行了wg.Add(-1)
  • Wait 阻塞当前的 goroutine,直到计数器的值变为 0

代码示例

package mainimport ("context""fmt""sync""time"
)var wg sync.WaitGroupfunc workerWithTimeout(ctx context.Context, duration time.Duration) {// 从上下文中获取值if v := ctx.Value("language"); v != nil {fmt.Printf("context language value : %v\n", v)} else {fmt.Printf("no language key found in context\n")}select {case <-time.After(duration):fmt.Printf("任务执行完成\n")case <-ctx.Done():fmt.Printf("任务执行超时被取消,%v\n", ctx.Err())}wg.Done()
}func main() {ctx := context.Background()//设置超时时间5sctx, cancel := context.WithTimeout(ctx, 5*time.Second)defer cancel()// 创建一个带有值的上下文ctx = context.WithValue(ctx, "language", "Go")wg.Add(1)go workerWithTimeout(ctx, 6*time.Second)// 等待子任务完成wg.Wait()
}

sync.Map

sync.Map是并发安全的 map,它可以在 高并发环境下安全地存取键值对,适用于读多写少的共享数据(如果读多写多场景下,sync.Map反而是劣势,可以直接用传统Map+sync.Mutex),而不需要 sync.Mutex 加锁。

方法

  • Store(key, value) 存储键值对
  • Load(key) (value, bool) 获取键对应的值,返回 bool 表示是否存在
  • Delete(key) 删除键
  • LoadOrStore(key, value) (actual, loaded bool) 若 key 存在,则返回原值,loaded=true;否则存入新值,loaded=false
  • Range(func(key, value interface{}) bool) 遍历 sync.Map,回调函数返回 false 时停止遍历

内部实现原理

sync.Map 内部使用了两个数据结构:read 和 dirty,以及一个 misses 计数器:

  • read:是一个 atomic.Value 类型,存储的是一个只读的映射,允许并发的无锁读操作。大多数读操作可以直接在 read 中完成,无需加锁,因此读操作的性能较高。
  • dirty:是一个普通的 map,存储了一些可能未同步到 read 中的键值对。写操作会先作用于 dirty,当 misses 计数器达到一定阈值时,会将 dirty 提升为 read,并清空 dirty。
  • misses:计数器用于记录从 read 中读取键值对失败的次数,当 misses 达到 dirty 的长度时,会触发 dirty 提升为 read 的操作。

type Map struct {mu Mutex// read contains the portion of the map's contents that are safe for// concurrent access (with or without mu held).//// The read field itself is always safe to load, but must only be stored with// mu held.//// Entries stored in read may be updated concurrently without mu, but updating// a previously-expunged entry requires that the entry be copied to the dirty// map and unexpunged with mu held.read atomic.Pointer[readOnly]// dirty contains the portion of the map's contents that require mu to be// held. To ensure that the dirty map can be promoted to the read map quickly,// it also includes all of the non-expunged entries in the read map.//// Expunged entries are not stored in the dirty map. An expunged entry in the// clean map must be unexpunged and added to the dirty map before a new value// can be stored to it.//// If the dirty map is nil, the next write to the map will initialize it by// making a shallow copy of the clean map, omitting stale entries.dirty map[any]*entry// misses counts the number of loads since the read map was last updated that// needed to lock mu to determine whether the key was present.//// Once enough misses have occurred to cover the cost of copying the dirty// map, the dirty map will be promoted to the read map (in the unamended// state) and the next store to the map will make a new dirty copy.misses int
}func (m *Map) Load(key any) (value any, ok bool) {read := m.loadReadOnly()e, ok := read.m[key]if !ok && read.amended {m.mu.Lock()//双重检查// Avoid reporting a spurious miss if m.dirty got promoted while we were// blocked on m.mu. (If further loads of the same key will not miss, it's// not worth copying the dirty map for this key.)read = m.loadReadOnly()e, ok = read.m[key]//如果read读取不到,才从dirty读数据if !ok && read.amended {e, ok = m.dirty[key]// Regardless of whether the entry was present, record a miss: this key// will take the slow path until the dirty map is promoted to the read// map.m.missLocked()}m.mu.Unlock()}if !ok {return nil, false}return e.load()
}func (m *Map) missLocked() {m.misses++//misses达到dirty长度时,将dirty数据推到readif m.misses < len(m.dirty) {return}m.read.Store(&readOnly{m: m.dirty})m.dirty = nilm.misses = 0
}

使用示例

package mainimport ("fmt""sync"
)func main() {var sm sync.Map// 存储值sm.Store("name", "Alice")sm.Store("age", 25)// 读取值if value, ok := sm.Load("name"); ok {fmt.Println("Name:", value)}// 删除键sm.Delete("age")// 再次读取(已删除的键)if _, ok := sm.Load("age"); !ok {fmt.Println("Key 'age' not found")}
}

sync.Cond

sync.Cond 是 Go 语言中的一个同步原语,用于在多线程环境中进行条件变量的通知和等待。它通常和 sync.Mutex 或 sync.RWMutex 一起使用,用来在某些条件下暂停和唤醒 goroutine。多数情况下首选channels而不是Cond,channel 更简单和高效。在复杂的条件等待和精细的控制,比如多个条件需要同时满足时,sync.Cond 提供了更多的灵活性。

结构

type Cond struct {noCopy noCopy// L is held while observing or changing the conditionL Lockernotify  notifyListchecker copyChecker
}

方法

  • NewCond(l Locker) *Cond 初始化一个条件变量,并传入一个 sync.Mutex(这个锁用于保护共享资源)
  • Wait() 使当前 goroutine 进入等待状态,会自动释放关联的锁,并阻塞当前 goroutine,直到被其他 goroutine 调用 Signal() 或 Broadcast() 方法唤醒。
  • Signal() 唤醒一个等待在该条件变量上的 goroutine。如果有多个 goroutine 在等待,只会唤醒其中一个。
  • Broadcast() 唤醒所有等待在该条件变量上的 goroutine。

代码示例

package mainimport ("fmt""sync"
)var cond = sync.NewCond(&sync.Mutex{})
var counter = 0func worker(id int) {cond.L.Lock() // 锁定条件变量defer cond.L.Unlock()for counter < 5 {// 需要等待的条件cond.Wait()}fmt.Printf("Worker %d: counter reached 5\n", id)
}func main() {// 启动多个工作 goroutinefor i := 1; i <= 3; i++ {go worker(i)}// 主 goroutine 更新 counter 的值cond.L.Lock()counter = 5cond.L.Unlock()// 唤醒所有等待中的 goroutinecond.Broadcast()// 等待所有 goroutine 完成fmt.Scanln()
}

sync.Pool

sync.Pool 用于对象的缓存和复用,其核心目的是减少内存分配和垃圾回收的压力,提高程序的性能。

  • 存储对象:sync.Pool 维护一组临时对象,供多个 goroutine 共享。
  • 自动回收:GC 运行时,sync.Pool 可能会清空内部缓存的对象。
  • 多 goroutine 安全:sync.Pool 是并发安全的,可用于高并发场景。

方法

  • New 字段:这是一个函数类型的字段,用于创建新的对象。当池中没有可用的对象时,会调用这个函数来创建一个新的对象。
  • Get() interface{}:从池中获取一个对象。如果池中存在可用的对象,则返回该对象;如果池中没有可用的对象,则调用 New 字段指定的函数创建一个新的对象并返回。
  • Put(x interface{}):将一个对象放回池中,以便后续复用。

代码示例

package mainimport ("fmt""sync"
)var bufferPool = sync.Pool{New: func() any {fmt.Println("Allocating new buffer...")return make([]byte, 1024) // 1KB 缓冲区},
}func main() {// 获取缓冲区buf := bufferPool.Get().([]byte)fmt.Println("Buffer size:", len(buf))// 归还缓冲区bufferPool.Put(buf)// 再次获取buf2 := bufferPool.Get().([]byte) // 复用缓存的 buffmt.Println("Buffer reused:", len(buf2))
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/15937.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PDF Shaper:免费多功能 PDF 工具箱,一站式满足您的 PDF 需求!

​PDF Shaper 是一款功能强大且完全免费的 PDF 工具箱&#xff0c;它几乎涵盖了日常 PDF 操作的方方面面&#xff0c;无论是转换、编辑还是处理&#xff0c;都能轻松搞定。以下是这款软件的详细介绍&#xff1a; 功能丰富&#xff0c;一应俱全 PDF 转换功能强大 PDF 转 Word&am…

未来替代手机的产品,而非手机的本身

替代手机的产品包括以下几种&#xff1a; 可穿戴设备&#xff1a;智能手表、智能眼镜等可穿戴设备可以提供类似手机的功能&#xff0c;如通话、信息推送、浏览网页等。 虚拟现实&#xff08;VR&#xff09;技术&#xff1a;通过佩戴VR头显&#xff0c;用户可以进行语音通话、发…

deepseek+“D-id”或“即梦AI”快速生成短视频

1、deepseek生成视频脚本 1.1、第一步&#xff1a;使用通用模板提出需求&#xff0c;生成视频脚本 对话输入示例脚本1&#xff1a; 大年初五是迎财神的日志&#xff0c;帮我生成10秒左右的短视频&#xff0c; 体现一家3口在院子里欢庆新年&#xff0c; 孩子在院子里放鞭炮烟…

在CT107D单片机综合训练平台上实现外部中断控制LED闪烁

引言 在单片机开发中&#xff0c;外部中断是一个非常重要的功能&#xff0c;它可以让单片机在检测到外部信号变化时立即做出响应。本文将详细介绍如何在CT107D单片机综合训练平台上使用外部中断来控制LED灯的闪烁。我们将使用两种不同的方式来实现这一功能&#xff1a;一种是在…

为什么推荐使用 LabVIEW 开发

在仪器行业的软件开发中&#xff0c;LabVIEW 以其图形化编程、快速原型开发、高效硬件集成的优势&#xff0c;成为自动化测试和控制系统的理想选择。尽管一些工程师仍然坚持使用 C 语言&#xff0c;但这更多是出于习惯&#xff0c;而非技术上的必然。LabVIEW 不仅支持 NI 硬件&…

力扣1448. 统计二叉树中好节点的数目

Problem: 1448. 统计二叉树中好节点的数目 文章目录 题目描述思路复杂度Code 题目描述 思路 对二叉树进行先序遍历&#xff0c;边遍历边对比并更新当前路径上的最大值pathMax&#xff0c;若当pathMax小于等于当前节点值&#xff0c;则好节点的数目加一 复杂度 时间复杂度: O (…

DeepSeek帮助做【真】软件需求-而不是批量刷废话

尝试给DeepSeek一份系统用例规约&#xff0c;让它帮判断哪些地方还没有覆盖涉众利益。结果见以下 需求工作的重点可以放在建模精细的真实现状流程和精细的真实涉众利益上&#xff0c;AI帮助推演系统需求。

【JVM详解五】JVM性能调优

示例&#xff1a; 配置JVM参数运行 #前台运行 java -XX:MetaspaceSize-128m -XX:MaxMetaspaceSize-128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio8 - XX:UseConcMarkSweepGC -jar /jar包路径 #后台运行 nohup java -XX:MetaspaceSize-128m -XX:MaxMetaspaceS…

Qt文本处理【正则表达式】示例详解:【QRegularExpression】

在 Qt 中&#xff0c;正则表达式是处理文本的强大工具&#xff0c;它能够帮助我们匹配、搜索和替换特定的字符串模式。自 Qt 5 起&#xff0c;QRegularExpression 类提供了对 ECMAScript 标准的正则表达式支持&#xff0c;这使得它在处理各种复杂的字符串任务时变得更加高效和灵…

【算法学习】拓扑排序(Topological Sorting)

目录 定义 例子 拓扑排序的实现 核心思想 实现方法 1&#xff0c;Kahn算法&#xff08;基于贪心策略&#xff09; 步骤&#xff1a; 用二维数组存储图的例子 用哈希表存储图的例子 2&#xff0c;基于DFS的后序遍历法 总结 拓扑排序的应用场景 1&#xff0c;任务调度 …

JavaEE-前端与后台的搭建

一.idea连接数据库 在使用 IntelliJ IDEA 连接数据库时&#xff0c;可以按照以下步骤操作&#xff1a; ### 1. 打开数据库工具窗口 - 在 IntelliJ IDEA 中&#xff0c;点击右侧的 Database 工具窗口&#xff0c;或通过 View -> Tool Windows -> Database 打开。 ### 2. 添…

华为Mate 70 Pro或推出全新版本

关于华为Mate 70 Pro或推出全新版本的相关内容&#xff1a;可能的版本及命名。 据数码博主“定焦数码”爆料&#xff0c;华为Mate 70 Pro将推出新版本&#xff0c;命名为“优享版”。这一命名方式与华为Mate 60系列中的Mate 60 Pro乐臻版类似&#xff0c;预计优享版也会是一个组…

Linux 实操篇 实用指令

一、远程登录到Linux服务器 &#xff08;1&#xff09;为什么需要远程登录Linux linux服务器是开发小组共享的正式上线的项目是运行在公网因此程序员需要远程登陆到Linux进行项目管理或者开发画出简单的网络拓扑示意图远程登陆客户端有Xshell6&#xff0c;Xftp6&#xff0c;我…

SpringBoot 统一功能处理之拦截器、数据返回格式、异常处理

目录 拦截器 一、什么是拦截器 二 拦截器的使用 三 拦截路径配置 四 拦截器的执行流程 统一数据返回格式 统一异常处理 拦截器 一、什么是拦截器 拦截器是Spring框架提供的核心功能之一&#xff0c;主要用来拦截用户的请求&#xff0c;在指定方法前后&#xff0c;根据业务…

Django学习笔记(第一天:Django基本知识简介与启动)

博主毕业已经工作一年多了&#xff0c;最基本的测试工作已经完全掌握。一方面为了解决当前公司没有自动化测试平台的痛点&#xff0c;另一方面为了向更高级的测试架构师转型&#xff0c;于是重温Django的知识&#xff0c;用于后期搭建测试自动化平台。 为什么不选择Java&#x…

Spring Cloud工程完善

目录 完善订单服务 启动类 配置文件 实体类 Controller Service Mapper 测试运行 完成商品服务 启动类 配置文件 实体类 Controller Service Mapper 测试运行 远程调用 需求 实现 1.定义RestTemplate 2.修改order-service中的OrderService 测试运行 Rest…

如何将网站提交百度收录完整SEO教程

百度收录是中文网站获取流量的重要渠道。本文以我的网站&#xff0c;www.mnxz.fun&#xff08;当然现在没啥流量&#xff09; 为例&#xff0c;详细讲解从提交收录到自动化维护的全流程。 一、百度收录提交方法 1. 验证网站所有权 1、登录百度搜索资源平台 2、选择「用户中心…

Linux ftrace 内核跟踪入门

文章目录 ftrace介绍开启ftrace常用ftrace跟踪器ftrace使用ftrace跟踪指定内核函数ftrace跟踪指定pid ftrace原理ftrace与stracetrace-cmd 工具KernelShark参考 ftrace介绍 Ftrace is an internal tracer designed to help out developers and designers of systems to find wh…

VUE项目中实现权限控制,菜单权限,按钮权限,接口权限,路由权限,操作权限,数据权限实现

VUE项目中实现权限控制&#xff0c;菜单权限&#xff0c;按钮权限&#xff0c;接口权限&#xff0c;路由权限&#xff0c;操作权限&#xff0c;数据权限实现 权限系统分类&#xff08;RBAC&#xff09;引言菜单权限按钮权限接口权限路由权限 菜单权限方案方案一&#xff1a;菜单…

Pdf手册阅读(1)--数字签名篇

原文阅读摘要 PDF支持的数字签名&#xff0c; 不仅仅是公私钥签名&#xff0c;还可以是指纹、手写、虹膜等生物识别签名。PDF签名的计算方式&#xff0c;可以基于字节范围进行计算&#xff0c;也可以基于Pdf 对象&#xff08;pdf object&#xff09;进行计算。 PDF文件可能包…