Go36-32-context.Context

context.Context

sync.WaitGroup类型是一个实现一对多goroutine协作流程的同步工具。还有另一种工具也可以实现这种协作流程。

回顾sync.WaitGroup实现协作流程

在使用WaitGroup的时候,建议是用“先统一Add,再并发Done,最后Wait”的模式来构建协作流程。要避免并发的调用Add方法。这就带来一个问题,需要在一开始就能确定执行子任务的goroutine的数量,至少也是在启动goroutine之前。
下面是一个示例,稍微做了一些改造:

package main

import (
    "time"
    "fmt"
    "sync"
    "sync/atomic"
)

func coordinateWithWaitGroup() {
    total := 12
    var num int32
    var wg sync.WaitGroup
    // 定义好goroutine中返回前要执行的defer函数
    deferFunc := func() {
        wg.Done()
    }
    for i := 0; i < total; i++ {
        wg.Add(1)
        go addNum(&num, i, deferFunc)
    }
    wg.Wait()
}

// 这个函数的defer函数通过参数来给出
func addNum(numP *int32, id int, deferFunc func()) {
    defer deferFunc()
    for i := 1; ; i++ {
        currNum := atomic.LoadInt32(numP)
        newNum := currNum + 1
        time.Sleep(time.Millisecond * 200)
        if atomic.CompareAndSwapInt32(numP, currNum, newNum) {
            fmt.Printf("id: %02d 第 %02d 次更新num成功: %d\n", id, i, newNum)
            break
        }
    }
}

func main() {
    coordinateWithWaitGroup()
}

这里的改造是为了更像之后要使用context包时的用法,不过在使用规则上还是满足WaitGroup的要求的。

通过context包实现协作流程

这里就是要在写一个coordinateWithWaitContext函数,来代替上面的coordinateWithWaitGroup函数。两个函数要具有相同的功能。
这里先直接给出示例代码了:

func coordinateWithWaitContext() {
    total := 12
    var num int32
    cxt, cancelFunc := context.WithCancel(context.Background())
    // 定义好goroutine中返回前要执行的defer函数,这里用到了上面的cancelFunc
    deferFunc := func() {
        if atomic.LoadInt32(&num) == int32(total) {
            cancelFunc()
        }
    }
    for i := 0; i < total; i++ {
        go addNum(&num, i, deferFunc)
    }
    <- cxt.Done()
}

所有的变化都在上面这个函数里了。这里先后调用了context.Background函数和context.WithCancel函数。得到了一个可撤销context.Context类型的值,赋值给了变量cxt。还有一个context.CancelFunc类型的撤销函数,赋值给了变量cancelFunc。
这里在判断goroutine执行完毕的依据是通过判断num里的值。一旦判断完成,就会调用之前准备好的cancelFunc函数,此时cxt.Done函数返回的通道就会接收到值,结束等待。

和WaitGroup的比较
WaitGroup需要事先知道所有goroutine的数量,而context这里更关心是否满足某个条件,一旦条件满足就可以退出。
这里我想提一下python,让我想到了python中的for循环和while循环。能用for循环就不要用while循环。使用while循环可能由于条件判断复杂了,造成条件永远无法满足而成了死循环。使用for循环的话就没有这个问题了。不过当循环的退出和数量没有关系时,只能用while循环了。
就好比WaitGroup,如果可以通过goroutine的数量判断,那么应该还是使用WaitGroup好。如果遇到结束条件和goroutine数量无关的时候,就只能用context了。

context.Context类型

context.Context类型,是在Go 1.7发布时才被加入到标准库的。而后,标准库中的很多其他代码包都为了支持它而进行了扩展,包括:os/exec包、net包、database/sql包、runtime/pprof包和runtime/trace包,等等。
之所以会收到众多代码包的积极支持,主要因为它是一种非常通用的同步工具。它的值不但可以任意的扩散,而且还可以被用来传递额外的信息和信号。就是Context类型可以提供一个代表上下文的值,之类值是并发安全的,也就是说它可以被传播给多个goroutine。

