Anonymous 发表于 2023-2-10 10:16

Go语言实战笔记(二十)| Go Context

<p>控制并发有两种经典的方式,一种是WaitGroup,另外一种就是Context,今天我就谈谈Context。</p>

<h2>什么是WaitGroup</h2>

<p>WaitGroup以前我们在并发的时候介绍过,它是一种控制并发的方式,它的这种方式是控制多个goroutine同时完成。</p>

<div>
<div>
<table style="background:0px 0px; border-spacing:0px; border:1px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f"> 1
</span><span style="color:#7f7f7f"> 2
</span><span style="color:#7f7f7f"> 3
</span><span style="color:#7f7f7f"> 4
</span><span style="color:#7f7f7f"> 5
</span><span style="color:#7f7f7f"> 6
</span><span style="color:#7f7f7f"> 7
</span><span style="color:#7f7f7f"> 8
</span><span style="color:#7f7f7f"> 9
</span><span style="color:#7f7f7f">10
</span><span style="color:#7f7f7f">11
</span><span style="color:#7f7f7f">12
</span><span style="color:#7f7f7f">13
</span><span style="color:#7f7f7f">14
</span><span style="color:#7f7f7f">15
</span><span style="color:#7f7f7f">16</span><span style="color:#7f7f7f">
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:735.789px">
                        <pre>
<strong>func</strong> <strong>main</strong>() {
        <strong>var</strong> wg sync.WaitGroup

        wg.<strong>Add</strong>(<span style="color:#009999">2</span>)
        <strong>go</strong> <strong>func</strong>() {
                time.<strong>Sleep</strong>(<span style="color:#009999">2</span><strong>*</strong>time.Second)
                fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;1号完成&quot;</span>)
                wg.<strong>Done</strong>()
        }()
        <strong>go</strong> <strong>func</strong>() {
                time.<strong>Sleep</strong>(<span style="color:#009999">2</span><strong>*</strong>time.Second)
                fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;2号完成&quot;</span>)
                wg.<strong>Done</strong>()
        }()
        wg.<strong>Wait</strong>()
        fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;好了,大家都干完了,放工&quot;</span>)
}
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>一个很简单的例子,一定要例子中的2个goroutine同时做完,才算是完成,先做好的就要等着其他未完成的,所有的goroutine要都全部完成才可以。</p>

<p>这是一种控制并发的方式,这种尤其适用于,好多个goroutine协同做一件事情的时候,因为每个goroutine做的都是这件事情的一部分,只有全部的goroutine都完成,这件事情才算是完成,这是等待的方式。</p>

<p>在实际的业务种,我们可能会有这么一种场景:需要我们主动的通知某一个goroutine结束。比如我们开启一个后台goroutine一直做事情,比如监控,现在不需要了,就需要通知这个监控goroutine结束,不然它会一直跑,就泄漏了。</p>

<h2>chan通知</h2>

<p>我们都知道一个goroutine启动后,我们是无法控制他的,大部分情况是等待它自己结束,那么如果这个goroutine是一个不会自己结束的后台goroutine呢?比如监控等,会一直运行的。</p>

<p>这种情况化,一直傻瓜式的办法是全局变量,其他地方通过修改这个变量完成结束通知,然后后台goroutine不停的检查这个变量,如果发现被通知关闭了,就自我结束。</p>

<p>这种方式也可以,但是首先我们要保证这个变量在多线程下的安全,基于此,有一种更好的方式:chan + select 。</p>

<div>
<div>
<table border="1" style="background:0px 0px; border-spacing:0px; border:0px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f"> 1
</span><span style="color:#7f7f7f"> 2
</span><span style="color:#7f7f7f"> 3
</span><span style="color:#7f7f7f"> 4
</span><span style="color:#7f7f7f"> 5
</span><span style="color:#7f7f7f"> 6
</span><span style="color:#7f7f7f"> 7
</span><span style="color:#7f7f7f"> 8
</span><span style="color:#7f7f7f"> 9
</span><span style="color:#7f7f7f">10
</span><span style="color:#7f7f7f">11
</span><span style="color:#7f7f7f">12
</span><span style="color:#7f7f7f">13
</span><span style="color:#7f7f7f">14
</span><span style="color:#7f7f7f">15
</span><span style="color:#7f7f7f">16
</span><span style="color:#7f7f7f">17
</span><span style="color:#7f7f7f">18
</span><span style="color:#7f7f7f">19
</span><span style="color:#7f7f7f">20</span><span style="color:#7f7f7f">
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:735.789px">
                        <pre>
