众所周知,Go lang的作用域相对严格,数据之间的通信往往要依靠参数的传递,但如果想在多个协程任务中间做数据通信,就需要通道(channel)的参与,我们可以把数据封装成一个对象,然后把这个对象的指针传入某个通道变量中,另外一个协程从这个通道中读出变量的指针,并处理其指向的内存对象。

通道的声明与创建

  

package main  

import "fmt"  

func main() {
var a chan int
if a == nil {
fmt.Println("通道是空的, 不能使用,需要先创建通道")
a = make(chan int)
fmt.Printf("数据类型是: %T", a)
}
}

这里注意,通道声明之后还需要进行创建。

也可以通过海象操作符声明并创建:

package main  

import "fmt"  

func main() {  

	a := make(chan int)  

	fmt.Printf("数据类型是: %T", a)  

}

程序返回:

数据类型是: chan int%

如此,一个类型为整形的通道就创建好了。

此外,通道是引用数据类型:

package main  

import (
"fmt"
) func main() {
ch1 := make(chan int)
fmt.Printf("%T,%p\n", ch1, ch1) test1(ch1) } func test1(ch chan int) {
fmt.Printf("%T,%p\n", ch, ch)
}

程序返回:

chan int,0x1400010e060
chan int,0x1400010e060

可以看到,在test1函数内和main函数内通道的地址是一样的,所以他们指向的都是同一个通道。

通道的使用

通道创建之后,即可以在协程之间充当桥梁:

package main  

import "fmt"  

func job(ch1 chan int) {  

	ch1 <- 1  

}  

func main() {  

	ch1 := make(chan int)  

	fmt.Println(ch1)  

	go job(ch1)  

	data := <-ch1 // 从ch1通道中读取数据
fmt.Println("data-->", data)
fmt.Println("main。。over。。。。")
}

这里我们声明一个函数job,把通道作为参数传递进去,注意这里参数类型除了声明通道本身以外,还得声明通道具体的数据类型。

随后在main函数中,可以理解为主协程,创建通道ch1,执行开启协程任务job,在job函数内,往通道内传递数字1

接着,主协程获取通道内由job协程传递的数据:

0x1400006a060
data--> 1
main。。over。。。。

藉此,就完成了数据的传递。

这里需要注意通道的调用语法:

data := <- a // 读取通道
a <- data // 写入通道

同步阻塞

这里需要注意的是,通道无论是写入还是读取,都是同步阻塞机制。即当有协程对通道进行操作的时候,其他协程都处于“等待”状态,说白了,就是在“排队”,在之前的一篇:并发与并行,同步和异步,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang并发编程之GoroutineEP13,我们要么通过sync.WaitGroup来阻塞主协程,或者通过time.Sleep(time.Second)方法来阻塞,就是怕主协程提前执行完,早成子协程来不及执行。

而通道的出现,就间接帮我们实现了“阻塞”主协程的目的。

比如,多个协程任务操作一个变量:

package main  

import (
"fmt"
) func job1(number int, squareop chan int) {
sum := 20
sum += number
squareop <- sum
} func job2(number int, cubeop chan int) {
sum := 10
sum += number
cubeop <- sum
}
func main() {
number := 0
ch1 := make(chan int)
ch2 := make(chan int)
go job1(number, ch1)
go job2(number, ch2)
num1, num2 := <-ch1, <-ch2
fmt.Println("Final output", num1+num2)
}

这里job1和job2两个协程任务同时异步执行,操作number变量,累加后往通道中写入,程序返回:

Final output 30

理论上,如果是并发执行,返回值应该是20或者10,但由于通道的存在,造成协程任务阻塞,变回了同步执行,所以返回了30。

同时,我们需要注意死锁问题,如果一个协程任务在一个通道上发送数据,那么其他的协程任务应该接收数据,如果这种情况不发生,那么程序将在运行时出现死锁。

换句话说,你发送了,就得有人接收,只发不接,或者只收不发,都会变成死锁。

此外,协程任务可以通过close(ch)方法来关闭通道:

package main  

