Golang go 关键字
最后修改于 2025 年 5 月 7 日
本教程将介绍如何在 Go 中使用 go 关键字。我们将通过并发执行的实际示例涵盖 goroutine 的基础知识。
go 关键字启动一个新的 goroutine,这是一个由 Go 运行时管理的轻量级线程。Goroutines 能够实现函数的并发执行。
在 Go 中,go 用于函数调用之前,以并发方式执行它们。Goroutines 比操作系统线程更高效,并能实现高度并发的程序。
基本的 goroutine 示例
go 最简单的用法是创建一个函数调用的 goroutine。此示例演示了并发执行。
注意: 使用 time.Sleep 来等待 goroutines 只适用于简单的演示。在生产代码中,请使用 sync.WaitGroup 或 channels 进行proper synchronization。
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello from goroutine")
}
func main() {
go sayHello()
time.Sleep(100 * time.Millisecond)
fmt.Println("Hello from main")
}
sayHello 函数与主函数并发运行。我们使用 time.Sleep 来等待 goroutine 完成。
多个 goroutines
我们可以创建多个 goroutines 来并发执行函数。此示例显示了三个 goroutines 同时运行。
package main
import (
"fmt"
"time"
)
func worker(id int) {
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
for i := 1; i <= 3; i++ {
go worker(i)
}
time.Sleep(2 * time.Second)
fmt.Println("All workers completed")
}
每个 worker goroutine 独立运行。主函数中的 sleep 确保所有 goroutines 在程序退出前完成。
匿名函数 goroutines
我们可以从匿名函数创建 goroutines。这对于快速并发操作很有用。
package main
import (
"fmt"
"time"
)
func main() {
go func() {
fmt.Println("Running in goroutine")
}()
time.Sleep(100 * time.Millisecond)
fmt.Println("Running in main")
}
匿名函数与主函数并发执行。这种模式对于短暂的 goroutines 很常见。
带参数的 Goroutines
我们可以像普通函数调用一样为 goroutines 传递参数。此示例演示了参数传递。
package main
import (
"fmt"
"time"
)
func printMessage(msg string) {
fmt.Println(msg)
}
func main() {
go printMessage("First message")
go printMessage("Second message")
time.Sleep(100 * time.Millisecond)
fmt.Println("Main message")
}
两个 goroutines 都正常接收它们的参数。由于调度原因,输出顺序在每次运行时可能会有所不同。
Goroutines 和共享内存
Goroutines 可以访问共享变量,但这需要同步来防止竞态条件。如果没有同步,多个 goroutines 同时修改共享变量可能导致不可预测的行为。一种确保线程安全的方法是使用 sync.Mutex。
sync.Mutex(互斥锁)确保一次只有一个 goroutine 可以修改共享变量。在访问共享资源之前必须锁定 mutex,之后再解锁。这可以防止并发 goroutines 相互干扰,并确保结果的一致性。
package main
import (
"fmt"
"sync"
)
var counter int
var mutex sync.Mutex // Declare a mutex
func increment() {
mutex.Lock() // Acquire the lock before modifying the shared variable
counter++
fmt.Println("Incremented to", counter)
mutex.Unlock() // Release the lock after modification
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait() // Wait for all goroutines to complete
fmt.Println("Final counter:", counter)
}
使用 sync.Mutex 可以防止竞态条件,确保一次只有一个 goroutine 可以修改 counter。关键特性包括:
- 使用
mutex.Lock和mutex.Unlock在修改counter时确保互斥。 - 添加
sync.WaitGroup确保程序在打印最终值之前等待所有 goroutines 完成执行。 - 使用
defer wg.Done确保每个 goroutine 都发出其完成信号。
如果没有 proper synchronization,多个 goroutines 可能会同时尝试修改 counter,导致意外的结果,例如不正确的最终值或不一致的增量。这被称为竞态条件,其结果取决于并发执行的不可预测的时序。
使用 sync.Mutex 可以确保对共享资源的更新是安全的,从而使代码在多次执行时都可靠且一致。
使用 WaitGroup 等待 Goroutines
sync.WaitGroup 提供了一种比 sleep 更好的等待 goroutines 的方法。此示例演示了 proper synchronization。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
// Simulate work
time.Sleep(200 * time.Millisecond)
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()
fmt.Println("All workers completed")
}
WaitGroup 跟踪 goroutine 的完成。Add 增加计数器,Done 减少计数器,Wait 阻塞直到计数器变为零。
带 channels 的 Goroutines
Channels 提供了 goroutines 之间的安全通信。此示例展示了 goroutines 发送和接收数据。
package main
import "fmt"
func produceMessages(ch chan<- string) {
for i := 0; i < 5; i++ {
ch <- fmt.Sprintf("Message %d", i)
}
close(ch)
}
func main() {
ch := make(chan string)
go produceMessages(ch)
for msg := range ch {
fmt.Println("Received:", msg)
}
fmt.Println("All messages received")
}
生产者 goroutine 通过 channel 发送消息。主 goroutine 接收它们,直到 channel 被关闭。
来源
本教程通过 goroutine 的创建和管理的实际示例,讲解了 Go 中的 go 关键字。