<strong>func</strong> <strong>main</strong>() {
        stop <strong>:=</strong> <span style="color:#0086b3">make</span>(<strong>chan</strong> <strong>bool</strong>)

        <strong>go</strong> <strong>func</strong>() {
                <strong>for</strong> {
                        <strong>select</strong> {
                        <strong>case</strong> <strong>&lt;-</strong>stop:
                                fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;监控退出,停止了...&quot;</span>)
                                <strong>return</strong>
                        <strong>default</strong>:
                                fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;goroutine监控中...&quot;</span>)
                                time.<strong>Sleep</strong>(<span style="color:#009999">2</span> <strong>*</strong> time.Second)
                        }
                }
        }()

        time.<strong>Sleep</strong>(<span style="color:#009999">10</span> <strong>*</strong> time.Second)
        fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;可以了,通知监控停止&quot;</span>)
        stop<strong>&lt;-</strong> <strong>true</strong>
        <em>//为了检测监控过是否停止,如果没有监控输出,就表示停止了
</em>        time.<strong>Sleep</strong>(<span style="color:#009999">5</span> <strong>*</strong> time.Second)

}
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>例子中我们定义一个stop的chan,通知他结束后台goroutine。实现也非常简单,在后台goroutine中,使用select判断stop是否可以接收到值,如果可以接收到,就表示可以退出停止了;如果没有接收到,就会执行default里的监控逻辑,继续监控,只到收到stop的通知。</p>

<p>有了以上的逻辑,我们就可以在其他goroutine种,给stop&nbsp;chan发送值了,例子中是在main goroutine中发送的,控制让这个监控的goroutine结束。</p>

<p>发送了stop&lt;- true结束的指令后,我这里使用time.Sleep(5 * time.Second)故意停顿5秒来检测我们结束监控goroutine是否成功。如果成功的话,不会再有goroutine监控中...的输出了;如果没有成功,监控goroutine就会继续打印goroutine监控中...输出。</p>

<p>这种chan+select的方式,是比较优雅的结束一个goroutine的方式,不过这种方式也有局限性,如果有很多goroutine都需要控制结束怎么办呢?如果这些goroutine又衍生了其他更多的goroutine怎么办呢?如果一层层的无穷尽的goroutine呢?这就非常复杂了,即使我们定义很多chan也很难解决这个问题,因为goroutine的关系链就导致了这种场景非常复杂。</p>

<p>&nbsp;</p>

Anonymous 发表于 2023-2-10 10:17

<h2>初识Context</h2>

<p>上面说的这种场景是存在的,比如一个网络请求Request,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他的goroutine。所以我们需要一种可以跟踪goroutine的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的Context,称之为上下文非常贴切,它就是goroutine的上下文。</p>

<p>下面我们就使用Go Context重写上面的示例。</p>

<div>
<div>
<table border="1" style="background:0px 0px; border-spacing:0px; border:0px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f"> 1
</span><span style="color:#7f7f7f"> 2
</span><span style="color:#7f7f7f"> 3
</span><span style="color:#7f7f7f"> 4
</span><span style="color:#7f7f7f"> 5
</span><span style="color:#7f7f7f"> 6
</span><span style="color:#7f7f7f"> 7
</span><span style="color:#7f7f7f"> 8
</span><span style="color:#7f7f7f"> 9
</span><span style="color:#7f7f7f">10
</span><span style="color:#7f7f7f">11
</span><span style="color:#7f7f7f">12
</span><span style="color:#7f7f7f">13
</span><span style="color:#7f7f7f">14
</span><span style="color:#7f7f7f">15
</span><span style="color:#7f7f7f">16
</span><span style="color:#7f7f7f">17
</span><span style="color:#7f7f7f">18
</span><span style="color:#7f7f7f">19
</span><span style="color:#7f7f7f">20</span><span style="color:#7f7f7f">
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:735.789px">
                        <pre>