import (
"fmt"
) func job(ch1 chan int) {
// 发送方:3条数据
for i := 0; i < 3; i++ {
ch1 <- i //将i写入通道中
}
close(ch1) //将ch1通道关闭了。
} func main() {
ch1 := make(chan int)
go job(ch1)
/*
子goroutine,写出数据3个
每写一个,阻塞一次,主程序读取一次,解除阻塞 主goroutine:循环读
每次读取一个,堵塞一次,子程序,写出一个,解除阻塞 发送发,关闭通道的--->接收方,接收到的数据是该类型的零值,以及false
*/
//主程序中获取通道的数据
for { v, ok := <-ch1 //其他goroutine,显示的调用close方法关闭通道。
if !ok {
fmt.Println("已经读取了所有的数据,", ok)
break
}
fmt.Println("取出数据:", v, ok)
} fmt.Println("main...over....")
}

这里将0到2写入chl通道,然后关闭通道。主函数里有一个死循环。类似while,它轮询通道是否在发送数据后,使用变量ok进行判断。如果ok是假的,则意味着通道关闭,因此循环结束,否则将会继续进行无限轮询。

select关键字

select 是 Go lang里面的一个流程控制结构,和switch关键字差不多,但是select会随机执行一个可运行的通道通信,如果没有通道通信可运行,它将阻塞,直到有通道通信可运行:

package main  

import (
"fmt"
"time"
) func job(ch1 chan int) { time.Sleep(2 * time.Second)
ch1 <- 200 } func main() { ch1 := make(chan int)
ch2 := make(chan int) go job(ch1)
go job(ch2) select {
case num1 := <-ch1:
fmt.Println("ch1中取数据。。", num1)
case num2, ok := <-ch2:
if ok {
fmt.Println("ch2中取数据。。", num2)
} else {
fmt.Println("ch2通道已经关闭。。")
} }
}

这里select会随机选择一个可运行的通道通信逻辑,可能是ch1通道,也有可能是ch2通道:

➜  mydemo git:(master) ✗ go run "/Users/liuyue/wodfan/work/mydemo/hello.go"
ch1中取数据。。 200
➜ mydemo git:(master) ✗ go run "/Users/liuyue/wodfan/work/mydemo/hello.go"
ch1中取数据。。 200
➜ mydemo git:(master) ✗ go run "/Users/liuyue/wodfan/work/mydemo/hello.go"
ch2中取数据。。 200
➜ mydemo git:(master) ✗

结语

综上,Golang的通道其实就是将协程任务进行隔离,编写并发逻辑时,关注通道即可,说白了,Golang的通道就是Python多进程通信中的管道,Golang虽然没有显性的多进程调用,但其协程调度底层就是多进程之间的通信,因为只有多进程才可能利用CPU的多核资源。

