Go中groutine通信与context控制实例详解

目录
  • 需求背景:
  • Groutine的并发控制:
  • Context:
  • 看看代码:
  • 总结

需求背景:

项目中需要定期执行任务A来做一些辅助的工作,A的执行需要在超时时间内完成,如果本次执行超时了,那就不对本次的执行结果进行处理(即放弃这次执行)。同时A又依赖B,C两个子任务的执行结果。B, C之间相互独立,可以并行的执行。但无论B,C哪一个执行失败或超时都会导致本次任务执行失败。

Groutine的并发控制:

go中对于groutine的并发控制有三种解决方案:

  • 通过channel控制。

    父groutine中声明无buffer的chan切片,向要开启的子groutine中传入切片中的一个chan

    子groutine执行完成后向这个chan中写入数据(可以是和父groutine通信的也可以不是)

    父groutine遍历所有chan并执行 <-chan 操作, 利用无buffer的channel只有读写同时准备好才能执行的特性进行控

  • WaitGroup控制。

    通过sync.Waitgroup, 每开启一个子groutine就执行 wg.Add(1), 子groutine内部执行wg.Done(), 父groutine通过wg.Wait()等待所有子协程
  • Context控制。

    waitGroup和Context应该是Go中较为常用的两种并发控制。相较而言,context对于派生groutine有更强大的控制力,可以控制多级树状分布的groutine。

    当然waitGroup的子groutine也可以再开启新的waitGroup并且等待多个孙groutine, 但是不如context的控制更加方便.

Context:

context包提供了四个方法创建不同类型的context

  • WitchCancel()
  • WithDeadline()
  • WithTimeout()
  • WithValue()

WithValue()主要用于通过context传递一些上下文消息,不在本次讨论中。WithTimeout和WithDeadLine几乎是一致的。但无论哪种,控制groutine都需要使用ctx.Done()方法. Done() 方法返回一个 "只读"的chan <-chan struct{}, 需要编写代码监听这个chan,一旦收到它的消息就说明这个context应当结束了,无论是到达了超时时间还是在某个地方主动cancel()了方法。

看看代码:

var ch1 chan int
var ch2 chan int
<br>// 任务A, 通过最外层的for来控制定期执行
func TestMe(t *testing.T) {
    ch1 = make(chan int, 0)
    ch2 = make(chan int, 0)
    count := 0
    for {
        count ++
        ctx, cancel := context.WithTimeout(context.Background(), time.Second * 2)<br>                // 任务A的逻辑部分,开启子任务B, C。<br>                // B,C通过ch1,ch2和A通信。<br>                // 同时监听ctx.Done,如果超时了立即结束本次任务不继续执行
        go func(ctx context.Context) {
            go g1(ctx, count)
            go g2(ctx, count)
            v1, v2 := -1, -1
            for v1 == -1 || v2 == -1 {
                select {
                case <- ctx.Done():
                    cancel()
                    fmt.Println("父级2超时退出,当前count值为", count, "当前时间:", time.Now())
                    return
                case v1 = <- ch1:
                case v2 = <- ch2:
                }
            }
            fmt.Println("正常执行完成退出, 开启下次循环,当前count值为:", count, "当前 v1: ", v1, "当前 v2: ", v2)
        }(ctx)<br>                // 任务A监控ctx是否到达timeOUT,timeout就终止本次执行
        select {
        case <- ctx.Done():
            fmt.Println("父级1超时退出,当前count值为", count, "当前时间:", time.Now())
        }
        time.Sleep(time.Second * 3)
    }
}
<br>// 改进后的任务B,即使计算出了结果,也不会再向ch1写数据了,不会造成脏数据
func g1 (ctx context.Context, num int) {
    fmt.Println("g1 num", num, "time", time.Now())
    select {
    case <-ctx.Done():
        fmt.Println("子级 g1关闭, 不向channel中写数据")
        return
    default:
        ch1 <- num
    }
}
<br>// 改进前的任务C
func g2 (ctx context.Context, num int) {
    fmt.Println("g2 num", num, "time", time.Now())
    ch2 <- num

基于上述代码,子任务B, C的处理其实有一次较大的变动。一开始B,C都是类似于子任务C,即g2的这种写法。

这种写法在执行完成后就把自身的结果交给channel, 父groutine通过channel来读取数据,正常情况下也能工作。但异常情况下,如子任务B执行完成,子任务C(即g2)因为网络通信等原因执行了5s(超过context的最大时长), 就会出现比较严重的问题。到达超时时间后,A检测到了超时就自动结束了本次任务,但g2还在执行过程中。g2执行完成后向ch2写数据阻塞了(因为A已关闭,没有读取ch2的groutine)。下一个循环中A再次开启读取ch1与ch2, 实际上读取ch1是当次的结果,ch2是上次任务中g2返回的结果,导致两处依赖的数据源不一致。

模拟上述情况,将g2做了一些改动如下:

// 在第3次任务重等待3s, 使得它超时<br>func g2 (ctx context.Context, num int) {
    if num == 3 {
        time.Sleep(time.Second * 3)
    }
    fmt.Println("g2 num", num, "time", time.Now())
    ch2 <- num
}

