Go语言中的并发:goroutine、channel与async/await(对比)

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么Go在并发方面超越了所有对手?

\n\n

想象一位同时烹饪十道菜的大厨。他并不是在炉灶之间疯狂奔跑——他只是高效地切换任务:炖肉的时候切沙拉;水烧开的时候摆盘。在编程世界里,这被称为并发——一种无需等待每个任务完成就能管理多个任务的能力。

\n\n

Go语言(Golang)正是为解决并发问题而生的。它的Google创建者们厌倦了C++和Java中多线程的复杂性。他们发明了goroutine——一种轻量级线程,只需一行代码即可启动。在本文中,我们将深入探讨Go中的并发工作原理,将其与操作系统线程以及Python的async/await范式进行比较,更重要的是,我们将展示您今天就能使用的真实代码示例。

\n\n

Goroutine vs 操作系统线程:轻骑兵之战

\n\n

当开发者听到“多线程”时,通常会想到操作系统线程。但在Go中,情况完全不同。让我们分析一下关键区别。

\n\n

什么是操作系统线程(Thread)?

\n

线程是由操作系统内核管理的重量级对象。每个线程都有自己的栈(通常为1-8 MB),创建和切换都需要系统调用。创建10,000个线程就意味着耗尽内存和CPU。Go采用了不同的理念。

\n\n

Goroutine——Go的轻量级线程

\n

Goroutine是一个并发执行的函数。它通过关键字go启动。主要特点:

\n
    \n
  • 栈大小:从2-4 KB开始,并动态增长/收缩。
  • \n
  • 创建:数千个goroutine可在毫秒内创建。
  • \n
  • 调度器:Go运行时自动将goroutine分配到操作系统线程上(通常数量等于CPU核心数)。
  • \n
  • 语法:只需在函数调用前添加go。
  • \n
\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc sayHello(name string) {\n    for i := 0; i < 3; i++ {\n        fmt.Println("你好,", name)\n        time.Sleep(100 * time.Millisecond)\n    }\n}\n\nfunc main() {\n    // 启动两个goroutine\n    go sayHello("Go")\n    go sayHello("Goroutine")\n\n    // 给goroutine时间执行\n    time.Sleep(1 * time.Second)\n    fmt.Println("主函数执行完毕")\n}
\n\n

结果:两个goroutine的消息会交错输出。Go会自动在它们之间切换,互不阻塞。尝试运行这段代码——您会看到“你好,Go”和“你好,Goroutine”交替输出。

\n\n

Channel:主要的同步工具

\n\n

Goroutine很棒,但它们如何通信?Go默认没有共享内存。相反,它遵循的原则是:“不要通过共享内存来通信;而要通过通信来共享内存”。为此,Go提供了channel(通道)。

\n\n

Channel是一个类型化的“管道”,goroutine通过它发送和接收数据。这安全、简单且优雅。

\n\n

示例:并行处理数字

\n
package main\n\nimport "fmt"\n\nfunc squareWorker(numbers <-chan int, results chan<- int) {\n    for n := range numbers {\n        results <- n * n\n    }\n    close(results)\n}\n\nfunc main() {\n    numbers := make(chan int, 5)\n    results := make(chan int, 5)\n\n    // 启动worker\n    go squareWorker(numbers, results)\n\n    // 发送数据\n    numbers <- 2\n    numbers <- 4\n    numbers <- 6\n    close(numbers)\n\n    // 读取结果\n    for res := range results {\n        fmt.Println("平方值:", res)\n    }\n}
\n\n

在这个示例中,我们使用了带缓冲的channel(第二个参数是缓冲区大小)。Worker goroutine从numbers channel中读取数据,计算平方,然后将结果发送到results channel。主函数发送数字后关闭输入channel,然后从结果channel中读取所有输出。

博客

书籍推荐