Go语言以其简洁的语法和强大的并发支持,成为开发高性能分布式系统、网络服务等领域的热门选择。本文将从基础到高级逐步讲解Go语言中的并发编程实践,帮助读者深入理解如何高效地利用Go语言的并发模型。
Goroutines是Go语言实现轻量级线程的核心机制。它允许开发者以极低的成本启动数千甚至上万个并发任务。通过go
关键字即可创建一个Goroutine。
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
在上面的例子中,say("world")
被作为一个独立的Goroutine运行,而say("hello")
则在主线程中执行。
Channels用于Goroutines之间的通信。它提供了一种安全的方式来传递数据,并且内置了阻塞机制,使得同步变得简单。
func sum(nums []int, c chan int) {
sum := 0
for _, num := range nums {
sum += num
}
c <- sum // 将结果发送到channel
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
c := make(chan int, 2)
go sum(nums[:len(nums)/2], c)
go sum(nums[len(nums)/2:], c)
x, y := <-c, <-c // 从channel接收数据
fmt.Println(x, y, x+y)
}
在这个例子中,我们将数组分为两部分分别求和,并通过两个Goroutines并发计算,最后通过Channel收集结果。
工作者池是一种常见的并发模式,适用于需要处理大量任务但资源有限的情况。
graph TD; A[Main] --> B[Create Task]; B --> C[Add to Channel]; C --> D[Workers]; D --> E[Process Task]; E --> F[Return Result];
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d started job %d\n", id, j)
time.Sleep(time.Second)
results <- j * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
for w := 1; w <= 3; w++ { // 创建3个工人
go worker(w, jobs, results)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= numJobs; a++ {
fmt.Println(<-results)
}
}
在并发编程中,超时控制是非常重要的,可以避免程序陷入无限等待状态。
select {
case res := <-result:
fmt.Println("Result:", res)
case <-time.After(1 * time.Second):
fmt.Println("Timed out!")
}
sync.WaitGroup
sync.WaitGroup
可以帮助我们管理多个Goroutines的生命周期,确保所有Goroutines完成后再继续执行后续代码。
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println("Task", i)
}(i)
}
wg.Wait()
fmt.Println("All tasks done.")
Go提供了丰富的性能分析工具,如pprof
,可以帮助开发者定位瓶颈并优化程序。
go tool pprof http://localhost:6060/debug/pprof/profile