Golang控制通道实现协程等待详解

目录
  • 前言
  • 方法一-睡眠等待
  • 方法二-通道
    • 什么是通道
    • 通道的特性
    • 什么是非缓冲通道
    • 什么是缓冲通道
    • 通道的简单使用
      • 非缓冲通道
      • 缓冲通道
      • 小心死锁
    • 使用通道实现协程等待

前言

上一次简单了解了协程的工作原理 前文链接

最后提到了几个使用协程时会遇到的问题,其中一个就是主线程不会等待子线程结束,在这里记录两种比较简单的方法,并借此熟悉下通道的概念。

方法一-睡眠等待

简单暴力的解决方案,在创建了子协程之后,主协程等待一段时间再结束。

func goroutineTest(i int) {
	fmt.Println(i)
}
func main() {
	for i := 0; i < 3; i++ {
		go func() {
			fmt.Println(i)
		}()
	}
	//等待一段时间
	time.Sleep(time.Millisecond * 500)
}

简单暴力但是并不实用,缺点十分明显, 我们并不知道子协程什么时候会全部执行结束,要么等待时间太短,要么等待时间太长影响方法执行性能。

所以我们需要其他更灵活的方式。

方法二-通道

什么是通道

通道是 Go 自带的、唯一的可以满足并发安全的类型,通道相当于是一个先进先出的队列,通道中的元素会按照插入进来时候的顺序再发送出去。

通道的特性

一、进出通道的值都是副本数据

当向通道内传值时,传递的其实是原本元素的副本,移动时也是同理。

二、对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的。

当有操作要向通道发送数据的时候,其他操作的发送处理会被阻塞,只有当前面的操作执行完毕,值完全被复制进通道内之后,后面的操作才可以发送。接收通道里的值也是同理,只有当值被接受,且元素在通道内被删除之后,其他的接受操作才能被执行。

三、对于同一个通道同一元素值来说,发送和接受操作也是互斥的。

因为上面提到元素进出通道是通过副本的方式,这样做可以避免出现复制还未完成就有操作将其取走的情况。

四、发送操作和接收操作中对元素值的处理是绝对完整的。

发送至通道的操作时,绝不会出现只复制一部分的情况,同理接收通道时,通道在准备好元素值的副本之后一定会将通道内的原值删除。

五、发送和接受操作在未完成之前会一直阻塞

其实就是为了实现操作的互斥和元素值的完整。

什么是非缓冲通道

无缓冲队列表示长度为 0 的通道,长度为 0 表示这个通道不能保留信息,数据是直接从发送方复制到接收方,当有信息传进来时,发送方会阻塞,直到有接收方接受这个值,当然我们也可以先找到一个接收方去接收(但是这里有个坑>>>需要注意死锁)。

什么是缓冲通道

缓冲队列就是表示长度大于 0 的通道,这里的通道相当于一个中转仓库。如果通道满了,那么对它的所有发送操作都会被阻塞,直到通道中有元素值被接收走,通道会优先通知最早因此在等待的发送操作。同理,如果通道已空,那么对它的所有接收操作都会被阻塞,直到通道中有新的元素值出现。这时,通道会通知最早等待的那个接收操作。

通道的简单使用

  • 在声明通道时,我们要定义通道内元素的类型 chan [type]
  • <- 是发送至通道和接收通道元素的操作符,通道在左侧表示向通道内发送,通道在右侧表示从通道内接收
  • 通道定义之后,一定要初始化,对于没有进行初始化的通道,发送和接收操作都是阻塞的

非缓冲通道

定义一个非缓冲通道,通道内元素类型为 int
var ch0 = make(chan int)
func set() {
	for i := 0; i < 3; i++ {
		time.Sleep(time.Millisecond * 1000)
		fmt.Println(i)
	}
	ch0 <- 999
}
func main() {
	//val := <-ch0
	go set()
	fmt.Println("work in here")
	val2 := <-ch0
	fmt.Println("get value")
	fmt.Println(val2)
	fmt.Println("work finished")
}

这段代码主协程中先开启了一个子协程,然后从通道中接受一个值,并打印出来,运行结果如下。

主协程执行到 val2 := <-ch0 这一行时发生了阻塞,三秒过后子协程向通道中发送了元素值。主协程从通道中接收到了值,然后继续向下走。