接口类型
Context最新实际是一个接口类型,在context包中实现该接口的所有私有类型,都是基于某个数据类型的指针类型。所以,如此传播并不会影响该类型值的功能和安全。

可繁衍的
Context类型的值是可以繁衍的,这意味着可以通过一个Context值产生出任意个子值。这些子值可以携带父值的属性和数据,也可以相应通过其父值传达的信号。如此,所有的Context值共同构成了一颗代表了上下文全貌的属性结构。树的根节点是一个已经在context包中预定义好的context值,它是全局唯一的。通过调用context.Background函数,就可以获取到它。

包内的函数
在context包中,包含了4个用于繁衍Context值的函数:

  • WithCancel,产生一个可撤销的parent的子值
  • WithDeadline,产生一个会定时撤销的parent的子值
  • WithTimeout,同上,也是定时撤销的parent的子值
  • WithValue,产生一个会携带额外数据的parent的子值

这些函数的第一个参数类型都是context.Context,而名称都为parent。顾名思义,这个位置上的参数对应的都是产生Context值的父值。

撤销信号在上下文树中的传播

context包中的WithCancel、WithDeadline和WithTimeout都是被用来基于给定的COntext值产生可撤销的子值的。

WithCancel
这个函数在被调用后,产生两个结果值。第一个是可撤销的Context值,第二个是用于触发撤销信号的函数。
撤销函数被调用后,对应的Context值会先关闭它内部的接收通道,通道关闭了接收该通道的操作就会立即返回,就是Done方法返回的那个通道。然后,它还会向它的所有子值传达撤销信号。这些子值如果还有子值,就会一级一级把撤销信号传递下去。最后,这个Context值会断开它与其父值之间的关联。

WithDeadline和WithTimeout
通过调用WithDeadline函数或者WithTimeout函数生成的Context值也是可撤销的。它们不但可以被手动撤销,还会依据在生成是给定的过期时间,自动地进行定时撤销。这里的定时撤销功能是借助它们内部的计时器来实现的。
当过期时间到达时,两种Context值的行为与手动撤销是的行为是几乎一致的,只是多了一步停止并释放掉内部的计时器。
WithDeadline和WithTimeout是相似的。都是通过设置,会在某个时间自动触发,就是ctx.Done()能够取到值。差别是,DeadLine是设置一个时间点,时间对上了就到期。Timeout是设置一段时间,比如几秒,过个这段时间,就超时。其实底层的Timeout也是通过Deadlin实现的。

WithValue
这个函数得到的值是不可撤销的。撤销信号在传播时,若遇到它们会直接跨过,并试图将信息直接传给它们的子值。

传递数据

通过WithValue函数产生新的Context值的时候需要3个参数:父值、键和值。这里键必须是可判断等的,类似字典的键。不过Context值并不是用字典来存储键和值的,而是简单地存储在父值相应的字段中。
通过Value方法,可以获取数据。在调用包含属性的Context值的Value方法是,会先判断给定的键,如有有就返回存储的值,否则会到其父值中继续查找,会一直沿着上下文根节点的方法一直查找。因为其他几种Context值都是无法携带数据的,所以Value方法在查找的时候,会跨过这这些Context值。

无法改变数据
Context接口没有提供改变数据的方法,所以通常只能通过在上下文数中添加含数据的Context值来存储新的数据,或者通过撤销此种值的父值丢弃掉相应的数据。如果存储在这里的数据可以从外部改变,那么必须自信保证安全。

下面这个示例展示了Context值里数据的传递:

package main

import (
    "context"
    "fmt"
    "time"
)

type myKey int

