Go 语言的协程(goroutine)是轻量级的并发机制,可以理解为 Go 中的线程,但它比线程更轻量,且由 Go 语言的运行时调度器管理。下面详细说明 Go 协程的原理、使用方法以及应用场景。
Go 协程(goroutine)在运行时由 Go 调度器管理,其运行时模型不同于传统的操作系统线程。Go 的调度器采用 M
模型,即多个 goroutine 由少量的线程管理和调度。它依赖于两个关键组件:
每个 Go 协程有很小的初始栈(通常只有几 KB),当需要时栈可以动态增长,这使得在一个程序中可以轻松创建数以万计的协程。
调度器通过工作窃取、抢占式调度等技术,使得协程的切换和调度更加高效。它避免了传统线程中由于频繁切换上下文而产生的性能开销。
在 Go 中,启动一个协程非常简单,只需要使用 go
关键字即可。基本语法如下:
go functionName(params)
下面是一个简单的示例,启动两个协程并发执行任务:
package main
import (
"fmt"
"time"
)
func printNumbers() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
time.Sleep(1 * time.Second)
}
}
func printLetters() {
for i := 'a'; i <= 'e'; i++ {
fmt.Printf("%c\n", i)
time.Sleep(1 * time.Second)
}
}
func main() {
go printNumbers()
go printLetters()
// 主协程等待 6 秒,以确保其他协程执行完毕
time.Sleep(6 * time.Second)
fmt.Println("Done")
}
在这个例子中,printNumbers()
和 printLetters()
两个函数将并发执行,主程序会在等待 6 秒后输出 "Done"。通过 go
关键字,函数被作为协程运行。
由于协程是并发运行的,通常需要一些同步机制来确保数据的一致性。Go 提供了多种方式来处理并发问题,其中最常用的是:
通道的基本用法如下:
package main
import (
"fmt"
)
func sum(a, b int, ch chan int) {
ch <- a + b
}
func main() {
ch := make(chan int)
go sum(2, 3, ch)
result := <-ch
fmt.Println("Result:", result)
}
在这个例子中,主协程通过通道 ch
接收子协程 sum()
的结果,从而保证了两个协程之间的同步。
sync.WaitGroup
:可以等待一组协程完成任务后再继续执行。package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait() // 等待所有 worker 完成
fmt.Println("All workers done")
}
通过 sync.WaitGroup
,可以确保所有 worker
协程执行完成后,主协程再继续执行。
协程由于其轻量级和高效的并发机制,在以下场景中具有广泛应用:
I/O 密集型任务:处理大量网络请求、文件读写等 I/O 操作时,协程非常适合。协程不会因为等待 I/O 操作而阻塞,能更好地利用资源。
示例:实现 HTTP 服务器,每个请求由独立的协程处理:
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
并行计算:在 CPU 密集型任务中,可以使用多个协程来并行处理数据,提高计算效率。
示例:并行处理一个数组中的数据:
package main
import (
"fmt"
"sync"
)
func worker(id int, numbers []int, wg *sync.WaitGroup) {
defer wg.Done()
sum := 0
for _, num := range numbers {
sum += num
}
fmt.Printf("Worker %d: sum = %d\n", id, sum)
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var wg sync.WaitGroup
wg.Add(2)
go worker(1, numbers[:5], &wg)
go worker(2, numbers[5:], &wg)
wg.Wait()
}
实时数据处理:例如流式处理系统中,协程可用于实时消费消息队列中的数据并进行处理。