8.3 Go channel
8.3 Go channel
在Go语言中,关键字go
的引入使得Go语言并发编程更加简单而优雅,但是并发编程的复杂性,以及时刻关注并发编程容易出现的问题需要时刻警惕。
并发编程的难度在于协调
,然而协调就必须要交流
,那么并发单元之间的通信
是最大的问题。
之前说了在程序中两种并发通信模型:共享数据和消息
。
共享数据
是指多个并发单元
分别保持对同一个数据
的引用,实现对数据的共享。
共享数据
可能是内存数据块、磁盘文件、网络数据等。
Go语言既然一并发为核心,它提供了另一种通信模型,以消息机制而非共享内存作为通信方式
。
消息机制:每个并发单元的输入和输入只有一种。
1.1. channel介绍
Go语言提供的消息通信机制被称为channel
。
channel是go语言在语言级别提供的goroutine间的通信方式。
channel是有类型的,一种channel只能传递一种类型值,这个类型在声明channel时定义。
1.channel本质是一个数据结构(队列)
2.channel数据遵循FIFO
,first in first out。
3.channel本身是线程安全的,多个goroutine访问时不需要加锁。
4.一个string类型的channel只能放入string类型数据。
1.2. 定义channel
基本语法:
var 管道名 chan 管道类型
如:
var intChan chan int
var strChan chan string
var stuChan chan Student //结构体类型channel
var mapChan chan map[string]string //map类型channel
var boolChan chan bool //布尔类型channel
注意:
channel是引用类型
channel必须make初始化后方可使用
1.3. channel使用
package main import "fmt" func main() {
//初始化一个可以存放3个int类型数据的管道
//创建方式一
//var intChan chan int
//intChan = make(chan int, 3) //创建方式二,简短声明创建,如果不make初始化,默认chan是nil
intChan := make(chan int, 3)
//查看下channel里面有什么
fmt.Printf("intChan值:%v intChan地址:%p\n", intChan, &intChan)
fmt.Printf("intChan长度:%v 容量%v\n", len(intChan), cap(intChan)) //向管道写入数据,只能写入int类型
intChan <- 10
//放入第二个数据
num := 999
intChan <- num
//放入第三个数据
intChan <- 666
//注意我们初始化chan的容量限制在3,数据不能超过chan的容量
fmt.Printf("intChan长度:%v 容量%v\n", len(intChan), cap(intChan)) //从管道中读取数据
//定义一个变量接收chan的数据,注意先进先出的规律
res := <-intChan //
res2 := <-intChan
fmt.Println(res, res2)
fmt.Printf("intChan长度:%v 容量%v\n", len(intChan), cap(intChan)) //数据遵循,有多少读多少,读空的channel也报错
res3 := <-intChan
fmt.Println(res3)
fmt.Printf("intChan长度:%v 容量%v\n", len(intChan), cap(intChan))
}
1.4. channel注意事项
1.channel只能存放声明的数据类型
2.channel数据放满了,不得再放入,否则panic报错,死锁
3.取出一个channel数据,少了一个坑,那可以再放入一个数据
4.数据取空后,不得在取,否则panic报错
1.5. channel存放数据类型
1.map类型channel
package main import "fmt" func main() {
mapChan := make(chan map[string]string, 10) //初始化创建map类型的chan,可以放入map数据
myMap := make(map[string]string, 10) //初始化创建map
myMap["姓名"] = "黑旋风"
myMap["年纪"] = "28" mymap2 := make(map[string]string, 10) //初始化创建第二个map
mymap2["姓名"] = "小妖精" mapChan <- myMap
mapChan <- mymap2 fmt.Printf("值:%v 长度:%v 容量:%v\n", mapChan, len(mapChan), cap(mapChan))
}
2.结构体类型channel
package main import "fmt" type Student struct {
Name string
Age int
} func main() {
//创建管道
structChan := make(chan Student,2)
//创建结构体对象
s1 := Student{"艾利克斯", 18}
s2 := Student{"银角大王吧", 19}
//数据放入管道
structChan <- s1
structChan <- s2 //取出管道数据
stu1 := <-structChan
stu2 := <-structChan
fmt.Println(stu1, stu2)
}
3.存放指针数据的管道
package main import "fmt" type Student struct {
Name string
Age int
} func main() {
//创建管道
stuChan := make(chan *Student, 10)
//创建结构体对象
s1 := Student{"王二狗", 18}
s2 := Student{"王八犊子", 19}
//传入结构体对象到管道
stuChan <- &s1
stuChan <- &s2
//取出管道的值
stu1 := <-stuChan
stu2 := <-stuChan
fmt.Printf("%v %v\n", stu1, stu2)
}
4.创建可以接收任意数据类型
的channel,注意,空接口
可以存放任意数据类型。
package main import "fmt" type Student struct {
Name string
Age int
} func main() {
allChan := make(chan interface{}, 10)
allChan <- 123
allChan <- "我是字符串"
res1 := <-allChan
res2 := <-allChan
fmt.Printf("res1值:%v 类型:%T\n", res1, res1)
fmt.Printf("res2值:%v 类型:%T\n", res2, res2) //定义结构体
s1 := Student{"王麻子", 11}
allChan <- s1
//取出结构体,查看类型
newStu := <-allChan
fmt.Printf("%T %v\n", newStu, newStu)
//newStu此时无法直接使用,必须类型断言后使用
r := newStu.(Student) //断言它是结构体类型
fmt.Printf("r值:%v 类型:%T\n", r, r)
}
1.6. channel缓冲区
初始化channel的方式是make
函数。
ch :=make(chan int) //无缓冲区的管道
ch2:=make(chan int,0)//无缓冲区的管道
ch3:=make(chan int,10)//有缓冲区的管道,且容量是10
如图所示:
两个协程,传递数据,无缓存的管道,就是不可以保存数据的通道。
无缓冲的管道:在管道接受数据前,不保存任何值。
无缓存的管道:这种类型的管道要求两个goroutine同时接、收值。
如果goroutine没同时准备好,会导致有一方阻塞状态。
这种管道进行接、收值的行为,本身是同步状态。
无缓冲管道案例
package main import (
"fmt"
"time"
) //运动员1
func write(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
fmt.Printf("棒子%d号来了\n", i)
//每秒塞一个棒子
time.Sleep(time.Second)
fmt.Println("-------------------")
}
} //运动员2
func read(ch chan int) {
for v := range ch {
fmt.Printf("拿到了棒子%d号\n", v)
}
} func main() {
intChan := make(chan int, 0)
go write(intChan)
go read(intChan)
time.Sleep(10 * time.Second)
}
有缓冲的管道
有缓冲的管道:在被接收前能存储一个或多个值的通道。
这种管道不要求goroutine必须同时发送和接收。
只有管道中没有要接收的值,接收动作才会阻塞。
只有管道塞满了数据,发送动作才会阻塞。
示意图
package main import (
"fmt"
"time"
) //运动员1
func write(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
fmt.Printf("棒子%d号来了\n", i)
//每秒塞一个棒子
fmt.Println("-------------------")
}
} //运动员2
func read(ch chan int) {
for v := range ch {
fmt.Printf("拿到了棒子%d号\n", v)
}
} func main() {
intChan := make(chan int, 5)
go write(intChan)
go read(intChan)
time.Sleep(2 * time.Second)
}
1.7. 关闭channel
使用内置函数close可以关闭channel。
channel关闭后,就不能再向channel写入数据了,但是仍然可以从channel读取数据。
package main import "fmt" func main() {
intChan := make(chan int, 3)
intChan <- 123
intChan <- 456
close(intChan) //通道关闭,游客禁止入内,数据也无法写入
//intChan<-123 //此处已经不能加载 panic: send on closed channel fmt.Println("管道已经关闭,在管道中的游客请尽快出来")
n1 := <-intChan
n2 := <-intChan
fmt.Println(n1, n2)
}
遍历channel的方式
package main import "fmt" func main() {
intChan := make(chan int, 100)
for i := 0; i < 100; i++ {
intChan <- i * i //循环写入一百个数据
} //遍历方式1,取出管道值
//for i := 0; i < len(intChan); i++ {
// fmt.Println(i)
//} //遍历方式2,for range,参数只有一个
//在遍历管道时,必须先关闭,否则死锁报错
close(intChan)
for v := range intChan {
fmt.Printf("类型:%T 值:%d\n", v, v)
}
}
1.8. 单向channel
默认情况下,channel是双向的,既可以写入数据,也可以读取数据。
但是有些场景下,管道当做参数传递,且希望仅仅是单向使用(只读,只写),此时可以使用单向管道。
语法
var ch1 chan int //普通管道
var ch2 chan <- string //只能写入string类型的数据
var ch3 <-chan int //只能读取int类型的数据
提示
chan <- 表示数据写入channel
<-chan 表示读取channel中的数据
示例
1.8.1. 生产消费者模型
package main import (
"fmt"
) func Producer(baozi chan<- int) {
for i := 0; i < 10; i++ {
baozi <- i //把包子放到管道
fmt.Printf("三全食品,生产了有毒包子%d号\n", i)
}
//每天就只卖10个包子,卖完为止
close(baozi)
} func Consumer(baozi <-chan int) {
for baozi := range baozi {
fmt.Printf("消费者吃了包子%d号.....\n", baozi)
}
} func main() {
ch := make(chan int, 10)
//生产者
go Producer(ch)
//消费者
Consumer(ch)
}
执行结果图
案例2
package main import "fmt" //只能写入数据
func sendData(sendch chan<- int) {
sendch <- 10
//只写模式下,不能读取操作
//invalid operation: <-sendch (receive from send-only type chan<- int)
//<-sendch
} //只能读取数据
func readData(sendch <-chan int) {
//sendch <- 10
data := <-sendch
fmt.Println(data)
} func main() {
chnl := make(chan int)
go sendData(chnl) //开启协程,写入数据到管道
readData(chnl)
}
8.3 Go channel的更多相关文章
- Golang, 以17个简短代码片段,切底弄懂 channel 基础
(原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程.管道等高并发编程知识 ...
- TODO:Go语言goroutine和channel使用
TODO:Go语言goroutine和channel使用 goroutine是Go语言中的轻量级线程实现,由Go语言运行时(runtime)管理.使用的时候在函数前面加"go"这个 ...
- GO语言之channel
前言: 初识go语言不到半年,我是一次偶然的机会认识了golang这门语言,看到他简洁的语法风格和强大的语言特性,瞬间有了学习他的兴趣.我是很看好go这样的语言的,一方面因为他有谷歌主推,另一方面他确 ...
- Critical: Update Your Windows Secure Channel (cve-2014-6321,MS14-066)
前言:风雨欲来山满楼,下半年开始各种凶猛的漏洞层出不穷,天下已经不太平,互联网已经进入一个新的台阶 0x01 cve-2014-6321 11月的补丁月,微软请windows的用户吃了顿大餐,发布了1 ...
- JAVA NIO Channel
Basic: 多数通道都是链接到开发的文件描述符的.Channel类提供维持平台独立性的抽象过程. 通道是一种途径,访问和操作操作系统,缓冲区是数据操作点: Channel类继承结构图: 通过 ...
- channel Golang
Golang, 以17个简短代码片段,切底弄懂 channel 基础 (原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的 ...
- go channel
channel 是go语言中不同goroutine之间通信一种方法 //定义一个channel c := make(chan bool) //向channel中写入 c <- true //读取 ...
- [bigdata] flume file channel CPU消耗比 memory channel高的原因
https://www.quora.com/Why-does-flume-take-more-resource-CPU-when-file-channel-is-used-compared-to-wh ...
- 图解Netty之Pipeline、channel、Context之间的数据流向。
声明:本文为原创博文,禁止转载. 以下所绘制图形均基于Netty4.0.28版本. 一.connect(outbound类型事件) 当用户调用channel的connect时,会发起一个 ...
- go:channel(未完)
注:1)以下的所有讨论建立在包含整形元素的通道类型之上,即 chan int 2)对于“<-”我的理解是,它可能是一个操作符(接收操作符),也 可能是类型的一部分(如“chan<- in ...
随机推荐
- QtConcurrent::run 运行类的成员函数
https://stackoverflow.com/questions/2152355/is-it-possible-to-use-qtconcurrentrun-with-a-function-me ...
- 梁国辉获Yes评分表系统3.0计算机软件著作权
梁国辉获Yes评分表系统3.0计算机软件著作权 Liang Guohui won the Yes score system 3 computer software copyright 登记证书如下 R ...
- 《PostgreSQL服务器编程》一一1.3 超越简单函数
本节书摘来自华章计算机<PostgreSQL服务器编程>一书中的第1章,第1.3节,作者:(美)Hannu Krosing, Jim Mlodgenski, Kirk Roybal 著,更 ...
- resetlogs 打开数据库时新生成日志位置问题
若系统中缺少联机日志,以resetlogs方式重建控制文件,那么当我们以alter database open resetlogs方式打开数据库时,新生成的联机日志会位于何处? 一:下面分别讨论几种情 ...
- angularJS中$http.get( ).success( )报错原因及解决方案
一.问题描述: 电脑安装的angular1.6.7版本,项目中使用了$http.get( ).success( ),控制台报错: $http.get(...).success is not a fun ...
- STL训练 HDU - 1716 Ray又对数字的列产生了兴趣:
HDU - 1716 Ray又对数字的列产生了兴趣: 现有四张卡片,用这四张卡片能排列出很多不同的4位数,要求按从小到大的顺序输出这些4位数. Input 每组数据占一行,代表四张卡片上的数字(0&l ...
- spring的bean的属性注入
一.设置注入 设置注入要求: 要求属性在实体类中必须有getter 和setter方法,然后在spring的工厂中就可以使用property标签进行设值注入. 二.构造注入 通过类的构造方法的方式注入 ...
- 【不断更新】mysql经典50道题自我练习
mysql经典50道题自我练习 测试数据和练习题均转载自CSDN博主@启明星的指引的文章sql语句练习50题(Mysql版),用于mysql的每日自我练习 表名和字段 –1.学生表 Student(s ...
- FOC中电流环调试的宝贵经验总结(有理有据+全盘拖出)
你是否经历过一个人独自摸索前进磕磕碰碰最终体无完肤,然后将胜利的旗帜插到山顶的时刻,如果有,本文也许能帮你在调试FOC电流环的时候给你带来一些帮助和思路. 如果本文帮到了您,请帮忙点个赞
- redis文章汇总
方便集群管理时的查看操作 http://www.cnblogs.com/mushroom/p/4738170.html http://www.cnblogs.com/hjwublog/p/568170 ...