这是Go语言最耀眼的特性。PHP的并发模型通常依赖于多进程(如php-fpm
)或扩展(如Swoole
, OpenSwoole
)。而Go在语言层面提供了轻量级的并发支持。
6.1 Goroutine #
Goroutine是Go并发的执行体,可以理解为一个极其轻量级的线程。创建一个Goroutine的成本非常低(只需几KB的栈空间),你可以轻松地创建成千上万个。
要启动一个Goroutine,只需在函数调用前加上go
关键字。
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
// 启动一个新的Goroutine来执行say("world")
go say("world")
// main函数自己(在主Goroutine中)执行say("hello")
say("hello")
}
/*
可能的输出 (顺序不固定):
hello
world
world
hello
hello
world
hello
world
hello
world
*/
注意:当main
函数结束时,整个程序会立即退出,不会等待其他Goroutine执行完毕。
6.2 Channel(通道) #
如果你有多个Goroutine,它们之间如何安全地通信和同步呢?答案是Channel。Channel可以被看作是Goroutine之间通信的“管道”。
Don’t communicate by sharing memory, share memory by communicating. (不要通过共享内存来通信,而要通过通信来共享内存。) - Go的座右铭
package main
import "fmt"
func main() {
// 创建一个可以传输string类型数据的channel
messages := make(chan string)
// 启动一个Goroutine,它会向channel发送一条消息
go func() {
fmt.Println("Goroutine: sending message...")
messages <- "ping" // 使用 <- 操作符发送数据到channel
fmt.Println("Goroutine: message sent.")
}()
fmt.Println("Main: waiting for message...")
// 从channel接收数据,这个操作会阻塞,直到有数据可读
msg := <-messages
fmt.Println("Main: received message:", msg)
}
/*
输出:
Main: waiting for message...
Goroutine: sending message...
Goroutine: message sent.
Main: received message: ping
*/
Channel是类型安全的,一个chan string
的通道只能发送和接收string
类型。
6.3 使用sync.WaitGroup
进行同步
#
为了解决main
函数提前退出的问题,我们可以使用sync.WaitGroup
来等待一组Goroutine执行完毕。
Add(n)
: 增加计数器,表示要等待n个Goroutine。Done()
: Goroutine完成任务后调用,计数器减一。Wait()
: 阻塞,直到计数器归零。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
// 在函数退出时,通知WaitGroup任务已完成
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second) // 模拟工作
fmt.Printf("Worker %d done\n", id)
}
func main() {
// 创建一个WaitGroup
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
// 增加计数器
wg.Add(1)
// 启动worker Goroutine
go worker(i, &wg)
}
// 等待所有Goroutine完成
fmt.Println("Main: Waiting for workers to finish...")
wg.Wait()
fmt.Println("Main: All workers finished. Exiting.")
}
并发是Go的一个巨大主题,还包括select
语句(处理多个channel)、带缓冲的channel、互斥锁(sync.Mutex
)等。掌握了Goroutine和Channel,你就掌握了构建高性能Go应用的核心武器。