实际上,如果想要通过context控制groutine, 一定要监控Done()方法。如g1所示。相同情况下A超时退出,C仍在执行。C执行完成后先检测Context是否已退出,如果已退出就不再向ch2中写入本次的数据了。(抛砖引玉了,也可能有更好的写法,希望大佬不吝赐教)

将g2改成和g1类似的写法后测试结果如下:

func g2 (ctx context.Context, num int) {
    if num == 3 {
        time.Sleep(time.Second * 10)
        fmt.Println("这次g2 超时,应当g1, g2都不返回")
    }
    fmt.Println("g2 num", num, "time", time.Now())
    select {
    case <-ctx.Done():
        fmt.Println("子级 g2关闭, 不向channel中写数据")
        return
    default:
        ch2 <- num
    }
}

总结

到此这篇关于Go中groutine通信与context控制的文章就介绍到这了,更多相关Go groutine通信与context控制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • GoLang之使用Context控制请求超时的实现

    起因   之前接触了一个需求:提供一个接口,这个接口有一个超时时间,如果超时了返回超时异常:这个接口中调用其他的接口,如果调用超时了,所有请求全部结束.   在这个接口中,我使用了go协程去调用其他接口,所以不仅涉及到请求的超时控制,而且还涉及到父协程对子协程的控制问题.在翻阅了一些资料之后,了解到了Context的基本知识. Context   Context是golang.org.pkg下的一个包,类型是接口类型.主要功能有 父协程控制所有的子协程   Context可以通过context.

  • 深入Golang之context的用法详解

    context在Golang的1.7版本之前,是在包golang.org/x/net/context中的,但是后来发现其在很多地方都是需要用到的,所有在1.7开始被列入了Golang的标准库.Context包专门用来简化处理单个请求的多个goroutine之间与请求域的数据.取消信号.截止时间等相关操作,那么这篇文章就来看看其用法和实现原理. 源码分析 首先我们来看一下Context里面核心的几个数据结构: Context interface type Context interface { D

  • Go 并发控制context实现原理剖析(小结)

    1. 前言 Golang context是Golang应用开发常用的并发控制技术,它与WaitGroup最大的不同点是context对于派生goroutine有更强的控制力,它可以控制多级的goroutine. context翻译成中文是"上下文",即它可以控制一组呈树状结构的goroutine,每个goroutine拥有相同的上下文. 典型的使用场景如下图所示: 上图中由于goroutine派生出子goroutine,而子goroutine又继续派生新的goroutine,这种情况下

  • Go中groutine通信与context控制实例详解

    目录 需求背景: Groutine的并发控制: Context: 看看代码: 总结 需求背景: 项目中需要定期执行任务A来做一些辅助的工作,A的执行需要在超时时间内完成,如果本次执行超时了,那就不对本次的执行结果进行处理(即放弃这次执行).同时A又依赖B,C两个子任务的执行结果.B, C之间相互独立,可以并行的执行.但无论B,C哪一个执行失败或超时都会导致本次任务执行失败. Groutine的并发控制: go中对于groutine的并发控制有三种解决方案: 通过channel控制. 父grout

  • Spring jdbc中数据库操作对象化模型的实例详解

    Spring jdbc中数据库操作对象化模型的实例详解 Spring Jdbc数据库操作对象化 使用面向对象方式表示关系数据库的操作,实现一个线程安全可复用的对象模型,其顶级父类接口RdbmsOperation. SqlOperation继承该接口,实现数据库的select, update, call等操作. 1.查询接口:SqlQuery 1) GenericSqlQuery, UpdatableSqlQuery, MappingSqlQueryWithParameter 2) SqlUpda

  • Android原生音量控制实例详解

    本文主要涉及AudioService.还是基于5.1.1版本的代码. AudioService.java文件位于/framework/base/media/java/android/media/下. 音量控制是AudioService最重要的功能之一.先总结一下: AudioService音量管理的核心是VolumeStreamState.它保存了一个流类型所有的音量信息. VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的.所以进行音

  • vue组件中使用props传递数据的实例详解

    在 Vue 中,父子组件的关系可以总结为 props向下传递,事件向上传递.父组件通过 props 给子组件下发数据,子组件通过事件给父组件发送消息.看看它们是怎么工作的.  一.基本用法 组件不仅仅是要把模板的内容进行复用,更重要的是组件间要进行通信. 在组件中,使用选项props 来声明需要从父级接收的数据, props 的值可以是两种, 一种是字符串数组,一种是对象. 1.1 字符串数组: <div id="app4"> <my-component4 messa

  • Python : turtle色彩控制实例详解

    turtle.pencolor(* args ) 返回或设置pencolor. 允许四种输入格式: pencolor() 将当前的pencolor返回为颜色规范字符串或元组(参见示例).可用作另一种颜色/ pencolor / fillcolor调用的输入. pencolor(colorstring) 设置pencolor到colorstring,这是一个Tk的颜色指定的字符串,例如"red","yellow",或"#33cc8c". penco

  • C++ 中引用与指针的区别实例详解

    C++ 中引用与指针的区别实例详解 引用是从C++才引入的,在C中不存在.为了搞清楚引用的概念,得先搞明白变量的定义及引用与变量的区别,变量的要素一共有两个:名称与空间. 引用不是变量,它仅仅是变量的别名,没有自己独立的空间,它只符合变量的"名称"这个要素,而"空间"这个要素并不满足.换句话说,引用需要与它所引用的变量共享同一个内存空间,对引用所做的改变实际上是对所引用的变量做出修改.并且引用在定义的时候就必须被初始化.     参数传递的类型及相关要点: 1 按值

  • SQLserver中cube:多维数据集实例详解

    1.cube:生成多维数据集,包含各维度可能组合的交叉表格,使用with 关键字连接 with cube 根据需要使用union all 拼接 判断 某一列的null值来自源数据还是 cube 使用GROUPING关键字 GROUPING([档案号]) = 1 : null值来自cube(代表所有的档案号) GROUPING([档案号]) = 0 : null值来自源数据 举例: SELECT * INTO ##GET FROM (SELECT * FROM ( SELECT CASE WHEN

  • Angular中$cacheFactory的作用和用法实例详解

    先说下缓存: 一个缓存就是一个组件,它可以透明地储存数据,以便以后可以更快地服务于请求.多次重复地获取资源可能会导致数据重复,消耗时间.因此缓存适用于变化性不大的一些数据,缓存能够服务的请求越多,整体系统性能就能提升越多. $cacheFactory介绍: $cacheFactory是一个为Angular服务生产缓存对象的服务.要创建一个缓存对象,可以使用$cacheFactory通过一个ID和capacity.其中,ID是一个缓存对象的名称,capacity则是描述缓存键值对的最大数量. 1.

  • C#中WPF ListView绑定数据的实例详解

    C#中WPF ListView绑定数据的实例详解 WPF中ListView用来显示数据十分方便, 我们可以将它分成几个列,每一个列用来显示一条数据,但是又是在一方之中. 那么怎样实现这样的效果的呢,这就要用绑定了. 我们先来看一看他的xmal代码 <ListView Name="receiveList" Grid.Row="0"> <ListView.View> <GridView> <GridView.Columns>

  • Oracle表中重复数据去重的方法实例详解

    Oracle表中重复数据去重的方法实例详解 我们在项目中肯定会遇到一种情况,就是表中没有主键 有重复数据 或者有主键 但是部分字段有重复数据 而我们需要过滤掉重复数据 下面是一种解决方法 delete from mytest ms where rowid in (select aa.rid from (select rowid as rid, row_number() over(partition by s.name order by s.id) as nu from mytest s) aa

随机推荐