<strong>func</strong> <strong>main</strong>() {
        ctx, cancel <strong>:=</strong> context.<strong>WithCancel</strong>(context.<strong>Background</strong>())
        <strong>go</strong> <strong>func</strong>(ctx context.Context) {
                <strong>for</strong> {
                        <strong>select</strong> {
                        <strong>case</strong> <strong>&lt;-</strong>ctx.<strong>Done</strong>():
                                fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;监控退出,停止了...&quot;</span>)
                                <strong>return</strong>
                        <strong>default</strong>:
                                fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;goroutine监控中...&quot;</span>)
                                time.<strong>Sleep</strong>(<span style="color:#009999">2</span> <strong>*</strong> time.Second)
                        }
                }
        }(ctx)

        time.<strong>Sleep</strong>(<span style="color:#009999">10</span> <strong>*</strong> time.Second)
        fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;可以了,通知监控停止&quot;</span>)
        <strong>cancel</strong>()
        <em>//为了检测监控过是否停止,如果没有监控输出,就表示停止了
</em>        time.<strong>Sleep</strong>(<span style="color:#009999">5</span> <strong>*</strong> time.Second)

}
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>重写比较简单,就是把原来的chan&nbsp;stop&nbsp;换成Context,使用Context跟踪goroutine,以便进行控制,比如结束等。</p>

<p>context.Background()&nbsp;返回一个空的Context,这个空的Context一般用于整个Context树的根节点。然后我们使用context.WithCancel(parent)函数,创建一个可取消的子Context,然后当作参数传给goroutine使用,这样就可以使用这个子Context跟踪这个goroutine。</p>

<p>在goroutine中,使用select调用&lt;-ctx.Done()判断是否要结束,如果接受到值的话,就可以返回结束goroutine了;如果接收不到,就会继续进行监控。</p>

<p>那么是如何发送结束指令的呢?这就是示例中的cancel函数啦,它是我们调用context.WithCancel(parent)函数生成子Context的时候返回的,第二个返回值就是这个取消函数,它是CancelFunc类型的。我们调用它就可以发出取消指令,然后我们的监控goroutine就会收到信号,就会返回结束。</p>

<h2>Context控制多个goroutine</h2>

<p>使用Context控制一个goroutine的例子如上,非常简单,下面我们看看控制多个goroutine的例子,其实也比较简单。</p>

<div>
<div>
<table style="background:0px 0px; border-spacing:0px; border:0px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f"> 1
</span><span style="color:#7f7f7f"> 2
</span><span style="color:#7f7f7f"> 3
</span><span style="color:#7f7f7f"> 4
</span><span style="color:#7f7f7f"> 5
</span><span style="color:#7f7f7f"> 6
</span><span style="color:#7f7f7f"> 7
</span><span style="color:#7f7f7f"> 8
</span><span style="color:#7f7f7f"> 9
</span><span style="color:#7f7f7f">10
</span><span style="color:#7f7f7f">11
</span><span style="color:#7f7f7f">12
</span><span style="color:#7f7f7f">13
</span><span style="color:#7f7f7f">14
</span><span style="color:#7f7f7f">15
</span><span style="color:#7f7f7f">16
</span><span style="color:#7f7f7f">17
</span><span style="color:#7f7f7f">18
</span><span style="color:#7f7f7f">19
</span><span style="color:#7f7f7f">20
</span><span style="color:#7f7f7f">21
</span><span style="color:#7f7f7f">22
</span><span style="color:#7f7f7f">23</span><span style="color:#7f7f7f">
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:735.789px">
                        <pre>
