via:
​​​https://medium.com/technofunnel/understanding-goroutine-go-channels-in-detail-9c5a28f08e0d​​​作者:Mayank Gupta

四哥水平有限,如有翻译或理解错误,烦请帮忙指出,感谢!

这篇文章源自 Medium,文章点赞 600+。

这篇文章的内容很简洁,知识点容易理解,很佩服作者能把知识点讲的通俗易懂,可能这就是文章获得高点赞的原因吧。

原文如下:


Go 语言使用 goroutine 可以轻松地实现并发。大家应该都知道,goroutine 是通过 channel 实现相互通信的。channel 确保 goroutine 和主线程时间可以相互通信。

在这篇文章中,将会与大家讨论如何创建 channel 和实现数据共享。

简单介绍下 channel

channel 给编程带来了很大的灵活性,并解决了涉及并发的各种问题。总结有如下几点:

  1. 是一种通信机制;
  2. channel 可以作为参数发送给不同的 goroutine;
  3. 既可充当发布者又可充当订阅者;

内存隔离

以前,程序通过全局变量的方式实现线程之间共享数据,我们不得不跟踪不同线程对数据的操作。

全局共享内存会导致与不同线程之间同步数据有关的诸多问题。

Go 语言通过 channel 实现了安全的数据通信,只有一个子线程可以对数据进行操作。channel 里的有效数据只能被单个 goroutine 访问。数据的接收方和发送方是一对一的关系。

第一步:创建 channel

package main
import "fmt"

func main() {
dataChannel := make(chan string)
fmt.Println(<-dataChannel)
}

我们来看下上面代码值得关注的几个细节:

  1. 使用 make 关键字创建新的对象;
  2. 需要指定使用 chan 创建的对象类型;
  3. 使用 string 指定 channel 返回的数据类型;

所以第 5 行代码,我们创建了 channel 类型的对象,goroutine 之间可以用来传递 string 类型的数据。

等待获取 channel 的数据

上面第 6 行代码,main 函数会一直等待,直到从 channel 接收到数据为止。上面的代码,没任何其他 goroutine 向 channel 发送数据,程序会在等待接收数据过程发生死锁。

我们看下输出:

Golang 入门笔记 - Channel_多线程

从运行结果可以看出,程序发生了死锁,因为 main 函数在一直等待 channel 返回数据。

第二步:往 channel 添加数据

上面的代码中,我们创建了 channel,并等待 channel 返回可用数据。由于未返回数据,程序发生死锁。

下一步,我们将在 main() 函数里为 channel 提供数据,一起来看下下面的代码:

package main

import "fmt"

func main() {
dataChannel := make(chan string)
dataChannel <- "Some Sample Data"
fmt.Println(<-dataChannel)
}

上面的代码中,往 channel 添加了一些简单的数据,现在你是不是认为可以从 channel 接收到数据呢?

一起来看下输出:

Golang 入门笔记 - Channel_go_02

从上面的输出可以看出,程序再次发生死锁。你能想出为什么吗?

上面的代码中,我们往 channel 添加数据。一旦往 channel 发送数据,主协程便发生阻塞直到有其他协程将数据从 channel 取出。由于没有其他协程将数据取出,主协程便一直阻塞,进而导致死锁。

第三步:解决死锁

需要重申一点,默认情况下,channel 是不带缓存的,这意味着数据需要被接收者立即取出,发送者协程才能继续正常工作。

如果没有协程接收数据,发送者协程将会一直阻塞。

通过带缓存区的 channel,我们可以在 channel 里存储数据,只要缓存没满,即使数据没被取出,发送者协程也可以正常工作。

我们可以使用带缓存的 channel 解决上述问题。

创建带缓存的 channel

为了保证即使在数据没被其他协程接收的情况下,发送者协程仍能正常工作,可以使用带缓存的 channel。

我们一起来看下例子:

package main

import "fmt"

func main() {
dataChannel := make(chan string, 3)
dataChannel <- "Some Sample Data"
dataChannel <- "Some Other Sample Data"
dataChannel <- "Buffered Channel"
fmt.Println(<-dataChannel)
fmt.Println(<-dataChannel)
fmt.Println(<-dataChannel)
}

上面的代码,我们创建了缓存容量为 3 的 channel,意味着该 channel 可以存储 3 个 string 值。往 channel 添加的数据可以不用立即处理,直到缓存满之前,我们可以一直往 channel 添加数据。

一旦缓存满了,我们就必须处理数据否则也会发生死锁。

因此,使用带缓存的 channel,允许协程将数据保存在 channel 中,以便在后续程序执行过程中取出,从而消除死锁。

让我们看下面的输出:

Golang 入门笔记 - Channel_多线程_03

从输出结果可以看出,死锁已经消除了。

如果我的文章对你有所帮助,点赞、转发都是一种支持!

Golang 入门笔记 - Channel_多线程_04

Golang 入门笔记 - Channel_编程语言_05