Go语言 通道的用途是什么?

iqih9akk  于 2022-12-07  发布在  Go
关注(0)|答案(4)|浏览(155)

在查看一些Go语言代码时,我发现了以下代码:

ch := make(chan int)

我在一个在线教程中查找了Go Channels是如何工作的:
https://tour.golang.org/concurrency/2
但我觉得这个例子不清楚。
有人能给予我一个简单的解释和使用通道的例子吗?

ru9i0ody

ru9i0ody1#

chan是Golang的一个频道,简单的说,你可以把它想象成一个盒子,你把一个东西放在盒子的一端,然后从另一端拿出来。
无缓冲通道

缓冲通道

这是我为你写的一小段代码,让你了解通道。现在改变go例程的顺序,看看输出。每次的输出可能不同。

package main

    import (
        "fmt"
        "time"
    )

    func main() {
        messages := make(chan int)
        go func() {
            time.Sleep(time.Second * 3)
            messages <- 1
        }()
        go func() {
            time.Sleep(time.Second * 2)
            messages <- 2
        }() 
        go func() {
            time.Sleep(time.Second * 1)
            messages <- 3
        }()
        go func() {
            for i := range messages {
                fmt.Println(i)
            }
        }()
        go func() {
            time.Sleep(time.Second * 1)
            messages <- 4
        }()
        go func() {
            time.Sleep(time.Second * 1)
            messages <- 5
        }()
        time.Sleep(time.Second * 5)
    }

为了更好地理解,请访问这个博客,其中介绍了GUI中的go例程和通道。
访视http://divan.github.io/posts/go_concurrency_visualize/

ercv8c1e

ercv8c1e2#

我认为规范对此非常明确。规范:通道类型:
通道提供了一种机制,用于通过发送和接收指定元素类型的值来并发执行函数以进行通信。
当你有多个同时执行的goroutine时,通道提供了最简单的方式让goroutine之间进行通信。
一种通信方式是通过一个“共享”变量,这个变量对两个goroutine都是可见的,但这需要适当的锁定/同步访问。
相反,围棋偏爱渠道。通过交流分享:
不通过共享内存进行通信;而是通过通信来共享存储器。
因此,你可以创建一个通道(对两个goroutine都是可见的),而不是把消息放到一个共享的切片中。在没有任何外部同步/锁的情况下,一个goroutine可以通过这个通道发送消息(值),而另一个goroutine可以接收它们。
在任何给定的时间,只有一个goroutine可以访问这个值,数据竞争是不会发生的。
因此,实际上,任何数量的goroutine都可以在同一个通道上发送值,也可以从同一个通道上接收值,而不需要任何进一步的同步。If I am using channels properly should I need to use mutexes?

通道示例

下面我们来看一个例子,我们启动了两个额外的goroutine来进行并发计算,我们将一个数字传递给第一个goroutine,第一个goroutine将这个数字加1,然后将结果传递给第二个goroutine,第二个goroutine将接收到一个数字,将这个数字乘以10,然后将结果传递给结果通道:

func AddOne(ch chan<- int, i int) {
    i++
    ch <- i
}

func MulBy10(ch <-chan int, resch chan<- int) {
    i := <-ch
    i *= 10
    resch <- i
}

以下是它的调用/使用方式:

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

    go AddOne(ch, 9)
    go MulBy10(ch, resch)

    result := <-resch
    fmt.Println("Result:", result)
}

通过通道进行通信也会考虑goroutine之间的等待,在这个例子中,这意味着MulBy10()会等待AddOne()发送递增后的数字,而main()会等待MulBy10()之后再打印结果。输出如预期的结果(在Go Playground上试试):

Result: 100

语言支持

有许多语言结构可方便地使用通道,例如:

  • 通道上的for ... range循环处理从通道接收的值,直到通道关闭。
  • select语句可用于列出多个通道操作,例如 * 在通道上发送 * 和 * 从通道接收 *,并且将选择可以继续而不阻塞的操作(如果有多个可以继续的操作,则随机选择;并且如果没有就绪则将阻塞)。
  • 有一种特殊形式的接收运算符,它允许您检查通道是否已关闭(除了接收值之外):v, ok := <-ch
  • 内置的len()函数告诉队列中的元素数(未读);构建的cap()函数返回信道缓冲器容量。

其他用途

更实际的例子是如何使用通道来实现worker pool。类似的用法是从producer to consumer(s)分配值。
另一个实际示例是实现memory pool using buffered channels
另一个实际的例子是一个优秀的代理实现。
通道通常用于超时一些阻塞操作,利用time.After()返回的通道,该通道在指定的延迟/持续时间后“触发”(“触发”意味着将在其上发送一个值)。请参见以下示例进行演示(在Go Playground上尝试):

ch := make(chan int)

select {
case i := <-ch:
    fmt.Println("Received:", i)
case <-time.After(time.Second):
    fmt.Println("Timeout, no value received")
}

它可以用来等待某个值的最长等待时间,但是如果其他goroutine在这个时间内不能提供这个值,我们可以决定做其他事情来代替。
另外,一种特殊的通信形式可能只是发出一些操作完成的信号(实际上不发送任何“有用”的数据)。这种情况可以通过一个具有任何元素类型的通道实现,例如chan int,并在其上发送任何值,例如0。但由于发送的值不包含任何信息,因此您可以像chan struct{}那样声明它。或者更好,如果你只需要一个一次性的信令,你可以关闭通道,使用for ... range在另一端截取它,或者从它接收(因为从一个关闭的通道接收会立即进行,产生元素类型的零值)。还要知道,即使一个通道可以用于这种信令,也有一个更好的选择:sync.WaitGroup.

进一步阅读

为了避免令人惊讶的行为,有必要了解渠道原则:How does a non initialized channel behave?
The Go Blog: Share Memory By Communicating
The Go Blog: Go Concurrency Patterns: Pipelines and cancellation
The Go Blog: Advanced Go Concurrency Patterns
Ardan labs: The Nature Of Channels In Go显示器

gtlvzcf8

gtlvzcf83#

这一概念与Unix/Linux从一开始就有的概念非常相似:* 管道。*
这是一个可靠的进程间/线程间通信工具,内置于该语言中。非常方便。

9w11ddsr

9w11ddsr4#

如果你想让goroutine之间相互发送信号,可以使用通道。
1.给另一个goroutine发信号,让它开始执行任务。
1.等待其他goroutine结束任务。
1.通过关闭通道来通知其他goroutine停止工作。这里还有其他类似的场景。https://www.ardanlabs.com/blog/2017/10/the-behavior-of-channels.html
如果你想让goroutine进行通信,可以使用通道,也可以不使用数据。

相关问题