大道如青天,协程来通信,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang通道channel的使用EP14的更多相关文章

  1. 仙人指路,引而不发,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中New和Make函数的使用背景和区别EP16

    Golang只有二十五个系统保留关键字,二十几个系统内置函数,加起来只有五十个左右需要记住的关键字,纵观编程宇宙,无人能出其右.其中还有一些保留关键字属于"锦上添花",什么叫锦上添 ...

  2. 延宕执行,妙用无穷,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中defer关键字延迟调用机制使用EP17

    先行定义,延后执行.不得不佩服Go lang设计者天才的设计,事实上,defer关键字就相当于Python中的try{ ...}except{ ...}finally{...}结构设计中的finall ...

  3. 清源正本,鉴往知来,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中引用类型是否进行引用传递EP18

    开篇明义,Go lang中从来就不存在所谓的"引用传递",从来就只有一种变量传递方式,那就是值传递.因为引用传递的前提是存在"引用变量",但是Go lang中从 ...

  4. 你有对象类,我有结构体,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang结构体(struct)的使用EP06

    再续前文,在面向对象层面,Python做到了超神:万物皆为对象,而Ruby,则干脆就是神:飞花摘叶皆可对象.二者都提供对象类操作以及继承的方式为面向对象张目,但Go lang显然有一些特立独行,因为它 ...

  5. 百亿数据百亿花, 库若恒河沙复沙,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang数据库操作实践EP12

    Golang可以通过Gorm包来操作数据库,所谓ORM,即Object Relational Mapping(数据关系映射),说白了就是通过模式化的语法来操作数据库的行对象或者表对象,对比相对灵活繁复 ...

  6. 兔起鹘落全端涵盖,Go lang1.18入门精炼教程,由白丁入鸿儒,全平台(Sublime 4)Go lang开发环境搭建EP00

    Go lang,为并发而生的静态语言,源于C语言又不拘泥于性能,高效却不流于古板,Python灵活,略输性能,Java严谨,稍逊风骚.君不见各大厂牌均纷纷使用Go lang对自己的高并发业务进行重构, ...

  7. 化整为零优化重用,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang函数的定义和使用EP07

    函数是基于功能或者逻辑进行聚合的可复用的代码块.将一些复杂的.冗长的代码抽离封装成多个代码片段,即函数,有助于提高代码逻辑的可读性和可维护性.不同于Python,由于 Go lang是编译型语言,编译 ...

  8. 层次分明井然有条,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang包管理机制(package)EP10

    Go lang使用包(package)这种概念元素来统筹代码,所有代码功能上的可调用性都定义在包这个级别,如果我们需要调用依赖,那就"导包"就行了,无论是内部的还是外部的,使用im ...

  9. 人非圣贤孰能无过,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang错误处理机制EP11

    人非圣贤,孰能无过,有则改之,无则加勉.在编程语言层面,错误处理方式大体上有两大流派,分别是以Python为代表的异常捕获机制(try....catch):以及以Go lang为代表的错误返回机制(r ...

  10. 并发与并行,同步和异步,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang并发编程之GoroutineEP13

    如果说Go lang是静态语言中的皇冠,那么,Goroutine就是并发编程方式中的钻石.Goroutine是Go语言设计体系中最核心的精华,它非常轻量,一个 Goroutine 只占几 KB,并且这 ...

随机推荐

  1. python---简单最大类间方差法(OTSU)算法

    from matplotlib import pyplot as plt # cv2.imread()用于读取图片文件 # imread函数有两个参数,第一个参数是图片路径,第二个参数表示读取图片的形 ...

  2. Rustlings通关记录与题解

    2023年6月19日决定对rust做一个重新的梳理,整理今年4月份做完的rustlings,根据自己的理解来写一份题解,记录在此. 周折很久,因为中途经历了推免的各种麻烦事,以及选择数据库作为未来研究 ...

  3. 码编译安装nginx

    1.解释源码安装nginx软件的预编译,编译以及安装,分别是在做什么,需要注意什么? 预编译(configure): ./configure 00prefix=/usr/local/nginx --u ...

  4. 从零开始的Java编程:教你如何实现“超级马里奥”游戏!

    引言超级马里奥,这个名字对于游戏迷来说一定不陌生.它是一款经典的游戏系列,以一个勇敢的水管工人--马里奥为主角,讲述了他在蘑菇王国中的冒险故事.在这个充满挑战和刺激的游戏中,玩家需要控制马里奥跳跃.躲 ...

  5. pta乙级1033(C语言)散列表解法

    #include"stdio.h" #include"string.h" int main() { int flag=1; char w[100010],ch[ ...

  6. [学习笔记]TypeScript查缺补漏(二):类型与控制流分析

    @ 目录 类型约束 基本类型 联合类型 控制流分析 instanceof和typeof 类型守卫和窄化 typeof判断 instanceof判断 in判断 内建函数,或自定义函数 赋值 布尔运算 保 ...

  7. 谈谈流计算中的『Exactly Once』特性

    本文翻译自 streaml.io 网站上的一篇博文:"Exactly once is NOT exactly the same" ,分析了流计算系统中常说的『Exactly Onc ...

  8. JVM-即时编译

    即时编译(JIT just in time,默认是开启的)是一项用来提升应用程序运行效率的技术.通常而言,代码会先被 Java 虚拟机解释执行,之后反复执行的热点代码则会被即时编译成为机器码,直接运行 ...

  9. Net 高级调试之八:代码审查及杂项命令

    一.简介 今天是<Net 高级调试>的第八篇文章.这篇文章设计的内容挺多的,比如:如何查看方法的汇编代码,如何获取方法的描述符,对象同步块的转储,对象方法表的转储,托管堆和垃圾回收器信息的 ...

  10. 接雨水(4.4 leetcode每日打卡)

    给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水.   上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可 ...