Golang控制协程执行顺序方法详解

目录
  • 循环控制
  • 通道控制
  • 互斥锁 async.Mutex

在 Go 里面的协程执行实际上默认是没有严格的先后顺序的。由于 Go 语言 GPM 模型的设计理念,真正执行实际工作的实际上是 GPM 中的 M(machine) 执行器,而我们的协程任务 G(goroutine) 协程需要被 P(produce) 关联到某个 M 上才能被执行。而每一个 P 都有一个私有队列,除此之外所有的 P 还共用一个公共队列。因此当我们创建了一个协程之后,并不是立即执行,而是进入队列等待被分配,且不同队列之间没有顺序关系可言。

但是在有些时候,我们并不是希望所有的协程都随机执行,所以我们需要想办法控制协程的执行顺序,这里整理了几种控制协程执行顺序的方法。

循环控制

思路就是我们要给每一个子协程设置一个序号,当前一个序号的协程执行完之后,才能执行下一个。

所以我们需要一个公共变量去记录当前可以执行的协程的序号,同时这个变量必须是线程安全的,以确保对于每个协程的每一次读写操作都是正确的。

首先循环等待合适的时机:

这个函数会不断循环获取一个 count 值,当 count 的值和参中的 i 相同时,他就会进入执行参数 fn 代表的函数,并且将 count 的值 +1 。

否则它将等待一纳秒然后重复以上步骤。

var count uint32
func sequence(i uint32, fn func()) {
	for {
		//使用原子操作
		if n := atomic.LoadUint32(&count); n == i {
			fn()
			atomic.AddUint32(&count, 1)
			break
		}
		time.Sleep(time.Nanosecond)
	}
}

然后用 sequence 来控制协程顺序:

我们将要执行的逻辑放在函数 fn 中,并放在 sequence 函数中执行,由函数 sequence 去确保写成的执行顺序。

最后 sequence(times, func() {}) 是为了让主协程最后退出,当然我们可一个使用通道 chan 去实现(可以参考上一篇)。

func main() {
	var times uint32 = 5
	for i := uint32(0); i < times; i++ {
		go func(i uint32) {
			fn := func() {
				fmt.Printf("this i is %v\n", i)
			}
			sequence(i, fn)
		}(i)
	}
	//让主协程等待最后执行
	sequence(times, func() {})
}

执行结果:

this i is 0
this i is 1
this i is 2
this i is 3
this i is 4

通道控制

原理就是,前后协程之间通过通道去相互限制,后一个协程尝试去获取一个通道里面的值,当通道中没有值时,就会一直阻塞。

而前一个协程则负责关闭通道,或向通道中发送值,当前一个协程完成了这个操作,后一个协程才可以结束阻塞,继续执行。

func main() {
	c1 := make(chan struct{})
	c2 := make(chan struct{})
	c3 := make(chan struct{})
	go func() {
		//协程一 不受限制 直接执行 执行结束后关闭通道一
		fmt.Println("this value is 0")
		close(c1)
	}()
	go func() {
		//协程二 需要从通道一中接收值 ,或者通道关闭时,获取到接收失败的结果,否则一直阻塞
		//执行结束后关闭通道二
		<-c1
		fmt.Println("this value is 1")
		close(c2)
	}()
	go func() {
		//协程三 需要从通道二中接收值 ,或者通道关闭时,获取到接收失败的结果,否则一直阻塞
		//执行结束后关闭通道三
		<-c2
		fmt.Println("this value is 2")
		close(c3)
	}()
	//主协程 需要从通道三中接收值 ,或者通道关闭时,获取到接收失败的结果,否则一直阻塞
	<-c3
}

执行结果

this value is 0
this value is 1
this value is 2

互斥锁 async.Mutex

直接上代码

func main() {
	times := 5
	//创建一个互斥锁数组 多一个给主协程用
	var cc = make([]*sync.Mutex, times+1)
	//往数组中塞入互斥锁,默认直接加锁
	for i := 0; i < len(cc); i++ {
		m := &sync.Mutex{}
		m.Lock()
		cc[i] = m
	}
	for i := 0; i < times; i++ {
		//创建子协程
		go func(index int) {
			//子协程尝试为数组中对应 index 位置的锁加锁,获取不到锁就等待
			//因为初始化的这些互斥锁默认就已经被锁住了,所以这里创建的子协程都会被阻塞
			//一旦获取到锁,就执行逻辑,最后将当前index的锁和index+1的锁释放,这样正在等待 index +1 位置的锁的子协程就可以继续执行了
			cc[index].Lock()
			fmt.Printf("this value is %d \n", index)
			cc[index].Unlock()
			cc[index+1].Unlock()
		}(i)
	}
	//将index 为 0 位置的锁解锁,让第一个子协程可以继续执行
	cc[0].Unlock()
	//为 index 为 times 的锁加锁,只有当最后一个子协程执行完毕后,这个锁才会解锁,主协程才能继续向下走
	cc[times].Lock()
	cc[times].Unlock()
}

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

