go 并发
Go 语言的并发支持是其一大特色,Go 通过 goroutines 和 channels 提供了轻量级且高效的并发编程模型。以下是 Go 并发相关的核心概念和常用技术的详细介绍。
1. Goroutines
Goroutine 是 Go 语言的并发执行单元,它是由 Go 运行时管理的轻量级线程。启动一个新的 goroutine 非常简单,只需要在函数调用前加上 go 关键字。
启动 Goroutine
go myFunction() // 启动一个新的 goroutine 执行 myFunction示例:并发执行多个任务
package main
import (
"fmt"
"time"
)
func task(id int) {
fmt.Printf("Task %d started\n", id)
time.Sleep(2 * time.Second)
fmt.Printf("Task %d completed\n", id)
}
func main() {
for i := 1; i <= 3; i++ {
go task(i) // 启动 goroutine 执行 task
}
// 等待一段时间让 goroutines 完成工作
time.Sleep(3 * time.Second)
}在这个示例中,task 函数被并发地执行了三次,每个任务会睡眠 2 秒钟,模拟一个耗时操作。
2. Channels
Go 的 channel 是一个用来在多个 goroutine 之间传递数据的管道。通过 channels,可以避免数据竞争和共享内存的复杂性。
创建 Channel
ch := make(chan int) // 创建一个传递 int 类型的 channel发送数据到 Channel
ch <- 42 // 发送数据到 channel从 Channel 接收数据
value := <-ch // 从 channel 接收数据示例:通过 Channel 协调并发任务
package main
import (
"fmt"
"time"
)
func worker(id int, ch chan string) {
fmt.Printf("Worker %d started\n", id)
time.Sleep(2 * time.Second)
ch <- fmt.Sprintf("Worker %d finished", id) // 向 channel 发送数据
}
func main() {
ch := make(chan string) // 创建一个 channel
for i := 1; i <= 3; i++ {
go worker(i, ch) // 启动并发任务
}
// 从 channel 接收数据
for i := 1; i <= 3; i++ {
fmt.Println(<-ch) // 等待并接收 worker 的结果
}
}在上面的代码中,worker 函数将任务的执行结果通过 channel 发送到主 goroutine,主 goroutine 会接收这些结果并输出。
3. Buffered Channels (缓冲区 Channel)
缓冲区 channel 与普通 channel 的区别在于,缓冲区 channel 允许存储一定数量的元素,而不需要立即等待接收方读取数据。这样可以增加并发度,避免阻塞。
创建缓冲区 Channel
ch := make(chan int, 3) // 创建一个容量为 3 的缓冲区 channel示例:使用缓冲区 Channel
package main
import "fmt"
func main() {
ch := make(chan int, 3) // 创建一个缓冲区大小为 3 的 channel
// 向 channel 发送数据
ch <- 1
ch <- 2
ch <- 3
// 读取并输出数据
fmt.Println(<-ch) // 输出: 1
fmt.Println(<-ch) // 输出: 2
fmt.Println(<-ch) // 输出: 3
}在这个示例中,缓冲区 channel 能够存储 3 个元素,当它满了时,发送操作会被阻塞,直到有接收方从 channel 中读取数据。
4. Select
select 语句允许我们同时监听多个 channel,类似于 switch 语句。它可以用于多个 goroutine 间的同步和通讯。select 会阻塞直到有一个 channel 准备好。
示例:使用 Select 处理多个 Channel
package main
import "fmt"
func sendData(ch chan string, message string) {
ch <- message
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go sendData(ch1, "Hello from ch1")
go sendData(ch2, "Hello from ch2")
// 使用 select 同时等待两个 channel 中的数据
select {
case msg1 := <-ch1:
fmt.Println("Received:", msg1)
case msg2 := <-ch2:
fmt.Println("Received:", msg2)
}
}在这个示例中,select 同时监听 ch1 和 ch2 两个 channel,谁先收到数据,谁就执行相应的 case 语句。这样,Go 会优先处理第一个可用的 channel。
5. WaitGroup
sync.WaitGroup 是 Go 中用于等待多个 goroutine 执行完成的工具。它提供了计数器,可以对多个并发任务进行等待。
示例:使用 WaitGroup 等待多个 Goroutine 完成
package main
import (
"fmt"
"sync"
)
func task(id int, wg *sync.WaitGroup) {
defer wg.Done() // goroutine 完成时,调用 Done 减少计数
fmt.Printf("Task %d started\n", id)
}
func main() {
var wg sync.WaitGroup
// 启动多个 goroutine
for i := 1; i <= 3; i++ {
wg.Add(1) // 增加计数器
go task(i, &wg)
}
wg.Wait() // 等待所有 goroutine 完成
fmt.Println("All tasks completed")
}在这个示例中,WaitGroup 用来确保主程序等待所有的 goroutine 执行完成后再继续执行。
6. Mutex (互斥锁)
Go 提供了 sync.Mutex 用于控制对共享资源的访问,以防止数据竞争。互斥锁确保在同一时刻只有一个 goroutine 可以访问某些共享数据。
示例:使用 Mutex 防止数据竞争
package main
import (
"fmt"
"sync"
)
var (
counter int
mu sync.Mutex // 定义互斥锁
)
func increment() {
mu.Lock() // 加锁
counter++ // 修改共享资源
mu.Unlock() // 解锁
}
func main() {
var wg sync.WaitGroup
// 启动多个 goroutine 修改共享资源
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait() // 等待所有 goroutine 完成
fmt.Println("Final counter value:", counter)
}在这个示例中,sync.Mutex 确保只有一个 goroutine 能够在某一时刻修改 counter 变量,避免数据竞争。
总结
Go 的并发模型基于 goroutines 和 channels,它提供了简单且高效的并发编程方式。常用的并发工具包括:
- Goroutines:轻量级的线程,用于并发执行任务。
- Channels:用于 goroutine 之间的通信。
- Buffered Channels:带缓冲区的 channel,支持异步操作。
- Select:用于等待多个 channel 的操作。
- WaitGroup:用于等待多个 goroutine 完成任务。
- Mutex:防止数据竞争,保证共享资源的互斥访问。
通过这些工具,Go 可以非常简洁和高效地实现并发编程,适用于处理高并发任务。