Go 并发实现协程同步的多种解决方法
在 Go 编程中,对于大量协程的并发执行,我们经常需要对它们进行同步控制,以保证协程之间的正确互动和信息传递。本文介绍 Go 实现协程同步的常用方法,包括使用 WaitGroup、channel、Mutex 等。
使用 WaitGroup
举个例子,我们可能需要同时开启多个协程进行图片下载,且需要等所有协程下载完毕才能继续执行下面的逻辑。这时我们就可以使用 WaitGroup 来控制协程同步。
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
urls := []string{
"https://www.google.com/",
"https://www.bing.com/",
"https://www.baidu.com/",
}
// 创建 WaitGroup
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1) // WaitGroup 计数器加 1
go func(url string) {
defer wg.Done() // WaitGroup 计数器减 1
resp, err := http.Get(url)
if err != nil {
fmt.Printf("Error: %s\n", err)
return
}
defer resp.Body.Close()
fmt.Printf("Finished downloading %s\n", url)
}(url)
}
wg.Wait() // 阻塞等待 WaitGroup 计数器为 0
fmt.Println("All downloads finished")
}
使用 WaitGroup 的主要流程为:
- 创建 WaitGroup 对象
- 在每个协程启动前,调用
WaitGroup.Add(1)
来将计数器加一 - 在协程里面执行任务
- 在协程任务结束时,调用
WaitGroup.Done()
来将计数器减一 - 最后调用
WaitGroup.Wait()
阻塞等待所有计数器都归零
使用 channel
在 Go 中 channel 也是一种用来同步协程的重要机制。它的主要特点是在协程之间传递数据,并且可以通过阻塞和非阻塞读写操作来实现同步控制。
下面是一个使用 channel 实现协程同步的示例,这里我们模拟一个生产者消费者的场景,其中一个协程作为生产者向 channel 中写数据,另一个协程作为消费者从 channel 中读数据。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 3) // 创建大小为 3 的 channel
go func() {
for i := 1; i <= 5; i++ {
ch <- i
fmt.Printf("Write %d to channel\n", i)
time.Sleep(time.Second)
}
close(ch) // 关闭 channel
}()
go func() {
for i := range ch {
fmt.Printf("Read %d from channel\n", i)
time.Sleep(time.Second)
}
}()
// 阻塞等待所有协程执行完毕
time.Sleep(5 * time.Second)
fmt.Println("All goroutines finished")
}
在这个示例中,我们首先创建了一个大小为 3 的 channel。在生产者协程中,我们按顺序向 channel 中写入了 5 个数据。由于 channel 的容量只有 3,当写入了 3 个数据后,协程会阻塞等待消费者协程读取数据。当消费者协程从 channel 中读取 1 个数据后,生产者协程又会继续向 channel 中写入数据,如此往复,直到所有数据都被写入并读取完毕。
其中,range ch
的作用是等待并循环读取 channel 中的数据,直到 channel 被关闭。
使用 Mutex
Mutex 是一种主要用于控制多个协程之间对某个资源进行互斥访问的机制。它的主要作用是保障互斥访问时数据的安全性,避免数据竞争。
下面是使用 Mutex 实现协程同步的示例,其中我们创建一个共享变量 count,同时启动 4 个协程对其进行读写:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
var count int // 共享变量
var mutex sync.Mutex // 互斥锁
for i := 0; i < 4; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 对 count 进行读
mutex.Lock()
fmt.Printf("Read: %d\n", count)
mutex.Unlock()
time.Sleep(time.Second)
// 对 count 进行写
mutex.Lock()
count++
fmt.Printf("Write: %d\n", count)
mutex.Unlock()
}()
}
wg.Wait()
fmt.Println("All goroutines finished")
}
在这个示例中,我们首先创建了一个共享变量 count 和一个互斥锁 mutex。在每个协程中,我们先通过 mutex.Lock()
获取互斥锁,然后进行数据访问操作。在对 count 进行写操作完成后,我们释放互斥锁,以此保障其他协程能够顺利地访问并操作同一个共享变量。最后我们调用 wg.Wait()
阻塞式地等待所有协程执行完毕。
以上是 Go 并发实现协程同步的常见方法及其示例。除此之外,Go 还提供了一些类似于 atomic 等原子操作库,以及基于 Context 的超时控制等机制,更加丰富和灵活。在实际应用中,我们需要根据具体的场景和需求来选择合适的同步机制,以保证 Go 程序的正确性和高效性。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Go 并发实现协程同步的多种解决方法 - Python技术站