<strong>func</strong> <strong>main</strong>() {
        ctx, cancel <strong>:=</strong> context.<strong>WithCancel</strong>(context.<strong>Background</strong>())
        <strong>go</strong> <strong>watch</strong>(ctx,<span style="color:#dd1144">&quot;【监控1】&quot;</span>)
        <strong>go</strong> <strong>watch</strong>(ctx,<span style="color:#dd1144">&quot;【监控2】&quot;</span>)
        <strong>go</strong> <strong>watch</strong>(ctx,<span style="color:#dd1144">&quot;【监控3】&quot;</span>)

        time.<strong>Sleep</strong>(<span style="color:#009999">10</span> <strong>*</strong> time.Second)
        fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;可以了,通知监控停止&quot;</span>)
        <strong>cancel</strong>()
        <em>//为了检测监控过是否停止,如果没有监控输出,就表示停止了
</em>        time.<strong>Sleep</strong>(<span style="color:#009999">5</span> <strong>*</strong> time.Second)
}

<strong>func</strong> <strong>watch</strong>(ctx context.Context, name <strong>string</strong>) {
        <strong>for</strong> {
                <strong>select</strong> {
                <strong>case</strong> <strong>&lt;-</strong>ctx.<strong>Done</strong>():
                        fmt.<strong>Println</strong>(name,<span style="color:#dd1144">&quot;监控退出,停止了...&quot;</span>)
                        <strong>return</strong>
                <strong>default</strong>:
                        fmt.<strong>Println</strong>(name,<span style="color:#dd1144">&quot;goroutine监控中...&quot;</span>)
                        time.<strong>Sleep</strong>(<span style="color:#009999">2</span> <strong>*</strong> time.Second)
                }
        }
}
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>示例中启动了3个监控goroutine进行不断的监控,每一个都使用了Context进行跟踪,当我们使用cancel函数通知取消时,这3个goroutine都会被结束。这就是Context的控制能力,它就像一个控制器一样,按下开关后,所有基于这个Context或者衍生的子Context都会收到通知,这时就可以进行清理操作了,最终释放goroutine,这就优雅的解决了goroutine启动后不可控的问题。</p>

<p>&nbsp;</p>

Anonymous 发表于 2023-2-10 10:21

<h2>Context接口</h2>

<p>Context的接口定义的比较简洁,我们看下这个接口的方法。</p>

<div>
<div>
<table border="1" style="background:0px 0px; border-spacing:0px; border:0px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f">1
</span><span style="color:#7f7f7f">2
</span><span style="color:#7f7f7f">3
</span><span style="color:#7f7f7f">4
</span><span style="color:#7f7f7f">5
</span><span style="color:#7f7f7f">6</span><span style="color:#7f7f7f">
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:743.158px">
                        <pre>
<strong>type</strong> Context <strong>interface</strong> {
        <strong>  Deadline</strong>() (deadline time.Time, ok <strong>bool</strong>)

        <strong>  Done</strong>() <strong>&lt;-</strong><strong>chan</strong> <strong>struct</strong>{}

        <strong>  Err</strong>() <strong>error</strong>

        <strong>  Value</strong>(key <strong>interface</strong>{}) <strong>interface</strong>{}
}
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>这个接口共有4个方法,了解这些方法的意思非常重要,这样我们才可以更好的使用他们。</p>

<p>Deadline方法是获取设置的截止时间的意思,第一个返回式是截止时间,到了这个时间点,Context会自动发起取消请求;第二个返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。</p>

<p>Done方法返回一个只读的chan,类型为struct{},我们在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起了取消请求,我们通过Done方法收到这个信号后,就应该做清理操作,然后退出goroutine,释放资源。</p>

<p>Err方法返回取消的错误原因,因为什么Context被取消。</p>

<p>Value方法获取该Context上绑定的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的。</p>

<p>以上四个方法中常用的就是Done了,如果Context取消的时候,我们就可以得到一个关闭的chan,关闭的chan是可以读取的,所以只要可以读取的时候,就意味着收到Context取消的信号了,以下是这个方法的经典用法。</p>