func main() {
    keys := []myKey{
        myKey(20),
        myKey(30),
        myKey(60),
        myKey(61),
    }
    values := []string{
        "value in node2",
        "value in node3",
        "value in node6",
        "value in node6Branch",
    }

    rootNode := context.Background()
    node1, cancelFunc1 := context.WithCancel(rootNode)
    defer cancelFunc1()

    node2 := context.WithValue(node1, keys[0], values[0])
    node3 := context.WithValue(node2, keys[1], values[1])
    fmt.Printf("The value of the key %v found in the node3: %v\n",
        keys[0], node3.Value(keys[0]))
    fmt.Printf("The value of the key %v found in the node3: %v\n",
        keys[1], node3.Value(keys[1]))
    fmt.Printf("The value of the key %v found in the node3: %v\n",
        keys[2], node3.Value(keys[2]))
    fmt.Println()

    node4, cancelFunc4 := context.WithCancel(node3)
    defer cancelFunc4()
    node5, cancelFunc5 := context.WithTimeout(node4, time.Hour)
    defer cancelFunc5()
    fmt.Printf("The value of the key %v found in the node5: %v\n",
        keys[0], node5.Value(keys[0]))
    fmt.Printf("The value of the key %v found in the node5: %v\n",
        keys[1], node5.Value(keys[1]))
    fmt.Println()

    node6 := context.WithValue(node5, keys[2], values[2])
    fmt.Printf("The value of the key %v found in the node6: %v\n",
        keys[0], node6.Value(keys[0]))
    fmt.Printf("The value of the key %v found in the node6: %v\n",
        keys[2], node6.Value(keys[2]))
    fmt.Println()

    node6Branch := context.WithValue(node5, keys[3], values[3])
    fmt.Printf("The value of the key %v found in the node6Branch: %v\n",
        keys[1], node6Branch.Value(keys[1]))
    fmt.Printf("The value of the key %v found in the node6Branch: %v\n",
        keys[2], node6Branch.Value(keys[2]))
    fmt.Printf("The value of the key %v found in the node6Branch: %v\n",
        keys[3], node6Branch.Value(keys[3]))
    fmt.Println()

    node7, cancelFunc7 := context.WithCancel(node6)
    defer cancelFunc7()
    node8, cancelFunc8 := context.WithTimeout(node7, time.Hour)
    defer cancelFunc8()
    fmt.Printf("The value of the key %v found in the node8: %v\n",
        keys[1], node8.Value(keys[1]))
    fmt.Printf("The value of the key %v found in the node8: %v\n",
        keys[2], node8.Value(keys[2]))
    fmt.Printf("The value of the key %v found in the node8: %v\n",
        keys[3], node8.Value(keys[3]))
}

总结

Context类型是一个可以实现多goroutine协作流程同步的工具。还可以通过它的值传达撤销信号或传递数据。
Context类型的值大体可分3种:

  • 根Context值
  • 可撤销的Context值
  • 含数据的Context值

所有的Context值共同构成了一颗上下文树。这棵树的作用域是全局的,根Context值就是树的根,它也是全局唯一的,并且不提供任何额外的功能。
包含数据的Context值不能被撤销,可撤销的Context值又无法携带数据。但是,由于它们共同组成了一个有机的整体,即上下文数,所以在功能上要比sync.WaitGroup强大的多。

这个系列偏重理论,就少了很多实际的应用,关于context包,我之前还有一篇:
http://blog.51cto.com/steed/2330218
在这篇里介绍了两个主要功能:

  • 控制超时时间
  • 保存上下文数据

原文地址:http://blog.51cto.com/steed/2347877

时间: 2024-10-31 07:06:40

Go36-32-context.Context的相关文章

关于AlertDialog.Builder(Context context)中所应传入的context

错误报告: 10-20 14:34:46.565: E/AndroidRuntime(23098): FATAL EXCEPTION: main10-20 14:34:46.565: E/AndroidRuntime(23098): android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application 问题分析: 在AlertDialog.Builder

idea 严重: Error configuring application listener of class org.springframework.web.context.Context 后面省略

根本原因:jar文件没有同步发布到自己项目的lib目录中 解决方案:把之前在这个位置的jar文件,put into 到 /WEB-INF/lib 目录下即可 原文地址:https://www.cnblogs.com/kinome/p/9033540.html