work in here
0
1
2
get value
999
work finished

缓冲通道

func main() {
  定义一个大小为 3 的通道
	chn := make(chan int, 3)
	chn <- 1
	chn <- 2
	chn <- 3
	fmt.Printf("get value: %v\n", <-chn)
	fmt.Printf("get value: %v\n", <-chn)
	fmt.Printf("get value: %v\n", <-chn)
}

get value: 1
get value: 2
get value: 3

小心死锁

有一个需要注意的地方,如果我们把非缓冲通道示例代码中主协程方法中的第一行代码解开注释再运行,会发现程序出现了报错。

表示所有的协程都睡眠了,发生了死锁。

fatal error: all goroutines are asleep - deadlock!

其实原因也很简单,就比如那上面的代码来说。我们上来就在主协程中接收通道中的值,而此时通道中没有值,所以主协程会阻塞,然而此时我们并没有开启其他的协程,那就相当于这个程序后面将不会有没有任何操作,永远锁在了这个位置。所以如果在阻塞期间发现没有正在执行的协程,程序将爆出异常退出。

所以我们在对通道进行操作时,要注意千万不要在逻辑上把自己锁死了。

使用通道实现协程等待

用起来有点像 JAVA 中的 CountDownLatch

  • 首先我们需要知道子协程的数量 num
  • 然后我们创建一个大小为 num 的通道
  • 当子协程完全执行完之后就向通道中放一个元素值
  • 主协程从通道中循环取值,取值的次数就是 num,取不到值时会阻塞,而这 num 个值只有当全部子协程都执行完毕之后才能提供全,所以这样就实现了主协程等待子协程

至于为什么要创建 struct{} 类型的通道,是因为空结构体占用了0字节的内存空间

func main() {
	num := 5
	sign := make(chan struct{}, num)
	for i := 0; i < num; i++ {
		go func() {
			fmt.Println(i)
			sign <- struct{}{}
		}()
	}
	for j := 0; j < num; j++ {
		<-sign
	}
}

