一、channel

在 Go 语言里,不仅可以使用原子函数和互斥锁来保证对共享资源的安全访问以及消除竞争状态,还可以使用 channel,通过发送和接收需要共享的资源,在 goroutine 之间做同步。
当一个资源需要在 goroutine 之间共享时,channel 在 goroutine 之间架起了一通道,并提供了确保同步交换数据的机制。声明 channel时,需要指定将要被共享的数据的类型。可以通过 channel 共享内置类型、命名类型、结构类型和引用类型的值或者指针。

基本使用

package main

import (
"fmt"
) func main() {
// 使用 make 创建 channel
// 方式1
var intChan chan int
intChan = make(chan int, 3)
// 方式2
// intChan := make(chan int, 3) fmt.Printf("aChan的值:%v\n", intChan) // 0xc000086000
fmt.Printf("aChan本身的地址:%p\n", &intChan) // 0xc000080018 // 向管道发送值(注意给channel放入数据时,不能超过其容量)
intChan <- 10
intChan <- 20
fmt.Printf("aChan的len:%v\n", len(intChan)) //
fmt.Printf("aChan的cap:%v\n", cap(intChan)) // // 从管道中读取数据
int1 := <- intChan
int2 := <- intChan
fmt.Println(int1, int2) // 10 20
}

示例1

package main

import (
"fmt"
) type Cat struct {
Name string
Age int
} func main() {
// 定义一个存放任意数据类型的管道3个数据
allChan := make(chan interface{}, 3)
allChan <- 10
allChan <- "pd"
cat := Cat{"tom", 10}
allChan <- cat
// 如果希望获得到管道中的第三个元素,则需先将前2个推出
<- allChan
<- allChan
newCat := <- allChan
fmt.Printf("newCat=%T , newCat=%v\n", newCat, newCat)
// fmt.Println(newCat.Name) // 编译不通过,错误
fmt.Println(newCat.(Cat).Name) // 使用类型断言,正确
}

示例2

channel 使用注意事项:

  1. channel中只能存放指定的数据类型;
  2. channel的数据放满后,就不能在放入了;
  3. 如果从 channel 取出数据后,就可以继续放入了;
  4. 在没有使用 goroutine 的情况下,如果 channel 数据被取完了,再取,就会报 dead lock。

二、关闭 channel

关闭 channel 非常简单,直接使用Go语言内置的close()函数即可:
func main() {
intChan := make(chan int, 3)
intChan <- 10
intChan <- 20
// 关闭 channel
close(intChan)
// 关闭 channel 后,无法将数据写入到 channel 中,读取数据是可以的
num := <- intChan
fmt.Println(num) // 10
}

三、遍历 channel

channel 支持 for-range 的方式进行遍历:

  1. 在遍历时,如果 channel 没有关闭,则会出现 deadlock 错误;
  2. 在遍历时,如果 channel 已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。
package main

import "fmt"

func main() {
ch := make(chan int, 3)
ch <- 10
ch <- 20
ch <- 30
// 关闭 channel
close(ch)
// 遍历 channel
for v := range ch {
fmt.Println(v)
}
}

四、应用实例

实例1:

  1. 开启一个 writeData 协程,向管道中写入30个整数;
  2. 开启一个 readData 协程,从管道中读取writeData写入的数据;
  3. writeData 和 readData 操作的是同一个管道;
  4. 主线程需要等待这两个协程都完成工作才能退出。
package main

import "fmt"

// 将数据放入管道
func writeData(intChan chan int) {
for i := 1; i <= 30; i++ {
// 放入数据
intChan<- i
fmt.Printf("写入数据:%v\n", i)
}
close(intChan) // 关闭管道
} // 从管道中获取数据
func readData(intChan chan int, exitFlagChan chan bool) {
for {
v, ok := <-intChan
if !ok {
break
}
fmt.Printf("获取数据:%v\n", v)
}
// 读取完数据后,写入一个"标志"到另一个管道
exitFlagChan<- true
close(exitFlagChan) // 关闭管道
} func main() {
intChan := make(chan int, 10)
exitFlagChan := make(chan bool, 1)
go writeData(intChan)
go readData(intChan, exitFlagChan)
// 获取"标志",用于结束主线程
for {
_, ok := <-exitFlagChan
if !ok {
break
}
}
}

阻塞:

对于示例1中,如果注销掉上图中的这行代码(即相当于只是向管道写入数据,而没有获取数据)。就会出现阻塞而发生 deadlock,原因是 intChan 的容量是10,而写入数据确是 50 个。

如果,编译器运行时发现一个管道只有写,没有读,则该管道会阻塞。PS:写管道和读管道的速度(频率)不一致,是没有关系的。

实例2:

需求:统计 1-5000 的数字中,那些是素数?

分析:

  • 传统的方式:使用一个循环,判断各个数是不是素数;
  • 协程的方式:将统计的素数的任务分配给(4个)goroutine 去完成(完成任务时间短)。
package main

import "fmt"

