Golang 高并发| 青训营

202 阅读4分钟

Golang 高并发

Go语言这门语言在并发处理方面非常强大,对于高性能、高并发、分布式、网络编程等领域有着广泛的应用。本文将介绍Golang的高并发实现,包括如何使用goroutine、channel、sync包和context包等。

Goroutine

Goroutine 是 Go 语言中一个非常重要的概念,它是轻量级的线程,通常由 Go 运行时管理。我们可以在一个程序中运行非常多的 goroutine,每个 goroutine 的成本非常小,只有几 KB 的内存。而在某些情况下,程序在运行时也会根据需要自动地增加或减少 goroutine 的数量,以维持高效的性能。

下面是一个使用 goroutine 的示例:

package main

import (
    "fmt"
    "time"
)

func main() {
    go printHello() // 启动一个新的 goroutine 执行 printHello 函数

    fmt.Println("main 函数")
    time.Sleep(time.Second) // 阻塞 main goroutine
}

func printHello() {
    fmt.Println("Hello, World!")
}

通过 go 关键字启动一个 goroutine,程序会继续往下执行,而不会等待 goroutine 执行完毕。在上面的例子中,main 函数和 printHello 函数可以并发执行。

Channel

Channel 是 Go 语言内置的一种数据结构,用于在 goroutine 之间进行通信和同步。它类似于 Unix 中的管道(pipe),用于连接输入和输出。通过通道,一个 goroutine 可以发送数据到另一个 goroutine,也可以从别的 goroutine 中接收数据。

下面是一个使用 channel 的示例:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int) // 创建一个整型的通道

    go func() {
        for i := 0; i < 5; i++ {
            ch <- i // 发送数据到通道中
            time.Sleep(time.Second) // 阻塞 1s
        }
    }()

    for i := 0; i < 5; i++ {
        fmt.Println(<-ch) // 从通道中读取数据
    }
}

在上面的例子中,我们创建了一个包含 5 个元素的整型通道,然后启动了一个 goroutine,在其中向通道中发送数据。在主 goroutine 中,我们通过 <-ch 语法从通道中读取数据,并打印出来。由于发送数据的 goroutine 每隔 1 秒钟向通道中发送一条数据,因此我们可以看到,程序打印出了 0 到 4 这五个数字。

sync 包

sync 包提供了一些常用的同步原语,如互斥锁、读写锁、信号量等。使用 sync 包可以更方便地实现并发应用中的同步和互斥。下面是一个使用 sync.Mutex 实现互斥锁的示例:

package main

import (
    "fmt"
    "sync"
)

var count int
var mutex sync.Mutex // 定义一个互斥锁

func main() {
    var wg sync.WaitGroup // 定义一个 WaitGroup,用于等待所有 goroutine 执行完毕
    for i := 0; i < 10; i++ {
        wg.Add(1) // 增加 WaitGroup 的计数器
        go increment(&wg) // 启动一个 goroutine 执行 increment 函数
    }
    wg.Wait() // 等待所有 goroutine 执行完毕
    fmt.Println(count)
}

func increment(wg *sync.WaitGroup) {
    defer wg.Done() // 减少 WaitGroup 的计数器
    mutex.Lock() // 加锁
    count++
    mutex.Unlock() // 解锁
}

在上面的例子中,我们定义了一个 count 变量,同时也定义了一个 Mutex 互斥锁。在 increment 函数中,我们通过 mutex.Lock() 和 mutex.Unlock() 来保证 count 变量的并发安全性。同时,为了保证所有 goroutine 的执行完毕,我们使用了 sync.WaitGroup 来等待所有 goroutine 执行完毕。

context 包

context 包是 Go 1.7 中引入的一个新包,用于在不同 goroutine 之间传递请求特定操作所需的数据、取消信号,以及处理请求的截止期限。在使用 goroutine 进行并发操作时,需要注意一些问题,比如如何正确地取消一个 goroutine、如何正确地处理 goroutine 并发执行时的错误等。context 包提供了一些工具方法来解决这些问题。

下面是一个使用 context 包取消 goroutine 的示例:

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background()) // 创建一个上下文对象和取消函数

    go func() {
        for {
            select {
            case <-ctx.Done(): // 当接收到取消信号时,退出循环
                fmt.Println("goroutine canceled")
                return
            default:
                fmt.Println("working...")
                time.Sleep(time.Second) // 后台工作
            }
        }
    }()

    time.Sleep(time.Second * 3)
    cancel() // 发送取消信号
    time.Sleep(time.Second * 1)
    fmt.Println("main function exited")
}

在上面的例子中,我们首先使用 context.WithCancel 创建了一个上下文对象和对应的取消函数。然后启动了一个 goroutine,在其中不断执行某些后台工作。当接收到取消信号时,通过 ctx.Done() 接收到一个通知信号,从而退出循环。最后在主函数中发送取消信号,来取消 goroutine 的执行。

总结

本文介绍了在 Golang 中实现高并发的几种常用方式,包括使用 goroutine、channel、sync 包和 context 包等。这些方式各具特点,可以根据实际情况选择相应的方式来解决问题。