<div>
<div>
<table style="background:0px 0px; border-spacing:0px; border:0px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f"> 1
</span><span style="color:#7f7f7f"> 2
</span><span style="color:#7f7f7f"> 3
</span><span style="color:#7f7f7f"> 4
</span><span style="color:#7f7f7f"> 5
</span><span style="color:#7f7f7f"> 6
</span><span style="color:#7f7f7f"> 7
</span><span style="color:#7f7f7f"> 8
</span><span style="color:#7f7f7f"> 9
</span><span style="color:#7f7f7f">10
</span><span style="color:#7f7f7f">11
</span><span style="color:#7f7f7f">12
</span><span style="color:#7f7f7f">13
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:735.789px">
                        <pre>
<strong>func</strong> <strong>Stream</strong>(ctx context.Context, out <strong>chan</strong><strong>&lt;-</strong> Value) <strong>error</strong> {
        <strong>  for</strong> {
                v, err <strong>:=</strong> <strong>DoSomething</strong>(ctx)
                <strong>    if</strong> err <strong>!=</strong> <strong>nil</strong> {
                        <strong>     return</strong> err
                }
                <strong>    select</strong> {
                <strong>      case</strong> <strong>&lt;-</strong>ctx.<strong>Done</strong>():
                        <strong>        return</strong> ctx.<strong>Err</strong>()
                <strong>      case</strong> out <strong>&lt;-</strong> v:
                }
        }
}
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>Context接口并不需要我们实现,Go内置已经帮我们实现了2个,我们代码中最开始都是以这两个内置的作为最顶层的partent context,衍生出更多的子Context。</p>

<div>
<div>
<table style="background:0px 0px; border-spacing:0px; border:0px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f"> 1
</span><span style="color:#7f7f7f"> 2
</span><span style="color:#7f7f7f"> 3
</span><span style="color:#7f7f7f"> 4
</span><span style="color:#7f7f7f"> 5
</span><span style="color:#7f7f7f"> 6
</span><span style="color:#7f7f7f"> 7
</span><span style="color:#7f7f7f"> 8
</span><span style="color:#7f7f7f"> 9
</span><span style="color:#7f7f7f">10</span><span style="color:#7f7f7f">
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:735.789px">
                        <pre>
<strong>var</strong> (
        background = <span style="color:#0086b3">new</span>(emptyCtx)
        todo       = <span style="color:#0086b3">new</span>(emptyCtx)
)

<strong>func</strong> <strong> Background</strong>() Context {
        <strong>  return</strong> background
}

<strong>func</strong> <strong> TODO</strong>() Context {
        <strong>  return</strong> todo
}
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>一个是Background,主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Context。</p>

<p>一个是TODO,它目前还不知道具体的使用场景,如果我们不知道该使用什么Context的时候,可以使用这个。</p>

<p>他们两个本质上都是emptyCtx结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的Context。</p>

<div>
<div>
<table style="background:0px 0px; border-spacing:0px; border:0px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f"> 1
</span><span style="color:#7f7f7f"> 2
</span><span style="color:#7f7f7f"> 3
</span><span style="color:#7f7f7f"> 4
</span><span style="color:#7f7f7f"> 5
</span><span style="color:#7f7f7f"> 6
</span><span style="color:#7f7f7f"> 7
</span><span style="color:#7f7f7f"> 8
</span><span style="color:#7f7f7f"> 9
</span><span style="color:#7f7f7f">10
</span><span style="color:#7f7f7f">11
</span><span style="color:#7f7f7f">12
</span><span style="color:#7f7f7f">13</span><span style="color:#7f7f7f">
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:735.789px">
                        <pre>
<strong>type</strong> emptyCtx <strong>int</strong>

<strong>func</strong> (<strong>*</strong>emptyCtx) <strong>Deadline</strong>() (deadline time.Time, ok <strong>bool</strong>) {
        <strong>  return</strong>
}