(0)

相关推荐

  • golang的协程上下文的具体使用

    go协程上下文context golang的context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号.超时时间.截止时间.k-v 等 context是golang1.17版本之后才出的特性 上下文解决的问题 协程间的通信 例如web应用中,每一个请求都由一个协程去处理.当然处理处理请求的这个协程,一般我们还会起一些其他的协程,用来处理其他的业务,比如操作数据库,生份验证.文件读写等.这些协程是独立的,我们在当前的协程中无法感知到其他的协程执行的情况怎么样了.实用通道ch

  • golang协程池设计详解

    Why Pool go自从出生就身带"高并发"的标签,其并发编程就是由groutine实现的,因其消耗资源低,性能高效,开发成本低的特性而被广泛应用到各种场景,例如服务端开发中使用的HTTP服务,在golang net/http包中,每一个被监听到的tcp链接都是由一个groutine去完成处理其上下文的,由此使得其拥有极其优秀的并发量吞吐量 for { // 监听tcp rw, e := l.Accept() if e != nil { ....... } tempDelay = 0

  • 一文详解Golang协程调度器scheduler

    目录 1. 调度器scheduler的作用 2. GMP模型 3. 调度机制 1. 调度器scheduler的作用 我们都知道,在Go语言中,程序运行的最小单元是gorouines. 然而程序的运行最终都是要交给操作系统来执行的,以Java为例,Java中的一个线程对应的就是操作系统中的线程,以此来实现在操作系统中的运行.在Go中,gorouines比线程更轻量级,其与操作系统的线程也不是一一对应的关系,然而,最终我们想要执行程序,还是要借助操作系统的线程来完成,调度器scheduler的工作就

  • Golang协程池gopool设计与实现

    目录 Goroutine 协程池 gopool 核心实现 Pool Task Worker 整体来看 三个角色的定位 使用 sync.Pool 进行性能优化 Goroutine Goroutine 是 Golang 提供的一种轻量级线程,我们通常称之为「协程」,相比较线程,创建一个协程的成本是很低的.所以你会经常看到 Golang 开发的应用出现上千个协程并发的场景. Goroutine 的优势: 与线程相比,Goroutines 成本很低. 它们的堆栈大小只有几 kb,堆栈可以根据应用程序的需

  • Golang 之协程的用法讲解

    一.Golang 线程和协程的区别 备注:需要区分进程.线程(内核级线程).协程(用户级线程)三个概念. 进程.线程 和 协程 之间概念的区别 对于 进程.线程,都是有内核进行调度,有 CPU 时间片的概念,进行 抢占式调度(有多种调度算法) 对于 协程(用户级线程),这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的,因为是由用户程序自己控制,那么就很难像抢占式调度那样做到强制的 CPU 控制权切换到其他进程/线程,通常只能进行 协作式调度,需要协程自己主动把控

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

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

  • Golang控制协程执行顺序方法详解

    目录 循环控制 通道控制 互斥锁 async.Mutex 在 Go 里面的协程执行实际上默认是没有严格的先后顺序的.由于 Go 语言 GPM 模型的设计理念,真正执行实际工作的实际上是 GPM 中的 M(machine) 执行器,而我们的协程任务 G(goroutine) 协程需要被 P(produce) 关联到某个 M 上才能被执行.而每一个 P 都有一个私有队列,除此之外所有的 P 还共用一个公共队列.因此当我们创建了一个协程之后,并不是立即执行,而是进入队列等待被分配,且不同队列之间没有顺

  • PHP生成器(generator)和协程的实现方法详解

    本文实例讲述了PHP生成器(generator)和协程的实现方法.分享给大家供大家参考,具体如下: 先说一些废话 PHP 5.5 以来,新的诸多特性又一次令 PHP 焕发新的光彩,虽然在本文写的时候已是 PHP 7 alpha 2 发布后的一段时间,但此时国内依旧是 php 5.3 的天下.不过我认为新的特性迟早会因为旧的版本的逐渐消失而变得越发重要,尤其是 PHP 7 的正式版出来后,因此本文的目的就是为了在这之前,帮助一些 PHPer 了解一些他们从没有了解的东西.所以打算将以本篇作为博客中

  • PHP7下协程的实现方法详解

    前言 相信大家都听说过『协程』这个概念吧. 但是有些同学对这个概念似懂非懂,不知道怎么实现,怎么用,用在哪,甚至有些人认为yield就是协程! 我始终相信,如果你无法准确地表达出一个知识点的话,我可以认为你就是不懂. 如果你之前了解过利用PHP实现协程的话,你肯定看过鸟哥的那篇文章:在PHP中使用协程实现多任务调度| 风雪之隅 鸟哥这篇文章是从国外的作者翻译来的,翻译的简洁明了,也给出了具体的例子了. 我写这篇文章的目的,是想对鸟哥文章做更加充足的补充,毕竟有部分同学的基础还是不够好,看得也是云

  • Python并发编程协程(Coroutine)之Gevent详解

    Gevent官网文档地址:http://www.gevent.org/contents.html 基本概念 我们通常所说的协程Coroutine其实是corporateroutine的缩写,直接翻译为协同的例程,一般我们都简称为协程. 在linux系统中,线程就是轻量级的进程,而我们通常也把协程称为轻量级的线程即微线程. 进程和协程 下面对比一下进程和协程的相同点和不同点: 相同点: 我们都可以把他们看做是一种执行流,执行流可以挂起,并且后面可以在你挂起的地方恢复执行,这实际上都可以看做是con

  • Golang实现程序优雅退出的方法详解

    目录 1. 背景 2. 常见的几种平滑关闭 2.1 http server 平滑关闭 2.2 gRPC server 平滑关闭 2.3 worker 协程平滑关闭 2.4 实现 io.Closer 接口的自定义服务平滑关闭 2.5 集成其他框架怎么做 1. 背景 项目开发过程中,随着需求的迭代,代码的发布会频繁进行,在发布过程中,如何让程序做到优雅的退出? 为什么需要优雅的退出? 你的 http 服务,监听端口没有关闭,客户的请求发过来了,但处理了一半,可能造成脏数据. 你的协程 worker

  • Kotlin协程Dispatchers原理示例详解

    目录 前置知识 demo startCoroutineCancellable intercepted()函数 DefaultScheduler中找dispatch函数 Runnable传入 Worker线程执行逻辑 小结 前置知识 Kotlin协程不是什么空中阁楼,Kotlin源代码会被编译成class字节码文件,最终会运行到虚拟机中.所以从本质上讲,Kotlin和Java是类似的,都是可以编译产生class的语言,但最终还是会受到虚拟机的限制,它们的代码最终会在虚拟机上的某个线程上被执行. 之

  • Golang实现快速求幂的方法详解

    今天讲个有趣的算法:如何快速求nm,其中n和m都是整数. 为方便起见,此处假设m>=0,对于m< 0的情况,求出n|m|后再取倒数即可. 另外此处暂不考虑结果越界的情况(超过 int64 范围). 当然不能用编程语言的内置函数,我们只能用加减乘除来实现. n的m次方的数学含义是:m个n相乘:n*n*n...*n,也就是说最简单的方式是执行 m 次乘法. 直接用乘法实现的问题是性能不高,其时间复杂度是 O(m),比如 329要执行29次乘法,而乘法运算是相对比较重的,我们看看能否采用什么方法将时

  • DOM事件阶段以及事件捕获与事件冒泡先后执行顺序(图文详解)

    俗话说的好,好记性不如个烂笔头,这么多技术文章如果不去吃透,技术点很快就容易忘掉,下面是小编平时浏览的技术文章,整理的笔记,分享给大家. 开发过程中我们都希望使用别人成熟的框架,因为站在巨人的肩膀上会使得我们开发的效率大幅度提升.不过,我们也应该.必须了解其基本原理.比如DOM事件,jquery框架帮我们为我们封装和抽象了各浏览器的差异行为,为事件处理带来了极大的便利.不过浏览器逐步走向统一和标准化,我们可以更加安全地使用官方规范的接口.因为只有获得众多开发者的芳心,浏览器才会走得更远.正如我们

  • 对java for 循环执行顺序的详解

    如下所示: for(表达式1;表达式2;表达式3) { //循环体 } 先执行"表达式1",再进行"表达式2"的判断,判断为真则执行 "循环体",循环体执行完以后执行表达式3. 例如 for(int i=0;i<2;i++){ //TODO } 先执行 int i = 0; 然后 判断 i<2 然后执行函数体 最后执行i++ 然后轮回到判断i<2 int[] arr = new int[3]; int j; arr[0] = 1

  • java 使用idea将工程打成jar并创建成exe文件类型执行的方法详解

    第一部分: 使用idea 打包工程jar 1.准备好一份 开发好的 可执行的 含有main方法的 工程. 例如:我随便写的main方法 public static void main(String[] args) throws IOException { Properties properties = System.getProperties(); String osName = properties.getProperty("os.name"); System.out.println

随机推荐