Skip to content

go 并发

Go 语言的并发支持是其一大特色,Go 通过 goroutineschannels 提供了轻量级且高效的并发编程模型。以下是 Go 并发相关的核心概念和常用技术的详细介绍。

1. Goroutines

Goroutine 是 Go 语言的并发执行单元,它是由 Go 运行时管理的轻量级线程。启动一个新的 goroutine 非常简单,只需要在函数调用前加上 go 关键字。

启动 Goroutine

go
go myFunction()  // 启动一个新的 goroutine 执行 myFunction

示例:并发执行多个任务

go
package main

import (
    "fmt"
    "time"
)

func task(id int) {
    fmt.Printf("Task %d started\n", id)
    time.Sleep(2 * time.Second)
    fmt.Printf("Task %d completed\n", id)
}

func main() {
    for i := 1; i <= 3; i++ {
        go task(i)  // 启动 goroutine 执行 task
    }

    // 等待一段时间让 goroutines 完成工作
    time.Sleep(3 * time.Second)
}

在这个示例中,task 函数被并发地执行了三次,每个任务会睡眠 2 秒钟,模拟一个耗时操作。

2. Channels

Go 的 channel 是一个用来在多个 goroutine 之间传递数据的管道。通过 channels,可以避免数据竞争和共享内存的复杂性。

创建 Channel

go
ch := make(chan int)  // 创建一个传递 int 类型的 channel

发送数据到 Channel

go
ch <- 42  // 发送数据到 channel

从 Channel 接收数据

go
value := <-ch  // 从 channel 接收数据

示例:通过 Channel 协调并发任务

go
package main

import (
    "fmt"
    "time"
)

func worker(id int, ch chan string) {
    fmt.Printf("Worker %d started\n", id)
    time.Sleep(2 * time.Second)
    ch <- fmt.Sprintf("Worker %d finished", id)  // 向 channel 发送数据
}

func main() {
    ch := make(chan string)  // 创建一个 channel

    for i := 1; i <= 3; i++ {
        go worker(i, ch)  // 启动并发任务
    }

    // 从 channel 接收数据
    for i := 1; i <= 3; i++ {
        fmt.Println(<-ch)  // 等待并接收 worker 的结果
    }
}

在上面的代码中,worker 函数将任务的执行结果通过 channel 发送到主 goroutine,主 goroutine 会接收这些结果并输出。

3. Buffered Channels (缓冲区 Channel)

缓冲区 channel 与普通 channel 的区别在于,缓冲区 channel 允许存储一定数量的元素,而不需要立即等待接收方读取数据。这样可以增加并发度,避免阻塞。

创建缓冲区 Channel

go
ch := make(chan int, 3)  // 创建一个容量为 3 的缓冲区 channel

示例:使用缓冲区 Channel

go
package main

import "fmt"

func main() {
    ch := make(chan int, 3)  // 创建一个缓冲区大小为 3 的 channel

    // 向 channel 发送数据
    ch <- 1
    ch <- 2
    ch <- 3

    // 读取并输出数据
    fmt.Println(<-ch)  // 输出: 1
    fmt.Println(<-ch)  // 输出: 2
    fmt.Println(<-ch)  // 输出: 3
}

在这个示例中,缓冲区 channel 能够存储 3 个元素,当它满了时,发送操作会被阻塞,直到有接收方从 channel 中读取数据。

4. Select

select 语句允许我们同时监听多个 channel,类似于 switch 语句。它可以用于多个 goroutine 间的同步和通讯。select 会阻塞直到有一个 channel 准备好。

示例:使用 Select 处理多个 Channel

go
package main

import "fmt"

func sendData(ch chan string, message string) {
    ch <- message
}

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go sendData(ch1, "Hello from ch1")
    go sendData(ch2, "Hello from ch2")

    // 使用 select 同时等待两个 channel 中的数据
    select {
    case msg1 := <-ch1:
        fmt.Println("Received:", msg1)
    case msg2 := <-ch2:
        fmt.Println("Received:", msg2)
    }
}

在这个示例中,select 同时监听 ch1ch2 两个 channel,谁先收到数据,谁就执行相应的 case 语句。这样,Go 会优先处理第一个可用的 channel。

5. WaitGroup

sync.WaitGroup 是 Go 中用于等待多个 goroutine 执行完成的工具。它提供了计数器,可以对多个并发任务进行等待。

示例:使用 WaitGroup 等待多个 Goroutine 完成

go
package main

import (
    "fmt"
    "sync"
)

func task(id int, wg *sync.WaitGroup) {
    defer wg.Done()  // goroutine 完成时,调用 Done 减少计数
    fmt.Printf("Task %d started\n", id)
}

func main() {
    var wg sync.WaitGroup

    // 启动多个 goroutine
    for i := 1; i <= 3; i++ {
        wg.Add(1)  // 增加计数器
        go task(i, &wg)
    }

    wg.Wait()  // 等待所有 goroutine 完成
    fmt.Println("All tasks completed")
}

在这个示例中,WaitGroup 用来确保主程序等待所有的 goroutine 执行完成后再继续执行。

6. Mutex (互斥锁)

Go 提供了 sync.Mutex 用于控制对共享资源的访问,以防止数据竞争。互斥锁确保在同一时刻只有一个 goroutine 可以访问某些共享数据。

示例:使用 Mutex 防止数据竞争

go
package main

import (
    "fmt"
    "sync"
)

var (
    counter int
    mu      sync.Mutex  // 定义互斥锁
)

func increment() {
    mu.Lock()         // 加锁
    counter++         // 修改共享资源
    mu.Unlock()       // 解锁
}

func main() {
    var wg sync.WaitGroup

    // 启动多个 goroutine 修改共享资源
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }

    wg.Wait()  // 等待所有 goroutine 完成
    fmt.Println("Final counter value:", counter)
}

在这个示例中,sync.Mutex 确保只有一个 goroutine 能够在某一时刻修改 counter 变量,避免数据竞争。

总结

Go 的并发模型基于 goroutines 和 channels,它提供了简单且高效的并发编程方式。常用的并发工具包括:

  • Goroutines:轻量级的线程,用于并发执行任务。
  • Channels:用于 goroutine 之间的通信。
  • Buffered Channels:带缓冲区的 channel,支持异步操作。
  • Select:用于等待多个 channel 的操作。
  • WaitGroup:用于等待多个 goroutine 完成任务。
  • Mutex:防止数据竞争,保证共享资源的互斥访问。

通过这些工具,Go 可以非常简洁和高效地实现并发编程,适用于处理高并发任务。