通道可以被认为是Goroutines通信的管道。类似于管道中的水从一端到另一端的流动,数据可以从一端发送到另一端,通过通道接收。

在前面讲Go语言的并发时候,我们就说过,当多个Goroutine想实现共享数据的时候,虽然也提供了传统的同步机制,但是Go语言强烈建议的是使用Channel通道来实现Goroutines之间的通信。

 “不要通过共享内存来通信,而应该通过通信来共享内存” 这是一句风靡golang社区的经典语

Go语言中,要传递某个数据给另一个goroutine(协程),可以把这个数据封装成一个对象,然后把这个对象的指针传入某个channel中,另外一个goroutine从这个channel中读出这个指针,并处理其指向的内存对象。Go从语言层面保证同一个时间只有一个goroutine能够访问channel里面的数据,为开发者提供了一种优雅简单的工具,所以Go的做法就是使用channel来通信,通过通信来传递内存数据,使得内存数据在不同的goroutine中传递,而不是使用共享内存来通信。

一、 什么是通道

1.1 通道的概念

通道是什么,通道就是goroutine之间的通道。它可以让goroutine之间相互通信。

每个通道都有与其相关的类型。该类型是通道允许传输的数据类型。(通道的零值为nil。nil通道没有任何用处,因此通道必须使用类似于map和切片的方法来定义。)

1.2 通道的声明

声明一个通道和定义一个变量的语法一样:

//声明通道
var 通道名 chan 数据类型
//创建通道:如果通道为nil(就是不存在),就需要先创建通道
通道名 := make(chan 数据类型)

也可以简短的声明:

a := make(chan int) 

1.3 channel的数据类型

channel是引用类型的数据,在作为参数传递的时候,传递的是内存地址。

package main

import "fmt"

func main() {
/*
channel,通道,先进先出 */
var a chan int
fmt.Printf("%T,%v\n", a, a) //chan int,<nil> if a == nil {
fmt.Println("channel是nil的,不能使用,需要先创建通道。。")
a = make(chan int)
fmt.Println(a) //0xc000046060 //打印出来的是地址,channel是引用类型的数据
}
test1(a)
} func test1(ch chan int) { //传递的是地址
fmt.Printf("%T,%v\n", ch, ch) //chan int,0xc000046060
}

1.4 通道的注意点

Channel通道在使用的时候,有以下几个注意点:

  1.用于goroutine,传递消息的。

  2.通道,每个都有相关联的数据类型, nil chan,不能使用,类似于nil map,不能直接存储键值对

  3.使用通道传递数据:<-

    chan <- data,发送数据到通道。向通道中写数据 data <- chan,从通道中获取数据。从通道中读数据

  4.阻塞:

     发送数据:chan <- data,阻塞的,直到另一条goroutine,读取数据来解除阻塞 读取数据:data <- chan,也是阻塞的。直到另一条goroutine,写出数据解除阻塞。

  5.本身channel就是同步的,意味着同一时间,只能有一条goroutine来操作。

最后:通道是goroutine之间的连接,所以通道的发送和接收必须处在不同的goroutine中。

二、通道的使用语法

2.1 发送和接收

发送和接收的语法:

data := <- a // read from channel a
a <- data // write to channel a

在通道上箭头的方向指定数据是发送还是接收。

另外:

v, ok := <- a //从一个channel中读取

2.2 发送和接收默认是阻塞的

一个通道发送和接收数据,默认是阻塞的。当一个数据被发送到通道时,在发送语句中被阻塞,直到另一个Goroutine从该通道读取数据。相对地,当从通道读取数据时,读取被阻塞,直到一个Goroutine将数据写入该通道。

这些通道的特性是帮助Goroutines有效地进行通信,而无需像使用其他编程语言中非常常见的显式锁或条件变量。

示例代码:

package main

import (
"fmt"
"time"
) func main() {
ch1 := make(chan int)
done := make(chan bool) // 通道
go func() {
fmt.Println("子goroutine执行。。。")
time.Sleep(3 * time.Second)
data := <-ch1 // 从通道中读取数据
fmt.Println("data:", data)
done <- true
}()
// 向通道中写数据。。
time.Sleep(5 * time.Second)
ch1 <- 100 <-done
fmt.Println("main。。over") } //子goroutine执行。。。
//data: 100
//main。。over

2.3 死锁

使用通道时要考虑的一个重要因素是死锁。如果Goroutine在一个通道上发送数据,那么预计其他的Goroutine应该接收数据。如果这种情况不发生,那么程序将在运行时出现死锁。

类似地,如果Goroutine正在等待从通道接收数据,那么另一些Goroutine将会在该通道上写入数据,否则程序将会死锁。

package main

func main() {
ch := make(chan int)
ch <- 5
} //fatal error: all goroutines are asleep - deadlock!

在主流的编程语言中为了保证多线程之间共享数据安全性和一致性,都会提供一套基本的同步工具集,如锁,条件变量,原子操作等等。Go语言标准库也毫不意外的提供了这些同步机制,使用方式也和其他语言也差不多。 除了这些基本的同步手段,Go语言还提供了一种新的同步机制: Channel,它在Go语言中是一个像int, float32等的基本类型,一个channel可以认为是一个能够在多个Goroutine之间传递某一类型的数据的管道。Go中的channel无论是实现机制还是使用场景都和Java中的BlockingQueue很接近。

