首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
Go语言的面向对象编程
面向对象编程的本质
Go语言实现封装
Go语言中字段和方法的封装
为值类型和指针类型绑定方法的区别
Go语言实现继承
利用组合实现继承
匿名字段的支持
多继承
Go语言实现多态
面向接口编程
Go语言中的接口
Go语言中的接口实现
利用面向接口编程实现方法多态
编程范例——接口的典型应用
接口嵌套实例
伪继承与接口实现
并发
线程的概念
线程模型
协程的工作原理
协程的使用
GPM模型
从线程模型看GOMAXPROCS参数
Go语言中的协程同步
独占锁——Mutex
读写锁——RWMutex
等待组——WaitGroup
利用channel实现协程同步
利用channel实现锁定
利用channel实现等待组
总结使用channel实现并发控制
让出时间片
time.Sleep()和runtime.Gosched()的本质区别
runtime.Gosched()与多核CPU
Go语言中的单例
利用sync.Once实现单例
sync.Once的实现原理
编程范例——协程池及协程中断
协程池的实现
协程的中断执行
当前位置:
首页>>
技术小册>>
深入浅出Go语言核心编程(四)
小册名称:深入浅出Go语言核心编程(四)
### Go语言中的协程同步 在Go语言中,协程(Goroutine)是其并发编程模型的核心。与传统的线程相比,Goroutine更加轻量级,由Go运行时(runtime)管理,使得创建成千上万的并发任务变得既简单又高效。然而,随着并发任务的增加,如何有效地同步这些Goroutine之间的执行顺序、保护共享资源不被并发访问破坏,以及处理协程间的通信,成为了编写高效、稳定Go程序的关键。本章将深入探讨Go语言中的协程同步机制,包括同步原语、通道(Channel)以及高级同步模式。 #### 一、同步原语 Go标准库提供了多种同步原语来帮助开发者控制Goroutine的执行顺序和访问共享资源的方式。这些原语主要包括互斥锁(Mutex)、读写锁(RWMutex)、条件变量(Condition Variables,通过sync.Cond实现)、Once(确保函数只执行一次)以及WaitGroup等。 ##### 1.1 互斥锁(sync.Mutex) 互斥锁是最基本的同步原语之一,用于保护共享资源不被多个Goroutine同时访问。当一个Goroutine获得锁后,其他尝试获取该锁的Goroutine将被阻塞,直到锁被释放。 ```go var mu sync.Mutex func criticalSection() { mu.Lock() defer mu.Unlock() // 访问或修改共享资源 } ``` 使用`defer`语句确保互斥锁在函数返回前被释放,是一种常见且推荐的做法。 ##### 1.2 读写锁(sync.RWMutex) 读写锁是对互斥锁的一种优化,它允许多个Goroutine同时读取共享资源,但写入时仍需要独占访问。这提高了并发读取的效率,但增加了实现的复杂性。 ```go var rwMu sync.RWMutex func readData() { rwMu.RLock() defer rwMu.RUnlock() // 读取共享资源 } func writeData() { rwMu.Lock() defer rwMu.Unlock() // 修改共享资源 } ``` ##### 1.3 条件变量(sync.Cond) 条件变量用于等待或通知一组Goroutine,通常与互斥锁一起使用。它允许一个或多个Goroutine在特定条件不满足时挂起,并在条件变为真时被唤醒。 ```go var mu sync.Mutex var cond = sync.NewCond(&mu) func waitForSomething() { mu.Lock() defer mu.Unlock() for !somethingHappened { cond.Wait() // 等待条件变量 } // 处理条件满足后的逻辑 } func signalSomethingHappened() { mu.Lock() defer mu.Unlock() somethingHappened = true cond.Signal() // 唤醒一个等待的Goroutine // 或 cond.Broadcast() 唤醒所有等待的Goroutines } ``` ##### 1.4 sync.Once `sync.Once`用于确保某个函数只执行一次,无论它被调用多少次或从多少个Goroutine中调用。这对于初始化全局变量或执行只需运行一次的设置操作非常有用。 ```go var once sync.Once func setup() { once.Do(func() { // 初始化操作 }) } ``` ##### 1.5 sync.WaitGroup `sync.WaitGroup`用于等待一组Goroutine完成。它允许Goroutine向WaitGroup添加自己(表示一个任务开始),并在完成时通知WaitGroup(表示任务完成)。主Goroutine通过调用WaitGroup的Wait方法等待所有任务完成。 ```go var wg sync.WaitGroup func worker(id int) { defer wg.Done() // 表示任务完成 wg.Add(1) // 表示任务开始(通常放在函数开始处) // 执行任务 } func main() { for i := 0; i < 5; i++ { go worker(i) } wg.Wait() // 等待所有worker完成 } ``` 注意:在上面的`worker`函数中,`wg.Add(1)`和`defer wg.Done()`的调用顺序需要特别注意,以避免竞态条件。通常,`wg.Add(1)`会放在函数开始处,而`defer wg.Done()`紧跟其后。 #### 二、通道(Channel) 虽然同步原语提供了强大的同步机制,但Go语言更推荐使用通道(Channel)作为Goroutine间通信的主要方式。通道不仅实现了协程间的同步,还实现了数据的传递,使得协程间的协作更加自然和高效。 ##### 2.1 基本概念 通道是一种特殊的类型,用于在不同的Goroutine之间安全地传递值。你可以将其视为一个管道,数据从一个Goroutine的一端发送,从另一个Goroutine的另一端接收。 ```go ch := make(chan int) go func() { ch <- 42 // 发送数据 }() val := <-ch // 接收数据 ``` ##### 2.2 通道的类型 - **无缓冲通道**:只有在发送方和接收方都准备好时,数据才会在通道中传输。这导致发送和接收操作都会阻塞,直到对方准备好。 - **有缓冲通道**:通道内部维护一个队列来存储元素。发送操作会在队列未满时立即返回,接收操作会在队列非空时立即返回。 ##### 2.3 通道的高级用法 - **关闭通道**:当没有更多的值会被发送到通道时,可以关闭通道。接收方可以通过额外的值(通道类型的零值)来检测通道是否已关闭。 - **range和close的结合**:使用`range`循环可以从通道中接收值,直到通道被关闭。 - **select语句**:`select`语句允许一个Goroutine等待多个通信操作。当多个通道都准备好时,`select`会随机选择一个执行。如果没有任何通道准备好,`select`会阻塞,直到至少有一个通道准备好。 #### 三、高级同步模式 除了基本的同步原语和通道外,Go语言还允许开发者通过组合这些工具来构建更复杂的同步模式,如信号量(Semaphore)、有限缓冲池等。 ##### 3.1 信号量(Semaphore) 信号量是一种用于控制同时访问某个特定资源(如数据库连接、文件句柄等)的操作数量的同步机制。在Go中,可以通过结合互斥锁和通道来实现信号量。 ##### 3.2 有限缓冲池 有限缓冲池是一种管理有限资源(如数据库连接池、线程池等)的同步模式。通过维护一个固定大小的资源池,并在需要时从池中获取或释放资源,可以有效地控制资源的使用和复用。 #### 结论 Go语言通过其内置的协程(Goroutine)和通道(Channel)机制,以及丰富的同步原语,为开发者提供了强大而灵活的并发编程能力。理解和掌握这些同步机制,对于编写高效、稳定的Go程序至关重要。无论是使用互斥锁、读写锁、条件变量等同步原语,还是通过通道进行协程间的通信和同步,都需要根据具体的应用场景和需求来选择合适的工具和方法。同时,通过组合这些基础工具来构建更复杂的同步模式,也是提升Go程序性能和可维护性的重要手段。
上一篇:
从线程模型看GOMAXPROCS参数
下一篇:
独占锁——Mutex
该分类下的相关小册推荐:
go编程权威指南(一)
GO面试指南
深入浅出Go语言核心编程(二)
从零写一个基于go语言的Web框架
Golang修炼指南
go编程权威指南(三)
深入浅出Go语言核心编程(八)
Go语言入门实战经典
深入浅出Go语言核心编程(七)
深入浅出Go语言核心编程(五)
Go Web编程(上)
Golang并发编程实战