原文: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.

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

  1. golang中的反射reflect详解

    先重复一遍反射三定律: 1.反射可以将"接口类型变量"转换为"反射类型对象". 2.反射可以将"反射类型对象"转换为"接口类型变量 ...

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

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

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

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载: 百度云盘:http://pan.baidu.com/s/1c0noOt ...

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

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

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

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  6. Oracle Statspack报告中各项指标含义详解~~学习性能必看!!!

    Oracle Statspack报告中各项指标含义详解~~学习性能必看!!! Data Buffer Hit Ratio#<#90# 数据块在数据缓冲区中的命中率,通常应该在90%以上,否则考虑 ...

  7. 巨人大哥谈Web应用中的Session(session详解)

    巨人大哥谈Web应用中的Session(session详解) 虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术. ...

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

    作者:李强强 上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算.这一讲,泥瓦匠带你走进Java中的进制详解. 一.引子 在Java世界里,99%的工作都是处理这高层. ...

  9. Swift 中的Closures(闭包)详解

    Swift 中的Closures(闭包)详解 在Swift没有发布之前,所有人使用OC语言编写Cocoa上的程序,而其中经常被人们讨论的其中之一 -- Block 一直备受大家的喜爱.在Swift中, ...

随机推荐

  1. scala学习笔记2:面向对象编程部分基础

    以下主要记录的是看完scala in programming这本书Functional Objects(第六章)后的要点总结. 1,程序中可变对象(var)和不可变对象(val)使用的权衡问题 不可变 ...

  2. hibernate--级联添加

    级联添加操作值操作当前数据时.将关联数据也进行操作,就是保存当前数据的同事也将保存和修改关联的数据 首先绑定对象间的关系; `将多方对象添加到一方对象的集合中 tm.getStudents().add ...

  3. Objective-C设计模式——桥接Bridge(接口适配)

    桥接模式 桥接模式就是让抽象和实现分离的最好体现,符合面向对象的依赖倒转原则.Abstruct抽象类负责设计客户端接口,Implementor则负责具体的细节逻辑. 在桥接模式中,Abstruct类持 ...

  4. Git命令add和commit的区别

    要想弄明白git add和git commit的区别,首先我们需要知道三个概念:工作区(Working Directory).版本库(Repository).暂存区(Stage or index). ...

  5. Python :用两个栈实现队列

    转自:http://blog.csdn.net/Lynette_bb/article/details/75092745 牛客网上的剑指 offer的在线编程: 题目描述 用两个栈来实现一个队列,完成队 ...

  6. 网页内容爬取:如何提取正文内容 BEAUTIFULSOUP的输出

    创建一个新网站,一开始没有内容,通常需要抓取其他人的网页内容,一般的操作步骤如下: 根据url下载网页内容,针对每个网页的html结构特征,利用正则表达式,或者其他的方式,做文本解析,提取出想要的正文 ...

  7. 如何利用sql注入进行爆库

    SQL注入能做什么 在<SQL注入基础>一文介绍了SQL注入的基本原理和实验方法,那接下来就要问一下,SQL注入到底能什么? 估计很多朋友会这样认为:利用SQL注入最多只能获取当前表中的所 ...

  8. JDBC更新10W级以上数据性能优化

    随笔缘由: 系统完成到一定程度,少不了要往数据库中添加大量数据进行性能测试. 我用程序做数据10W条,使用jdbc批更新的API,发现每次只能插入2W多条记录. 一番小小研究,觉得总结一下可能有些意义 ...

  9. JAVA基础——集合浅析

    Java  集合      数组是一种很常见的数据结构,开始接触编程的时候多数程序都和数组相关.刚开始接触Java时也是一直使用数组写一些程序,后来越来越觉得数组这东西没法满足需求了,这时一位“前辈” ...

  10. 如何在MONO 3D寻找最短路路径

    前段时间有个客户说他们想在我们的3D的机房中找从A点到B点的最短路径,然而在2D中确实有很多成熟的寻路算法,其中A*是最为常见的,而这个Demo也是用的A*算法,以下计算的是从左上角到右下角的最短路径 ...