Golang 中的 Channels 使用案例剖析

1. Channels 概述

Channels 是 Go 语言并发模型中的一种重要机制,它是一种可以让不同 goroutine(轻量级线程) 间进行通信和同步的方式。基于 Channel,我们可以实现各种并发模型的设计。在 Channel 中,数据是通过管道方式进行传输的,当一个 goroutine 向 Channel 中发送数据的时候,另一个 goroutine 可以通过 Channel 接收到这个数据,反之亦然。在 Go 语言中,我们使用 chan 关键字来声明 Channel。

2. Channels 的使用场景

2.1. 简单的例子

让我们先看一下一个简单的例子,理解下 Channel 是如何工作的:

package main

import "fmt"

func goroutine(ch chan int) {

fmt.Println("Sending to channel")

ch <- 1

}

func main() {

ch := make(chan int)

go goroutine(ch)

fmt.Println("Receiving from channel")

fmt.Println(<-ch)

}

在这个例子中,我们定义了一个 goroutine 和一个主函数。在 main 函数中,我们使用 make() 函数来创建一个名为 ch 的 Channel。接着,我们使用 go 关键字来启动一个 goroutine 并且将 ch 作为它的参数传递给 goroutine 函数。在 goroutine 函数中,我们将 1 发送到了 Channel 中。最后,在主函数中,我们通过 <-ch 语法来从 Channel 中接收到了这个数。输出结果如下:

Sending to channel

Receiving from channel

1

可以看到,当我们从 Channel 中接收数据时,程序会阻塞直到数据被发送到 Channel 中。同样的,在 Channel 中发送数据时,程序也会被阻塞直到数据被接收。

2.2. 使用 Channel 进行任务分配

另外一个惯用的 Channel 用法是使用它进行任务分配。例如,当我们需要处理大量数据集合时,我们可以将这些数据分配给多个 goroutine,每个 goroutine 处理其中的一部分数据,最后将它们的处理结果汇总。

package main

import (

"fmt"

"math/rand"

"sync"

"time"

)

func worker(id int, jobs <-chan int, results chan<- int) {

for j := range jobs {

fmt.Printf("Worker %d started job %d\n", id, j)

time.Sleep(time.Duration(rand.Intn(3)) * time.Second)

fmt.Printf("Worker %d finished job %d\n", id, j)

results <- j * 2

}

}

func main() {

const numJobs = 5

jobs := make(chan int, numJobs)

results := make(chan int, numJobs)

for w := 1; w <= 3; w++ {

go worker(w, jobs, results)

}

for j := 1; j <= numJobs; j++ {

jobs <- j

}

close(jobs)

var wg sync.WaitGroup

wg.Add(numJobs)

go func() {

for r := range results {

fmt.Printf("Result is: %v.\n", r)

wg.Done()

}

}()

wg.Wait()

}

在这个例子中,我们定义了一个 worker 函数,它负责处理由 jobs Channel 中传入的数据,并将处理结果发送到 results Channel 中。在 main 函数中,我们使用 make() 函数创建了两个 Channel:jobs 和 results。接着,我们启动了三个 goroutine 并将 jobs 和 results 分别作为参数传递给它们。然后,我们向 jobs Channel 中发送了 5 条数据。当所有的任务都被添加到 jobs Channel 中之后,我们关闭了它。最后,我们使用 sync.WaitGroup 来等待所有的结果处理完毕。

2.3. 使用 Channel 进行数据传输

另外一个使用 Channel 的场景是在多个 goroutine 之间传输数据。在这种情况下,数据会同时存在于发送者和接收者中,因此我们需要选择合适的 Channel 实现来避免数据竞争和死锁。

package main

import (

"fmt"

"sync"

)

func main() {

var wg sync.WaitGroup

ch := make(chan int)

wg.Add(1)

go func(ch <-chan int) {

// 持续读取 Channel 中的数据,直到 Channel 被关闭

for {

if value, ok := <-ch; ok {

fmt.Println("Value received: ", value)

} else {

wg.Done()

return

}

}

}(ch)

ch <- 10

ch <- 20

close(ch)

wg.Wait()

}

在这个例子中,我们创建了一个名为 ch 的 Channel。接着,我们启动了一个 goroutine,并将 ch 作为参数传递给它。在 goroutine 中,我们使用 for 循环来持续读取从 Channel 接收到的数据,直到 Channel 被关闭为止。在主函数中,我们向 Channel 中发送了两个数(10 和 20)。最后,我们关闭了 Channel 并使用 sync.WaitGroup 来等待所有的 goroutine 执行完毕。

3. 总结

在 Go 语言中,Channels 是一个非常重要的并发机制,它能够让我们实现各种不同的并发计算模型。在本文中,我们介绍了 Channels 的基础概念、使用场景以及如何避免数据竞争和死锁。如果您最近在学习 Go 语言并发编程,那么这篇文章可以帮助您更好地理解 Channel 的使用方法。

后端开发标签