Go语言协程是什么
在写网络服务或者并发任务的时候,你可能经常听到“协程”这个词。尤其是在Go语言里,它几乎是绕不开的核心特性。那Go语言的协程到底是什么?简单来说,它是轻量级的执行单元,由Go运行时管理,能让你用很少的资源同时处理成千上万个任务。
比如你开了一家奶茶店,如果每个顾客来了都要一个店员全程盯着:点单、做奶茶、打包、收钱,那得雇多少人?现实中显然不会这么做。更高效的方式是把流程拆开,几个人分工协作,一个人可以同时照看多个环节。Go的协程就类似这个思路——它让一个操作系统线程能跑很多个“小任务”,这些小任务就是协程(Goroutine)。
怎么启动一个协程?
语法极其简单。你只需要在函数调用前加个 go 关键字:
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello from goroutine")
}
func main() {
go sayHello()
time.Sleep(100 * time.Millisecond) // 等一等,不然主程序可能直接退出
fmt.Println("Main function ends")
}上面这段代码中,go sayHello() 就启动了一个协程。主函数继续往下走的同时,sayHello 也在后台执行。注意那个 time.Sleep,它只是为了防止主程序太快结束,导致协程还没来得及打印就被杀掉。
协程为什么这么轻?
传统线程由操作系统调度,创建成本高,内存占用大,一般几千个就差不多到极限了。而Go的协程由Go runtime自己管,初始栈只有几KB,按需增长或收缩。你可以轻松起十万甚至百万个协程,只要你的程序逻辑需要。
举个例子,你要做个爬虫,抓一万个网页。如果用传统线程,系统可能直接卡死。但用Go协程,写起来就像这样:
func fetch(url string) {
// 模拟网络请求
time.Sleep(time.Second)
fmt.Printf("Fetched %s\n", url)
}
func main() {
urls := []string{"http://a.com", "http://b.com", "http://c.com"} // 假设有一万个
for _, url := range urls {
go fetch(url)
}
time.Sleep(2 * time.Second)
}每一个 fetch 都在一个独立的协程里跑,互不干扰,资源消耗却很低。
协程之间怎么通信?
多个协程一起跑,总得交换数据吧?Go提倡“用通信来共享内存,而不是用共享内存来通信”。这话听着拗口,其实意思就是别乱抢变量,用 channel 来传消息。
还是拿奶茶店举例。如果多个员工都去改同一个订单状态,很容易出错。更好的方式是有个“消息板”,谁有更新就往板上贴条,其他人来看。channel 就是这块消息板。
func main() {
ch := make(chan string)
go func() {
ch <- "奶茶做好了"
}()
msg := <-ch
fmt.Println(msg)
}这里,协程往 channel 发消息,主线程从里面收。两边不用碰同一块内存,也能安全协作。
Go语言的协程不是什么神秘技术,但它改变了我们写并发程序的方式。它不靠堆硬件,而是靠设计让程序更高效。当你看到一个Go服务扛住几万并发连接时,背后往往就是成千上万个安静运行的协程在干活。