<strong>func</strong> (<strong>*</strong>emptyCtx) <strong>Done</strong>() <strong>&lt;-</strong><strong>chan</strong> <strong>struct</strong>{} {
        <strong>  return </strong> <strong>nil</strong>
}

<strong>func</strong> (<strong>*</strong>emptyCtx) <strong>Err</strong>() <strong>error</strong> {
        <strong>  return </strong> <strong>nil</strong>
}

<strong>func</strong> (<strong>*</strong>emptyCtx) <strong>Value</strong>(key <strong>interface</strong>{}) <strong>interface</strong>{} {
        <strong> return</strong> <strong>  nil</strong>
}
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>这就是emptyCtx实现Context接口的方法,可以看到,这些方法什么都没做,返回的都是nil或者零值。</p>

<h2>Context的继承衍生</h2>

<p>有了如上的根Context,那么是如何衍生更多的子Context的呢?这就要靠context包为我们提供的With系列的函数了。</p>

<div>
<div>
<table style="background:0px 0px; border-spacing:0px; border:0px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f">1
</span><span style="color:#7f7f7f">2
</span><span style="color:#7f7f7f">3
</span><span style="color:#7f7f7f">4
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:743.158px">
                        <pre>
<strong>func</strong> <strong>WithCancel</strong>(parent Context) (ctx Context, cancel CancelFunc)
<strong>func</strong> <strong>WithDeadline</strong>(parent Context, deadline time.Time) (Context, CancelFunc)
<strong>func</strong> <strong>WithTimeout</strong>(parent Context, timeout time.Duration) (Context, CancelFunc)
<strong>func</strong> <strong>WithValue</strong>(parent Context, key, val <strong>interface</strong>{}) Context
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>这四个With函数,接收的都有一个partent参数,就是父Context,我们要基于这个父Context创建出子Context的意思,这种方式可以理解为子Context对父Context的继承,也可以理解为基于父Context的衍生。</p>

<p>通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个。</p>

<p>WithCancel函数,传递一个父Context作为参数,返回子Context,以及一个取消函数用来取消Context。&nbsp;WithDeadline函数,和WithCancel差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消。</p>

<p>WithTimeout和WithDeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思。</p>

<p>WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到,后面我们会专门讲。</p>

<p>大家可能留意到,前三个函数都返回一个取消函数CancelFunc,这是一个函数类型,它的定义非常简单。</p>

<div>
<div>
<table border="1" style="background:0px 0px; border-spacing:0px; border:0px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f">
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:754.737px">
                        <pre>
<strong>type</strong> CancelFunc <strong>func</strong>()
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>这就是取消函数的类型,该函数可以取消一个Context,以及这个节点Context下所有的所有的Context,不管有多少层级。</p>

<h2>WithValue传递元数据</h2>

<p>通过Context我们也可以传递一些必须的元数据,这些数据会附加在Context上以供使用。</p>

<div>
<div>
<table style="background:0px 0px; border-spacing:0px; border:0px; width:800px">
        <tbody>
                <tr>
                        <td style="border-color:initial; vertical-align:top">
                        <pre>
<span style="color:#7f7f7f"> 1
</span><span style="color:#7f7f7f"> 2
</span><span style="color:#7f7f7f"> 3
</span><span style="color:#7f7f7f"> 4
</span><span style="color:#7f7f7f"> 5
</span><span style="color:#7f7f7f"> 6
</span><span style="color:#7f7f7f"> 7
</span><span style="color:#7f7f7f"> 8
</span><span style="color:#7f7f7f"> 9
</span><span style="color:#7f7f7f">10
</span><span style="color:#7f7f7f">11
</span><span style="color:#7f7f7f">12
</span><span style="color:#7f7f7f">13
</span><span style="color:#7f7f7f">14
</span><span style="color:#7f7f7f">15
</span><span style="color:#7f7f7f">16
</span><span style="color:#7f7f7f">17
</span><span style="color:#7f7f7f">18
</span><span style="color:#7f7f7f">19
</span><span style="color:#7f7f7f">20
</span><span style="color:#7f7f7f">21
</span><span style="color:#7f7f7f">22
</span><span style="color:#7f7f7f">23
</span><span style="color:#7f7f7f">24
</span><span style="color:#7f7f7f">25
</span><span style="color:#7f7f7f">26</span><span style="color:#7f7f7f">
</span></pre>
                        </td>
                        <td style="border-color:initial; vertical-align:top; width:735.789px">
                        <pre>
