【Go语言快速上手】第二部分:Go语言进阶之并发编程
文章目录
- 一、并发编程
- 1. goroutine:创建和调度 goroutine
- 2. channel:无缓冲 channel、有缓冲 channel、select 语句
- 2.1 无缓冲 channel
- 2.2 有缓冲 channel
- 2.3 select 语句
- 3. sync 包:Mutex、RWMutex、WaitGroup 等同步原语
- 3.1 Mutex:互斥锁
- 3.2 RWMutex:读写互斥锁
- 3.3 WaitGroup:等待多个 goroutine 完成
一、并发编程
Go 语言的并发编程是其一大亮点。通过 goroutine
和 channel
,Go 使得并发编程变得简洁且高效。Go 还提供了 sync
包,包含了多种同步原语,帮助开发者在并发程序中处理共享数据和同步问题。
1. goroutine:创建和调度 goroutine
goroutine
是 Go 中最基本的并发单元,可以认为是轻量级的线程。通过 go
关键字可以轻松创建一个新的 goroutine
,并发执行指定的函数或方法。
package main
import (
"fmt"
"time"
)
// 一个简单的并发任务
func sayHello() {
time.Sleep(1 * time.Second)
fmt.Println("Hello from goroutine!")
}
func main() {
go sayHello() // 创建一个 goroutine
fmt.Println("Hello from main!")
time.Sleep(2 * time.Second) // 等待 goroutine 执行完毕
}
在这个示例中,go sayHello()
启动了一个新的 goroutine
,并发执行 sayHello
函数。主函数继续执行并打印输出,最后通过 time.Sleep
等待 goroutine
完成。
2. channel:无缓冲 channel、有缓冲 channel、select 语句
channel
是 Go 语言用于不同 goroutine
之间通信的机制。通过 channel
,可以安全地传递数据,避免了数据竞争问题。Go 提供了无缓冲和有缓冲的 channel
,以及 select
语句来处理多个 channel
的操作。
2.1 无缓冲 channel
无缓冲 channel
会在发送数据和接收数据时进行同步,确保发送和接收操作相互配合。
package main
import "fmt"
func main() {
ch := make(chan string) // 创建一个无缓冲 channel
// 启动 goroutine 发送数据
go func() {
ch <- "Hello from goroutine!" // 向 channel 发送数据
}()
// 接收数据
msg := <-ch
fmt.Println(msg) // 输出: Hello from goroutine!
}
2.2 有缓冲 channel
有缓冲的 channel
可以在发送数据时不必立即等待接收方,直到缓冲区满或接收方取走数据。创建有缓冲 channel
时,可以指定缓冲区的大小。
package main
import "fmt"
func main() {
ch := make(chan string, 2) // 创建一个缓冲区大小为 2 的 channel
ch <- "Hello"
ch <- "World" // 向 channel 发送两个消息
fmt.Println(<-ch)
fmt.Println(<-ch)
}
在这个示例中,channel
的缓冲区大小为 2,可以在不立即接收的情况下向 channel
发送两个数据。
2.3 select 语句
select
语句允许你等待多个 channel
操作。它类似于 switch
语句,但用于处理多个 channel
的发送或接收。
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "Message from ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "Message from ch2"
}()
// 使用 select 语句监听多个 channel
select {
case msg1 := <-ch1:
fmt.Println("Received:", msg1)
case msg2 := <-ch2:
fmt.Println("Received:", msg2)
}
}
在这个示例中,select
会等待 ch1
或 ch2
中的任一 channel
可用。当第一个 channel
可用时,它会立即执行对应的 case
,并停止等待。
3. sync 包:Mutex、RWMutex、WaitGroup 等同步原语
Go 提供了 sync
包中的多种同步原语,用于处理并发程序中的共享资源访问问题。
3.1 Mutex:互斥锁
Mutex
是最常用的同步原语,它确保同一时刻只有一个 goroutine
可以访问共享资源。通过 Lock
和 Unlock
方法来加锁和解锁。
package main
import (
"fmt"
"sync"
)
var counter int
var mutex sync.Mutex
func increment() {
mutex.Lock() // 加锁
counter++
mutex.Unlock() // 解锁
}
func main() {
var wg sync.WaitGroup
// 启动多个 goroutine 进行并发操作
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait() // 等待所有 goroutine 执行完毕
fmt.Println("Final counter:", counter)
}
在这个示例中,使用 Mutex
来保证对 counter
的并发访问是安全的。
3.2 RWMutex:读写互斥锁
RWMutex
是一种读写互斥锁,它允许多个 goroutine
同时读取共享资源,但在写操作时会阻止其他的读写操作。
package main
import (
"fmt"
"sync"
)
var counter int
var rwMutex sync.RWMutex
func read() int {
rwMutex.RLock() // 读锁
defer rwMutex.RUnlock()
return counter
}
func write() {
rwMutex.Lock() // 写锁
counter++
rwMutex.Unlock()
}
func main() {
var wg sync.WaitGroup
// 启动多个 goroutine 进行读写操作
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
write()
}()
}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
read()
}()
}
wg.Wait()
fmt.Println("Final counter:", counter)
}
在这个示例中,RWMutex
允许多个 goroutine
同时进行读操作,但在执行写操作时会锁住资源。
3.3 WaitGroup:等待多个 goroutine 完成
WaitGroup
用于等待一组 goroutine
执行完毕。它提供了 Add
、Done
和 Wait
方法来控制并发流程。
package main
import (
"fmt"
"sync"
)
func task(wg *sync.WaitGroup) {
defer wg.Done() // 执行完毕时调用 Done
fmt.Println("Task completed")
}
func main() {
var wg sync.WaitGroup
// 启动多个 goroutine
for i := 0; i < 5; i++ {
wg.Add(1) // 增加等待的 goroutine 数量
go task(&wg)
}
wg.Wait() // 等待所有 goroutine 执行完毕
fmt.Println("All tasks completed")
}
在这个示例中,WaitGroup
用于等待多个并发任务完成。