golang中channels的本质详解,经典!

原文:https://www.goinggo.net/2014/02/the-nature-of-channels-in-go.html

The Nature Of Channels In Go

这篇文章关于channel讲解得非常好,深度形象。深入浅出。尤其是这两幅图,太厉害了。非常清楚,一目了然。

----------------------------------------------------------------------------------------------------------------------------------------

Introduction
In my last post called Concurrency, Goroutines and GOMAXPROCS, I set the stage for talking about channels. We discussed what concurrency was and how goroutines played a role. With that foundation in hand, we can now understand the nature of channels and how they can be used to synchronize goroutines to share resources in a safe, less error prone and fun way.

What Are Channels
Channels are type safe message queues that have the intelligence to control the behavior of any goroutine attempting to receive or send on it. A channel acts as a conduit between two goroutines and will synchronize the exchange of any resource that is passed through it. It is the channel’s ability to control the goroutines interaction that creates the synchronization mechanism. When a channel is created with no capacity, it is called an unbuffered channel. In turn, a channel created with capacity is called a buffered channel.

To understand what the synchronization behavior will be for any goroutine interacting with a channel, we need to know the type and state of the channel. The scenarios are a bit different depending on whether we are using an unbuffered or buffered channel, so let’s talk about each one independently.

Unbuffered Channels
Unbuffered channels have no capacity and therefore require both goroutines to be ready to make any exchange. When a goroutine attempts to send a resource to an unbuffered channel and there is no goroutine waiting to receive the resource, the channel will lock the sending goroutine and make it wait. When a goroutine attempts to receive from an unbuffered channel, and there is no goroutine waiting to send a resource, the channel will lock the receiving goroutine and make it wait.

In the diagram above, we see an example of two goroutines making an exchange using an unbuffered channel. In step 1, the two goroutines approach the channel and then in step 2, the goroutine on the left sticks his hand into the channel or performs a send. At this point, that goroutine is locked in the channel until the exchange is complete. Then in step 3, the goroutine on the right places his hand into the channel or performs a receive. That goroutine is also locked in the channel until the exchange is complete. In step 4 and 5 the exchange is made and finally in step 6, both goroutines are free to remove their hands and go on their way.

Synchronization is inherent in the interaction between the send and the receive. One can not happen without the other. The nature of an unbuffered channel is guaranteed synchronization.

Buffered Channels
Buffered channels have capacity and therefore can behave a bit differently. When a goroutine attempts to send a resource to a buffered channel and the channel is full, the channel will lock the goroutine and make it wait until a buffer becomes available. If there is room in the channel, the send can take place immediately and the goroutine can move on. When a goroutine attempts to receive from a buffered channel and the buffered channel is empty, the channel will lock the goroutine and make it wait until a resource has been sent.

In the diagram above, we see an example of two goroutines adding and removing items from a buffered channel independently. In step 1, the goroutine on the right is removing a resource from the channel or performing a receive. In step 2, the goroutine on the right can remove the resource independent of the goroutine on the left adding a new resource to the channel. In step 3, both goroutines are adding and removing a resource from the channel at the same time and in step 4 both goroutines are done.

Synchronization still occurs within the interactions of receives and sends, however when the queue has buffer availability, the sends will not lock. Receives will not lock when there is something to receive from the channel. Consequently, if the buffer is full or if there is nothing to receive, a buffered channel will behave very much like an unbuffered channel.

Relay Race
If you have ever watched a track meet you may have seen a relay race. In a relay race there are four athletes who run around the track as fast as they can as a team. The key to the race is that only one runner per team can be running at a time. The runner with the baton is the only one allowed to run, and the exchange of the baton from runner to runner is critical to winning the race.

Let’s build a sample program that uses four goroutines and a channel to simulate a relay race. The goroutines will be the runners in the race and the channel will be used to exchanged the baton between each runner. This is a classic example of how resources can be passed between goroutines and how a channel controls the behavior of the goroutines that interact with it.

package main

import (
    "fmt"
    "time"
)

