一、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. 【Tip】Python

    『基本操作』 [查看Python所在目录] import os print(os.__file__) [查看已安装的包] pip list [获取当前脚本所在目录] import sys import ...

  2. NOIp2016 蚯蚓 【二叉堆/答案单调性】By cellur925

    题目传送门 $Sol$ $50pts$:我们考虑$q==0$的情况,每次在所有的蚯蚓中找到一只长度最大的,这非常二叉堆.所以我们可以用一个优先队列,随便水一下就有50分.($NOIp$的分真这么好拿? ...

  3. C++十进制到任意进制

    #include<stdio.h> #include<string.h> #include<math.h> #include<iostream> #in ...

  4. css - 单词的自动换行问题

    转载自:解决文档中有url链接时被强制换行的问题 问题 当行内出现很长的英文单词或者url的时候,会出现自动换行的问题,为了美化页面,往往会希望这些很长的英文单词或者url能够断开来,超出的部分换行到 ...

  5. 关于MyBatis的两种写法

    刚接触MyBatis是在Jike的视频中学习的,但是之后又发现和项目中的MyBatis的用法不太一致.上网找了好多资料,发现网上的教程分为两种写法: 第一种,是jike视频中的写法,写好map.xml ...

  6. JAVA字符串转日期或日期转字符串【转】

    JAVA字符串转日期或日期转字符串[转] 文章中,用的API是SimpleDateFormat,它是属于java.text.SimpleDateFormat,所以请记得import进 来! 用法: S ...

  7. 百度地图API简单初始化

    <script src="http://api.map.baidu.com/api?key=&v=2.0&ak=youkey"></script& ...

  8. Dynamic Median

    题意: 设计一个数据结构,初始为空,支持以下操作: (1)增加一个元素,要求在log(n)时间内完成,其中n是该数据结构中当前元素的个数.注意:数据结构中允许有重复的元素. (2)返回当前元素集合的中 ...

  9. Web前端攻防,一不小心就中招了

    随着各浏览器安全功能的提高,前端防御面临的问题也没有之前那么复杂,但浏览器的防御措施并不能百分百的保证网站的安全. 浏览器的XSS Auditor,使得反射型xss几乎被废:CSP(Content-S ...

  10. Java编译时根据调用该方法的类或对象所属的类决定

    class Base{     int x = 1;     static int y = 2; } class Subclass extends Base{     int x = 4;     i ...