// 向intChan放入1-5000个整数
func putNum(intChan chan int) {
for i := 1; i <= 5000; i++ {
intChan<- i
}
close(intChan)
} // 从intChan取出数据,并判断是否为素数,如果是,就放入到resultChan中
func getNum(intChan chan int, resultChan chan int, exitChan chan bool) {
var flag bool
for {
num, ok := <-intChan
if !ok {
// intChan 取不到
break
}
// flag默认为true(假设取到的是素数)
flag = true
// 判断num是不是素数
for i := 2; i < num; i++ {
if num % i == 0 { // 说明该num不是素数
flag = false
break
}
}
if flag {
// 将这个数就放入到resultChan
resultChan<- num
}
}
// 还不能关闭 resultChan,因为是4个协程在工作
// 向 exitChan 写入true
exitChan<- true
} func main() {
// 保存原始数据的管道
intChan := make(chan int , 500)
// 保存结果的管道
resultChan := make(chan int, 1000)
// 标识退出的管道
exitChan := make(chan bool, 4) // 4个
// 产生数据
go putNum(intChan)
// 开启4个协程,从intChan取出数据,并判断是否为素数,如果是,就放入到resultChan
for i := 0; i < 4; i++ {
go getNum(intChan, resultChan, exitChan)
}
// 主线程进行处理,当从resultChan取出了4个结果,就可以放心的关闭resultChan
go func(){
for i := 0; i < 4; i++ {
<-exitChan
}
close(resultChan)
}()
// 遍历resultChan,把结果取出
count := 0
for {
r, ok := <-resultChan
if !ok{
break
}
count += 1
fmt.Printf("素数:%d\n", r)
}
fmt.Printf("素数数量:%d\n", count)
fmt.Println("主线程退出")
}

  

Go:channel的更多相关文章

  1. Go基础系列:channel入门

    Go channel系列: channel入门 为select设置超时时间 nil channel用法示例 双层channel用法示例 指定goroutine的执行顺序 channel基础 chann ...

  2. Java NIO学习系列二:Channel

    上文总结了Java NIO中的Buffer相关知识点,本文中我们来总结一下它的好兄弟:Channel.上文有说到,Java NIO中的Buffer一般和Channel配对使用,NIO中的所有IO都起始 ...

  3. handy源码阅读(四):Channel类

    通道,封装了可以进行epoll的一个fd. struct Channel: private noncopyable { Channel(EventBase* base, int fd, int eve ...

  4. Netty入门(二):Channel

    前言 Netty系列索引: 1.Netty入门(一):ByteBuf 2.Netty入门(二):Channel 在Netty框架中,Channel是其中之一的核心概念,是Netty网络通信的主体,由它 ...

  5. go:channel(未完)

    注:1)以下的所有讨论建立在包含整形元素的通道类型之上,即 chan int 2)对于“<-”我的理解是,它可能是一个操作符(接收操作符),也  可能是类型的一部分(如“chan<- in ...

  6. GO语言练习:channel 缓冲机制

    1.代码 2.运行 3.解析 1.代码 buffer.go package main import ( "fmt" "time" ) func readThre ...

  7. 专题:Channel Bonding/bonding

    EtherChannel最初是由cisco提出,通过聚合多条物理链路为单条逻辑链路,从而实现高可用及提高吞吐量等目的.AgP(Port Aggregation Protocol,Cisco专有协议). ...

  8. (转)[疯狂Java]NIO:Channel的map映射

    原文出自:http://blog.csdn.net/lirx_tech/article/details/51396268 1. 通道映射技术: 1) 其实就是一种快速读写技术,它将通道所连接的数据节点 ...

  9. Java-NIO(六):Channel聚集(gather)写入与分散(scatter)读取

    Channel聚集(gather)写入: 聚集写入( Gathering Writes)是指将多个 Buffer 中的数据“聚集”到 Channel. 特别注意:按照缓冲区的顺序,写入 positio ...

随机推荐

  1. bzoj 3613: [Heoi2014]南园满地堆轻絮【二分+贪心】

    二分答案w,然后判断的时候维护一个mx,扫描序列,先更新mx=max(mx,a[i]-w),然后如果a[i]+w<mx的话就是说这个位置即使升到极限并且前面降到极限也不能符合条件了 #inclu ...

  2. 将tomcat添加到系统服务

    一.安装服务 执行“service.bat install 二.卸载服务 在命令行中进入/Tomcat路径/bin/,执行“service.bat remove”  

  3. 贪心+拓扑排序 AOJ 2456 Usoperanto

    题目传送门 题意:给出一条链,比如x连到y,x一定要在y的左边,且代价是这条链经过的点的权值和,问如何排序使得代价最小 分析:类似拓扑排序,先把入度为0的点入队,把指向该点的所有点按照权值排序,保证这 ...

  4. 制作ubuntu启动U盘:Windows,Mac osx ,Ubuntu

    1.How to create a bootable USB stick on Windows https://www.ubuntu.com/download/desktop/create-a-usb ...

  5. Suricata的初始化脚本

    见官网 https://suricata.readthedocs.io/en/latest/initscripts.html

  6. Web API DataContract DataMember Serializable简单解释

    首先看一下DataContract这个类契约: Web API/WCF 中类一旦标记了DataContract 属性,那么类中的属性只有被标记为DataMember属性才会被序列化,也就是说一个类的属 ...

  7. 如何在Windows2008 Server服务器上开启Ping或者禁PING

    方法1:命令行模式 进入服务器后 点击 开始--运行 输入命令: netsh firewall set icmpsetting 8 这样就可以在外部ping到服务器了 非常简单实用! 同样道理,如果想 ...

  8. 【前端】html5获取经纬度,百度api获取街区名,并使用JS保存进cookie

    引用js<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak= ...

  9. 通用maper无法获取实体类com.qmtt.model.PhWxUser对应的表名问题

    spring boot在采用了热加载后,可能会出现“无法获取实体类com.qmtt.model.PhWxUser对应的表名!”的异常, 解决办法 在resources新建一个文件夹META-INF,新 ...

  10. 【转】PowerManager 与 WakeLock

    PowerManager 与 WakeLock PowerManager 用来控制设备的电源状态. 而PowerManager.WakeLock 也称作唤醒锁, 是一种保持 CPU 运转防止设备休眠的 ...