PoEdu - Windows阶段班 【Po学校】Lesson006_线程_线程的启动到消亡 &amp;线程状态 &amp; 线程安全 &amp; CONTEXT结构体 &amp; 令牌锁

011_线程启动到死亡的详细讲解 1. 线程内核对象 使用计数 2 ##决定当前线程何时销毁 暂停计数 1 ##UINT类型初始为1,可以暂停多次,如置为0则取消暂停. 退出代码 STILL_ACTIVE Signaled FALSE CONTEXT 为空 2. 栈##在隶属于当前进程的空间中,分配一块"栈"空间,以供线程使用 参数 lpParam 入口地址 lpfnAddr 3. CONTEXT##线程上一次运行时的寄存器 IP(指令寄存器) void RtlUserThreadSt

Application Context的设计

基本上每一个应用程序都会有一个自己的Application,并让它继承自系统的Application类,然后在自己的Application类中去封装一些通用的操作.其实这并不是Google所推荐的一种做法,因为这样我们只是把Application当成了一个通用工具类来使用的,而实际上使用一个简单的单例类也可以实现同样的功能.但是根据观察,有太多的项目都是这样使用Application的.当然这种做法也并没有什么副作用,只是说明还是有不少人对于Application理解的还有些欠缺.那么这里我们先

Android应用Context详解及源码解析

[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 今天突然想起之前在上家公司(做TV与BOX盒子)时有好几个人问过我关于Android的Context到底是啥的问题,所以就马上要诞生这篇文章.我们平时在开发App应用程序时一直都在使用Context(别说你没用过,访问当前应用的资源.启动一个activity等都用到了Context),但是很少有人关注过这玩意到底是啥,也很少有人知道getApplication与getApplica

[hadoop]Hadoop源码分析-Context

学编程第一个肯定是hello world,Hadoop也不例外,它的hello world就是Wordcount,单词统计例子 1 package org.apache.hadoop.examples; 2 3 import java.io.IOException; 4 import java.util.StringTokenizer; 5 6 import org.apache.hadoop.conf.Configuration; 7 import org.apache.hadoop.fs.P

context.startActivity时报错startActivity() from outside of an Activity context require the FLAG_ACTIVITY_NEW_TASK flag

源代码如下: 1 public class ReceiveHandler extends BroadcastReceiver{ 2 3 private final String ACTION_RECE_LinkReply="com.hutao.linkRequestReceive"; 4 private Context context; 5 private int ReceiveNumber; 6 private byte businessType; 7 @Override 8 pub

Android Context 是什么?

andorid 开发(42)  版权声明:本文为博主原创文章,未经博主允许不得转载. [转载请注明出处:http://blog.csdn.net/feiduclear_up CSDN 废墟的树] PS:修该了一些有误区的地方. 引言 Context对于Android开发人员来说并不陌生,项目中我们会经常使用Context来获取APP资源,创建UI,获取系统Service服务,启动Activity,绑定Service,发送广播,获取APP信息等等.那么Context到底是什么?Context又是怎

ActionBar(18)context action bar 两个示例

一.通过activity启动Context Action Bar 1.主java public class ActivityActionModeFrgmt extends Fragment implements OnCheckedChangeListener, ActionMode.Callback { private ActionMode mActionMode; private CheckBox mCheckBox; @Override public View onCreateView(La

golang Context for goroutines

概要 goroutine 的控制 取消控制 超时控制 goroutine 之间的传值 总结 概要 golang 的提供的 channel 机制是基于 CSP(Communicating Sequencial Processes)模型的并发模式. 通过 channel, 可以很方便的写出多个 协程 (goroutine)之间协作的代码, 将顺序的代码改成并行的代码非常简单. 改造成并行的代码之后, 虽然可以更好的利用多核的硬件, 有效的提高代码的执行效率, 但是, 也带来了代码控制的问题. 并行的