func main() {
    // Create an unbuffered channel
    baton := make(chan int)

// First runner to his mark
    go Runner(baton)

// Start the race
    baton <- 1

// Give the runners time to race
    time.Sleep(500 * time.Millisecond)
}

func Runner(baton chan int) {
    var newRunner int

// Wait to receive the baton
    runner := <-baton

// Start running around the track
    fmt.Printf("Runner %d Running With Baton\n", runner)

// New runner to the line
    if runner != 4 {
        newRunner = runner + 1
        fmt.Printf("Runner %d To The Line\n", newRunner)
        go Runner(baton)
    }

// Running around the track
    time.Sleep(100 * time.Millisecond)

// Is the race over
    if runner == 4 {
        fmt.Printf("Runner %d Finished, Race Over\n", runner)
        return
    }

// Exchange the baton for the next runner
    fmt.Printf("Runner %d Exchange With Runner %d\n", runner, newRunner)
    baton <- newRunner
}

When we run the sample program we get the following output:

Runner 1 Running With Baton
Runner 2 To The Line
Runner 1 Exchange With Runner 2
Runner 2 Running With Baton
Runner 3 To The Line
Runner 2 Exchange With Runner 3
Runner 3 Running With Baton
Runner 4 To The Line
Runner 3 Exchange With Runner 4
Runner 4 Running With Baton
Runner 4 Finished, Race Over

The program starts out creating an unbuffered channel:

// Create an unbuffered channel
baton := make(chan int)

Using an unbuffered channel forces the goroutines to be ready at the same time to make the exchange of the baton. This need for both goroutines to be ready creates the guaranteed synchronization.

If we look at the rest of the main function, we see a goroutine created for the first runner in the race and then the baton is handed off to that runner. The baton in this example is an integer value that is being passed between each runner. The sample is using a sleep to let the race complete before main terminates and ends the program:

// Create an unbuffered channel
baton := make(chan int)

// First runner to his mark
go Runner(baton)

// Start the race
baton <- 1

// Give the runners time to race
time.Sleep(500 * time.Millisecond)

If we just focus on the core parts of the Runner function, we can see how the baton exchange takes place until the race is over. The Runner function is launched as a goroutine for each runner in the race. Every time a new goroutine is launched, the channel is passed into the goroutine. The channel is the conduit for the exchange, so the current runner and the one waiting to go next need to reference the channel:

func Runner(baton chan int)

The first thing each runner does is wait for the baton exchange. That is simulated with the receive on the channel. The receive immediately locks the goroutine until the baton is sent into the channel. Once the baton is send into the channel, the receive will release and the goroutine will simulate the next runner sprinting down the track. If the fourth runner is running, no new runner will enter the race. If we are still in the middle of the race, a new goroutine for the next runner is launched.

// Wait to receive the baton
runner := <-baton

// New runner to the line
if runner != 4 {
    newRunner = runner + 1
    go Runner(baton)
}

Then we sleep to simulate some time it takes for the runner to run around the track. If this is the fourth runner, the goroutine terminates after the sleep and the race is complete. If not, the baton exchange takes place with the send into the channel. There is a goroutine already locked and waiting for this exchange. As soon as the baton is sent into the channel, the exchange is made and the race continue:

// Running around the track
time.Sleep(100 * time.Millisecond)

// Is the race over
if runner == 4 {
    return
}

// Exchange the baton for the next runner
baton <- newRunner

Conclusion
The example showcases a real world event, a relay race between runners, being implemented in a way that mimics the actual events. This is one of the beautiful things about channels. The code flows in a way that simulates how these types of exchanges can happen in the real world.

Now that we have an understanding of the nature of unbuffered and buffered channels, we can look at different concurrency patterns we can implement using channels. Concurrency patterns allow us to implement more complex exchanges between goroutines that simulate real world computing problems like semaphores, generators and multiplexers.

时间: 2024-10-26 18:18:06

golang中channels的本质详解,经典!的相关文章

Scala 深入浅出实战经典 第53讲:Scala中结构类型实战详解

