Skip to content

在Go语言中,并发编程是其重要特性之一。Go语言通过 goroutinechannel 实现轻量级的并发编程模型,允许程序高效地并行执行多个任务。Go的并发模型简单而强大,适合处理大量并发任务,是Go在服务器开发和高并发应用中的一大优势。

1. goroutine —— 轻量级线程

goroutine 是Go语言中的并发执行单元,类似于线程,但更轻量。它的调度由Go运行时管理,而不是直接依赖操作系统线程,因而可以高效地处理大量并发任务。

启动 goroutine

只需在函数调用前加上 go 关键字即可启动一个新的 goroutine

go
package main

import (
    "fmt"
    "time"
)

func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Println(i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go printNumbers()  // 启动一个新的 goroutine 执行 printNumbers
    fmt.Println("Main function")
    time.Sleep(1 * time.Second)  // 主函数等待,以便让 goroutine 有时间运行
}

在这个例子中,printNumbers 函数在新的 goroutine 中并发执行。因为 goroutine 是并发的,主程序不会等待它完成,而是继续执行主函数的代码。

2. channel —— goroutine 通信

channel 是Go语言提供的用于在 goroutine 之间通信的机制。channel 是类型安全的通信管道,可以通过它来传递数据,实现 goroutine 之间的同步和协作。

声明和初始化 channel

channel 使用 make 函数创建,声明格式如下:

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

使用 channel 进行通信

  • 发送数据:使用 <- 操作符将数据发送到 channel
  • 接收数据:同样使用 <- 操作符从 channel 接收数据。
go
package main

import "fmt"

func main() {
    ch := make(chan int)

    go func() {
        ch <- 10 // 发送数据到 channel
    }()

    value := <-ch // 从 channel 接收数据
    fmt.Println(value) // 输出:10
}

在这个示例中,主 goroutinechannel 中接收值,子 goroutinechannel 中发送值。这种模式可以实现 goroutine 之间的同步。

3. 带缓冲的 channel

Go语言的 channel 分为无缓冲和带缓冲两种。无缓冲 channel 是同步的,必须在发送和接收同时准备好时才能传递数据。带缓冲 channel 则允许异步传递数据,缓冲区未满时可以发送数据而无需接收方立即准备好。

创建带缓冲的 channel

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

示例:带缓冲的 channel

go
package main

import "fmt"

func main() {
    ch := make(chan int, 2) // 容量为 2 的带缓冲 channel

    ch <- 1 // 发送数据到缓冲区
    ch <- 2

    fmt.Println(<-ch) // 输出:1
    fmt.Println(<-ch) // 输出:2
}

在这个例子中,带缓冲的 channel 可以在没有接收方的情况下缓冲两个值,达到容量后发送操作会阻塞,直到有接收方读取数据。

4. channel 的关闭

使用 close 函数可以关闭 channel,表示没有更多数据会被发送。关闭后,可以继续从 channel 接收数据,但无法再向其发送数据。

示例:关闭 channel

go
package main

import "fmt"

func main() {
    ch := make(chan int, 2)
    ch <- 10
    ch <- 20
    close(ch) // 关闭 channel

    for value := range ch { // 使用 range 读取所有数据
        fmt.Println(value)
    }
}

在这个例子中,close(ch) 关闭了 channel,然后可以通过 range 遍历 channel 中所有未读取的数据。

5. select 语句

select 语句用于监听多个 channel 的操作,使得 goroutine 能够在多个 channel 上进行非阻塞选择。select 会阻塞直到某个 channel 有可用的操作。

示例:使用 select 监听多个 channel

go
package main

import (
    "fmt"
    "time"
)

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

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "Message from ch1"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "Message from ch2"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        }
    }
}

在这个示例中,select 会阻塞直到其中一个 channel 收到消息,然后执行相应的 caseselect 是处理多个 channel 的典型方式,适合在 goroutine 之间进行调度。

6. 并发中的同步控制

在一些情况下,可能需要更严格的同步控制,例如控制并发访问共享资源。Go标准库提供了 sync 包,其中 sync.WaitGroupsync.Mutex 是常用的同步工具。

使用 sync.WaitGroup 控制并发完成

sync.WaitGroup 用于等待一组 goroutine 执行完成。

go
package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 计数器 -1
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(1 * time.Second)
    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() // 等待所有 goroutine 完成
    fmt.Println("All workers done")
}

在这个例子中,wg.Add(1) 增加计数器,每个 goroutine 完成后通过 wg.Done() 减少计数器。wg.Wait() 阻塞主 goroutine,直到计数器归零。

使用 sync.Mutex 控制并发访问

sync.Mutex 是一种互斥锁,用于保护共享资源的访问。

go
package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock() // 加锁
    c.value++
    c.mu.Unlock() // 解锁
}

func main() {
    counter := Counter{}

    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < 1000; j++ {
                counter.Increment()
            }
        }()
    }

    wg.Wait()
    fmt.Println("Final counter value:", counter.value)
}

在这个示例中,Increment 方法使用互斥锁保护共享资源 value,避免并发写入导致的数据竞争。

评论区

欢迎留言、补充或勘误。

xiaoba.blog