第六章:Go的杀手锏——并发编程

这是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应用的核心武器。