王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 腾讯微云:http://url.cn/TnGbdC 360云盘:http://yunpan.cn/cQ4c2UALDjSKy 访问密码 45e2土豆:http://www.tudou.com/programs/view/pR_4sY0cJLs/优酷:http://v.youku.com/v_show/id_

Scala 深入浅出实战经典 第57讲:Scala中Dependency Injection实战详解

王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 腾讯微云:http://url.cn/TnGbdC 360云盘:http://yunpan.cn/cQ4c2UALDjSKy 访问密码 45e2土豆:http://www.tudou.com/programs/view/5LnLNDBKvi8/优酷:http://v.youku.com/v_show/id_

Scala 深入浅出实战经典 第54讲:Scala中复合类型实战详解

王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 腾讯微云:http://url.cn/TnGbdC 360云盘:http://yunpan.cn/cQ4c2UALDjSKy 访问密码 45e2土豆:http://www.tudou.com/programs/view/a6qIB7SqOlc/优酷:http://v.youku.com/v_show/id_

Scala 深入浅出实战经典 第55讲:Scala中Infix Type实战详解

王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载: 百度云盘:http://pan.baidu.com/s/1c0noOt6 腾讯微云:http://url.cn/TnGbdC 360云盘:http://yunpan.cn/cQ4c2UALDjSKy 访问密码 45e2土豆:http://www.tudou.com/programs/view/9JKSqMiQuBE/优酷:http://v.youku.com/v_show/id

第58讲:Scala中Abstract Types实战详解

package com.parllay.scala.type_parameterizitor import scala.io.{Source, BufferedSource} /** * Created by richard on 15-8-17. * 第58讲:Scala中Abstract Types实战详解 */ /** * scala里的类型,除了在定义class,trait,object时会产生类型, * 还可以通过type关键字来声明类型. type相当于声明一个类型别名: scala

快速傅立叶变换算法FFT——图像处理中的数学原理详解22

欢迎关注我的博客专栏"图像处理中的数学原理详解" 全文目录请见 图像处理中的数学原理详解(总纲) http://blog.csdn.net/baimafujinji/article/details/48467225 图像处理中的数学原理详解(已发布的部分链接整理) http://blog.csdn.net/baimafujinji/article/details/48751037 交流学习可加图像处理研究学习QQ群(529549320) 傅立叶变换以高等数学(微积分)中的傅立叶级数为基

Swift 中的Closures(闭包)详解

Swift 中的Closures(闭包)详解 在Swift没有发布之前,所有人使用OC语言编写Cocoa上的程序,而其中经常被人们讨论的其中之一 -- Block 一直备受大家的喜爱.在Swift中,同样有这样的一个角色,用于当开发者需要异步执行的之后使用的一种语法 - Closure.中文翻译为闭包. 闭包出了可以进行异步执行之外,它的完整使用还依赖闭包本身的变量.常量的捕获.闭包捕获并存储对它们定义的上下文中的任何常量和变量的引用,这也就意味着,你可以在任何时候异步执行闭包的时候获取之前的所

Java I/O : Java中的进制详解

作者:李强强 上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算.这一讲,泥瓦匠带你走进Java中的进制详解. 一.引子 在Java世界里,99%的工作都是处理这高层.那么二进制,字节码这些会在哪里用到呢? 自问自答:在跨平台的时候,就凸显神功了.比如说文件读写,数据通信,还有Java编译后的字节码文件.下面会有个数据通信的例子哦. Java对对象实现Serializablle接口,就可以将其转化为一系列字节,而在通信中,不必要关系数据如何在不同机器表示和字节的顺

Akka第一个案例动手实战main方法实现中ActorSystem等代码详解

学习了Akka第一个案例动手实战main方法实现中ActorSystem等代码详解,创建ActorSystem实例,用acterOf创建MasterActor,用tell的方式给MasterActor发信息,睡眠一段时间给MasterActor发信息,处理完后关闭,资源回收. 案例如下: public static void main(String[] args) throws Exception{ ActorSystem_system =  ActorSystem.create("HelloA