<strong>var</strong> key <strong>string</strong>=<span style="color:#dd1144">&quot;name&quot;</span>

<strong>func</strong> <strong>main</strong>() {
        ctx, cancel <strong>:=</strong> context.<strong>WithCancel</strong>(context.<strong>Background</strong>())
        <em>  //附加值
</em>        valueCtx<strong>:=</strong>context.<strong>WithValue</strong>(ctx,key,<span style="color:#dd1144">&quot;【监控1】&quot;</span>)
        <strong>  go</strong> <strong> watch</strong>(valueCtx)
        time.<strong>Sleep</strong>(<span style="color:#009999">10</span> <strong>*</strong> time.Second)
        fmt.<strong>Println</strong>(<span style="color:#dd1144">&quot;可以了,通知监控停止&quot;</span>)
        <strong>  cancel</strong>()
        <em>  //为了检测监控过是否停止,如果没有监控输出,就表示停止了
</em>        time.<strong>Sleep</strong>(<span style="color:#009999">5</span> <strong>*</strong> time.Second)
}

<strong>func</strong> <strong> watch</strong>(ctx context.Context) {
        <strong> for</strong> {
                <strong>  select</strong> {
                <strong>    case </strong> <strong>&lt;-</strong>ctx.<strong>Done</strong>():
                        <em>      //取出值
</em>                        fmt.<strong>Println</strong>(ctx.<strong>Value</strong>(key),<span style="color:#dd1144">&quot;监控退出,停止了...&quot;</span>)
                        <strong>      return</strong>
                <strong>    default</strong>:
                        <em>      //取出值
</em>                        fmt.<strong>Println</strong>(ctx.<strong>Value</strong>(key),<span style="color:#dd1144">&quot;goroutine监控中...&quot;</span>)
                        time.<strong>Sleep</strong>(<span style="color:#009999">2</span> <strong>*</strong> time.Second)
                }
        }
}
</pre>
                        </td>
                </tr>
        </tbody>
</table>
</div>
</div>

<p>在前面的例子,我们通过传递参数的方式,把name的值传递给监控函数。在这个例子里,我们实现一样的效果,但是通过的是Context的Value的方式。</p>

<p>我们可以使用context.WithValue方法附加一对K-V的键值对,这里Key必须是等价性的,也就是具有可比性;Value值要是线程安全的。</p>

<p>这样我们就生成了一个新的Context,这个新的Context带有这个键值对,在使用的时候,可以通过Value方法读取ctx.Value(key)。</p>

<p>记住,使用WithValue传值,一般是必须的值,不要什么值都传递。</p>

<h2>Context 使用原则</h2>

<ol>
        <li>不要把Context放在结构体中,要以参数的方式传递</li>
        <li>以Context作为参数的函数方法,应该把Context作为第一个参数,放在第一位。</li>
        <li>给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用context.TODO</li>
        <li>Context的Value相关方法应该传递必须的数据,不要什么数据都使用这个传递</li>
        <li>Context是线程安全的,可以放心的在多个goroutine中传递</li>
</ol>

<div>&nbsp;</div>

<div>&nbsp;</div>

<div><span style="font-family:iowan old style,ovo,hoefler text,Georgia,times new roman,tibch,source han sans,pingfangsc-regular,hiragino sans gb,stheiti,microsoft yahei,droid sans fallback,wenquanyi micro hei,sans-serif"><span style="font-size:15px">原文地址:https://www.flysnow.org/2017/05/12/go-in-action-go-context</span></span></div>

<p>&nbsp;</p>
页: [1]
查看完整版本: Go语言实战笔记(二十)| Go Context