三、 关闭通道

发送者可以通过关闭信道,来通知接收方不会有更多的数据被发送到channel上。

 close(ch)

接收者可以在接收来自通道的数据时使用额外的变量来检查通道是否已经关闭。

语法结构:

 v, ok := <- ch  

类似map操作,存储key,value键值对

v,ok := map[key] //根据key从map中获取value,如果key存在, v就是对应的数据,如果key不存在,v是默认值

在上面的语句中,如果ok的值是true,表示成功的从通道中读取了一个数据value。如果ok是false,这意味着我们正在从一个封闭的通道读取数据。从闭通道读取的值将是通道类型的零值。

例如,如果通道是一个int通道,那么从封闭通道接收的值将为0。

package main

import (
"fmt"
"time"
) func main() {
/*
关闭通道:close(ch)
子goroutine:写出10个数据
每写一个,阻塞一次,主goroutine读取一次,解除阻塞 主goroutine,读取数据
每次读取数据,阻塞一次,子goroutine,写出一个,解除阻塞 */
ch1 := make(chan int)
go sendData(ch1) //读取通道的数据
for{
time.Sleep(1*time.Second)
v, ok := <- ch1 // 最后一次读取
if !ok{
fmt.Println("已经读取了所有的数据。。",ok,v)
break
}
fmt.Println("读取的数据:",v,ok)
} fmt.Println("main..over...") } func sendData(ch1 chan int){
//发送方:10条数据
for i:=0;i<10;i++{
ch1 <- i //将i写入到通道中
// 0 1 .. 9
}
close(ch1) //将ch1通道关闭
}

我们可以循环从通道上获取数据,直到通道关闭。for循环的for range形式可用于从通道接收值,直到它关闭为止。

package main

import (
"fmt"
"time"
) func main() {
/*
通过range访问通道
*/
ch1 := make(chan int)
go sendData(ch1)
//for循环的for range,来访问通道
for v := range ch1{ // v <- ch1
fmt.Println("读取数据:",v)
}
fmt.Println("main..over...")
}
func sendData(ch1 chan int){
for i:=0;i<10;i++{
time.Sleep(1* time.Second)
ch1 <- i // 0 1...9
}
close(ch1)//通知对方,通道关闭,对方才能结束循环,不读了
}

  

Go_channel的更多相关文章

  1. Go_Channel详解

    一 channel介绍 单纯地将函数并发执行是没有意义的.函数与函数间需要交换数据才能体现并发执行函数的意义. 虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问 ...

  2. Flume实战案例运维篇

    Flume实战案例运维篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Flume概述 1>.什么是Flume Flume是一个分布式.可靠.高可用的海量日志聚合系统,支 ...

随机推荐

  1. [SDOI2012] Longge的问题 - 欧拉函数

    求 \(\sum\limits_{i=1}^{n}gcd(i,n)\) Solution 化简为 \(\sum\limits_{i|n}^{n}φ(\dfrac{n}{i})i\) 筛出欧拉函数暴力求 ...

  2. C++查看大端小端模式

    在学习计算机组成原理的时候,看到大端小端模式,便想实验一下,首先介绍一下 C 中的union,这个平时用得少,估计在单片机这种可能会运用,在平时写代码的时候几乎是用不着union的. union:联合 ...

  3. Intellij IDEA史上最全快捷键大全

    古人有云:工欲善其事,必先利其器,要是只是手握利器,而不能发挥其最大的效益,那无异于赤手空拳,对敌对垒. 那古人所云,未得其精髓,只能为碎语闲言尔. 自动代码 常用的有fori/sout/psvm+T ...

  4. Android 开发 facebook分享,登陆,获取信息

    1 搭建开发环境    1.1 在Facebook官网SDK中,下载4.0.0的SDK包.        1.2 使用Eclipse导入SDK包中的Facebook工程,并添加android-supp ...

  5. numpy 中array 和ndrray的区别联系

    numpy.array()  标明array只是一个方法 ndarray 是类名,是一个实例. a=numpy.array(b)    #这是把变量b转换为数组a,这里array()是个方法,a的类型 ...

  6. 分类问题(一)MINST数据集与二元分类器

    分类问题 在机器学习中,主要有两大类问题,分别是分类和回归.下面我们先主讲分类问题. MINST 这里我们会用MINST数据集,也就是众所周知的手写数字集,机器学习中的 Hello World.sk- ...

  7. 题解 【Codefoeces687B】Remainders Game

    题意: 给出c1,c2,...cn,问对于任何一个正整数x,给出x%c1,x%c2,...的值x%k的值是否确定; 思路: 中国剩余定理.详见https://blog.csdn.net/acdream ...

  8. HTML列表,表格与媒体元素

    一.无序列表 <ul> <li>无序列表</li> <li>有序列表</li> <li>自定义列表</li> < ...

  9. shiro中setUnauthorizedUrl("/403")不起作用

    最近学习shiro框架,在用户没有权限的情况下想让其跳转到403页面,结果非自己预想的效果.后来找到一个解决办法如下: 转载来源 SpringBoot中集成Shiro的时候, 配置setUnautho ...

  10. .NET知识梳理——3.面向对象

    1. 面向对象 1.1        封装.继承.多态理解 1.1.1  封装 封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“ ...