到此这篇关于Golang控制通道实现协程等待详解的文章就介绍到这了,更多相关Go协程等待内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Golang 并发以及通道的使用方式

    Golang最擅长的就是并发编程,使用Golang可以很方便的进行并发编程.先看一段普通的代码 package main import ( "fmt" "time" ) func Foo(i int) { fmt.Printf("%d will sleep\n", i) time.Sleep(5 * time.Second) fmt.Printf("%d wake up\n", i) } func main() { for i

  • Golang通道的无阻塞读写的方法示例

    无论是无缓冲通道,还是有缓冲通道,都存在阻塞的情况,但其实有些情况,我们并不想读数据或者写数据阻塞在那里,有1个唯一的解决办法,那就是使用select结构. 这篇文章会介绍,哪些情况会存在阻塞,以及如何使用select解决阻塞. 阻塞场景 阻塞场景共4个,有缓存和无缓冲各2个. 无缓冲通道的特点是,发送的数据需要被读取后,发送才会完成,它阻塞场景: 通道中无数据,但执行读通道. 通道中无数据,向通道写数据,但无协程读取. // 场景1 func ReadNoDataFromNoBufCh() {

  • Golang控制通道实现协程等待详解

    目录 前言 方法一-睡眠等待 方法二-通道 什么是通道 通道的特性 什么是非缓冲通道 什么是缓冲通道 通道的简单使用 非缓冲通道 缓冲通道 小心死锁 使用通道实现协程等待 前言 上一次简单了解了协程的工作原理 前文链接 最后提到了几个使用协程时会遇到的问题,其中一个就是主线程不会等待子线程结束,在这里记录两种比较简单的方法,并借此熟悉下通道的概念. 方法一-睡眠等待 简单暴力的解决方案,在创建了子协程之后,主协程等待一段时间再结束. func goroutineTest(i int) { fmt

  • Java 协程 Quasar详解

    目录 前言 协程是什么? Quasar使用 1.运行时间 2.内存占用 3.原理与应用 总结 前言 在编程语言的这个圈子里,各种语言之间的对比似乎就一直就没有停过,像什么古早时期的"PHP是世界上最好的语言"就不提了,最近我在摸鱼的时候,看到不少文章都在说"Golang性能吊打Java".作为一个写了好几年java的javaer,这我怎么能忍?于是在网上看了一些对比golang和java的文章,其中戳中java痛点.也是golang被吹上天的一条,就是对多线程并发的

  • Go使用select切换协程入门详解

    目录 前言 程序示例 select 特性预览 管道读写 总结 前言 在 Go 中,可以通过关键字 select 来完成从不同的并发执行的协程中获取值,它和 switch 控制语句非常相似,也被称作通信开关:它的行为像是“你准备好了吗”的轮询机制: select 监听进入通道的数据,也可以是用通道发送值的时候. select 是 Go 在语言层面提供的多路 I/O 复用机制,用于检测多个管道是否就绪(即可读或可写),其特性与管道息息相关. 语法格式: select { case u:= <- ch

  • Python的进程,线程和协程实例详解

    目录 相关介绍 实验环境 进程 多进程 用进程池对多进程进行操作 线程 使用_thread模块实现 使用threading模块实现 协程 使用asyncio模块实现 总结 相关介绍 Python是一种跨平台的计算机程序设计语言.是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言.最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的.大型项目的开发. 例如 实验环境 Python 3.x (面向对象的高级语言) Multiprocessin

  • Golang 标准库 tips之waitgroup详解

    WaitGroup 用于线程同步,很多场景下为了提高并发需要开多个协程执行,但是又需要等待多个协程的结果都返回的情况下才进行后续逻辑处理,这种情况下可以通过 WaitGroup 提供的方法阻塞主线程的执行,直到所有的 goroutine 执行完成. 本文目录结构: WaitGroup 不能被值拷贝 Add 需要在 Wait 之前调用 使用 channel 实现 WaitGroup 的功能 Add 和 Done 数量问题 WaitGroup 和 channel 控制并发数 WaitGroup 和

  • Java并发之嵌套管程锁死详解

    ·嵌套管程死锁是如何发生的 ·具体的嵌套管程死锁的例子 ·嵌套管程死锁 vs 死锁 嵌套管程锁死类似于死锁, 下面是一个嵌套管程锁死的场景: Thread 1 synchronizes on A Thread 1 synchronizes on B (while synchronized on A) Thread 1 decides to wait for a signal from another thread before continuing Thread 1 calls B.wait()

  • Golang 探索对Goroutine的控制方法(详解)

    前言 在golang中,只需要在函数调用前加上关键字go即可创建一个并发任务单元,而这个新建的任务会被放入队列中,等待调度器安排.相比系统的MB级别线程栈,goroutine的自定义栈只有2KB,这使得我们能够轻易创建上万个并发任务,如此对性能提升不少.但随之而来的有以下几个问题: 如何等待所有goroutine的退出 如何限制创建goroutine的数量(信号量实现) 怎么让goroutine主动退出 探索--如何从外部杀死goroutine 本文记录了笔者就以上几个问题进行探究的过程,文中给

  • python golang中grpc 使用示例代码详解

    python 1.使用前准备,安装这三个库 pip install grpcio pip install protobuf pip install grpcio_tools 2.建立一个proto文件hello.proto // [python quickstart](https://grpc.io/docs/quickstart/python.html#run-a-grpc-application) // python -m grpc_tools.protoc --python_out=. -

  • 基于gin的golang web开发:路由示例详解

    Gin是一个用Golang编写的HTTP网络框架.它的特点是类似于Martini的API,性能更好.在golang web开发领域是一个非常热门的web框架. 启动一个Gin web服务器 使用下面的命令安装Gin go get -u github.com/gin-gonic/gin 在代码里添加依赖 import "github.com/gin-gonic/gin" 快速启动一个Gin服务器的代码如下 package main import "github.com/gin-

  • golang类型转换组件Cast的使用详解

    开源地址 https://github.com/spf13/cast Cast是什么? Cast是一个库,以一致和简单的方式在不同的go类型之间转换. Cast提供了简单的函数,可以轻松地将数字转换为字符串,将接口转换为bool类型等等.当一个明显的转换是可能的时,Cast会智能地执行这一操作.它不会试图猜测你的意思,例如,你只能将一个字符串转换为int的字符串表示形式,例如"8".Cast是为Hugo开发的,Hugo是一个使用YAML.TOML或JSON作为元数据的网站引擎. 为什么

随机推荐