Go语言并发编程:sync、channel、goroutine详解
Go语言以其高效的并发编程能力著称,而sync、channel和goroutine则是其并发编程的三大核心机制。理解和掌握这三种机制对于编写高性能、可扩展的Go语言程序至关重要。
1. goroutine:协程
goroutine是Go语言实现的轻量级线程,是一种比传统线程更轻便、更易管理的并发执行单元。每个goroutine都有自己的调用栈,可以独立运行。Go运行时会自动管理和调度goroutine,使得开发者无需过多关注底层的线程管理细节。
创建goroutine可以使用go关键字。例如:
Go
go func() {
fmt.Println("Hello from goroutine!")
}()
这段代码将创建一个新的goroutine,并执行fmt.Println("Hello from goroutine!")语句。
goroutine的优势在于其轻便性和易用性。与传统线程相比,goroutine的创建和销毁代价更低,并且不需要额外的线程同步机制。这使得Go语言程序可以轻松地创建大量的goroutine,从而充分利用多核CPU的性能。
2. channel:通信与同步
channel是Go语言用于goroutine之间通信的同步机制。channel可以传输任意类型的数据,并且支持阻塞式和非阻塞式的通信方式。
创建channel可以使用make函数。例如:
Go
ch := make(chan int)
这段代码创建了一个名为ch的channel,该channel可以传输整数类型的数据。
向channel发送数据可以使用<-操作符。例如:
Go
ch <- 100
这段代码将数字100发送到channel ch中。
从channel接收数据可以使用->操作符。例如:
Go
v := <-ch
fmt.Println(v)
这段代码从channel ch中接收数据并将其赋值给变量v。
channel还支持非阻塞式的通信方式。例如:
Go
select {
case v := <-ch:
fmt.Println(v)
case <-time.After(time.Second):
fmt.Println("Timeout")
}
这段代码使用select语句来实现非阻塞式的通信。如果channel ch中存在数据,则将其接收并输出;否则,等待1秒后超时并输出"Timeout"。
3. sync:同步与控制
sync包提供了各种同步机制,用于控制goroutine之间的协作。常用的sync机制包括:
sync.Mutex:用于互斥访问共享资源。sync.WaitGroup:用于等待一组goroutine完成工作。sync.Once:用于确保某项操作只执行一次。
例如,使用sync.Mutex来保护共享变量:
Go
var counter int
var mutex sync.Mutex
func increment() {
mutex.Lock()
defer mutex.Unlock()
counter++
}
这段代码使用sync.Mutex来保护共享变量counter。在increment函数中,首先使用mutex.Lock()获取互斥锁,然后递增变量counter,最后使用mutex.Unlock()释放互斥锁。
4. 综合运用求素数
1. 未使用并发编程
package main
import (
"fmt"
"time"
)
// 判断是否是素数
func isPrime(num int) bool {
if num <= 1 {
return false
}
for i := 2; i*i <= num; i++ {
if num%i == 0 {
return false
}
}
return true
}
func main() {
start := time.Now().Unix()
for i := 0; i < 5000000; i++ {
if isPrime(i) {
fmt.Println(i)
}
}
end := time.Now().Unix()
fmt.Printf("用时: %d 秒\n", end - start) // 用时: 0 秒
}
2. 使用并发编程
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
// 判断是否是素数
func isPrime(num int) bool {
if num <= 1 {
return false
}
for i := 2; i*i <= num; i++ {
if num%i == 0 {
return false
}
}
return true
}
// 设置数据
func setNmber(number int, ch chan int) {
for i := 2; i < number; i++ {
ch <- i
}
close(ch)
mySync.Done()
}
// 将素数添加到单独的chan
func filterPrimes(ch chan int, primeCh chan int, filter chan bool) {
for num := range ch {
if isPrime(num) {
primeCh <- num
}
}
filter <- true
if len(filter) == 10 {
close(primeCh)
}
mySync.Done()
}
// 打印 耗时比较多
func printPrimes(primeCh chan int) {
for prime := range primeCh {
fmt.Println(prime)
}
mySync.Done()
}
// 确保协程都完成再结束main
var mySync sync.WaitGroup
func main() {
numCPUs := runtime.NumCPU()
fmt.Printf("cpu核数 of CPUs: %d\n", numCPUs)
numChan := make(chan int, 1000)
primeChan := make(chan int, 1000)
filterChan := make(chan bool, 10) // 用来计算什么时候关闭primeChan chan,使用rang遍历chan时必须要关闭chan
start := time.Now().Unix()
mySync.Add(1)
go setNmber(5000000, numChan)
for i := 0; i < 10; i++ {
mySync.Add(1)
go filterPrimes(numChan, primeChan, filterChan)
}
mySync.Add(1)
go printPrimes(primeChan)
mySync.Wait()
end := time.Now().Unix()
fmt.Printf("用时: %d 秒\n", end - start) // 用时: 0 秒
}
3. 执行速度对比
| 数据范围 | 无并发用时(秒) | 并发用时(秒) |
|---|---|---|
| 500000 | 0 | 0 |
| 5000000 | 1 | 1 |
| 50000000 | 21 | 13 |
| 500000000 | 521 | 182 |
总结
sync、channel和goroutine是Go语言并发编程的三大核心机制。理解和掌握这三种机制对于编写高性能、可扩展的Go语言程序至关重要。