首页IT科技golang并发编程实战(《Go 语言并发之道》读书笔记(六))

golang并发编程实战(《Go 语言并发之道》读书笔记(六))

时间2025-09-19 15:00:59分类IT科技浏览6034
导读:今天这篇笔记我们来学习一下context包...

今天这篇笔记我们来学习一下context包

context包的一个应用场景是可以通过它控制goroutine的取消                ,超时等                。

我们先来看一个取消的例子

context.WithCancel

func doSomething(ctx context.Context) { ctx, cancelctx := context.WithCancel(ctx) printCh := make(chan int) go doAnother(ctx, printCh) for i := 0; i < 3; i++ { printCh <- i } cancelctx() fmt.Println("do something finished") } func doAnother(ctx context.Context, printCh chan int) { for { select { case <-ctx.Done(): if err := ctx.Err(); err != nil { fmt.Printf("do Another with error, %s \n", err) } fmt.Println("do Another finished") return case data := <-printCh: fmt.Printf("receive %d \n", data) } } }

我们在doSomething方法中加了一个可以取消的Context                        , 然后定义了一个channel, 往channel里面放入3个数        ,另外启动一个goroutine doAnother 来接收它, 在完成放入数据后我们调用了取消方法            , 通知doAnother 结束掉                        。 这样程序就达到一个goroutine通知另外一个goroutine的效果                        , 程序运行结果如下

receive 0 receive 1 receive 2 do Another with error, context canceled do Another finished do something finished

通过打印的输出我们可以看到doAnother是通过canneled来结束的        。

如果我们再加一个goroutine            ,也能够通过context来取消            。 这里比较简单不贴代码了                        。

context.WithTimeout

如果只是单纯的通知另外一个goroutine        ,直接通过close channel也可以做到了                        , Context还可以通过WithTimeout设定超时时间来限定程序运行多久                ,我们简单改造一下上面的代码

func doSomething(ctx context.Context) { ctx, cancelctx := context.WithTimeout(ctx, 1500*time.Millisecond) defer cancelctx() printCh := make(chan int) go doAnother(ctx, printCh) outer: for i := 0; i < 3; i++ { select { case printCh <- i: time.Sleep(1 * time.Second) case <-ctx.Done(): break outer } } cancelctx() fmt.Println("do something finished")

我们把doSomething改造了一下    , 通过context.WithTimeout(ctx, 1500*time.Millisecond)将context 改成运行1.5秒就退出, doAnother 保持不变                        , 然后发送的时候                    ,发送一个就sleep 1秒            。 运行效果如下

receive 0 receive 1 do Another with error, context deadline exceeded do Another finished do something finished

我们可以看到只发送了两个数,就退出了                    ,退出的原因是exceeded. 这样就达到了控制goroutine运行时间的效果        。

通过context.WithDeadline 也可以达到和WithTimeout一样的效果                        ,只是一个是按时间点来停    ,一个是按时间段来停                , 示例代码如下 deadLineTime := time.Now().Add(1500 * time.Millisecond) ctx, cancelctx := context.WithDeadline(ctx, deadLineTime)

context.WithValue

context还有个用途是用来记录和传递value, 比如我们可以向别的goroutine传递UserID                        ,RequestID等要跟踪的变量信息                        。 还是通过示例代码来说明

func main() { var ctx = context.Background() key := 1 ctx = context.WithValue(ctx, key, "someValue") doSomething(ctx) } func doSomething(ctx context.Context) { fmt.Printf("Doing something! %s \n", ctx.Value(1)) }

输出结果很简单就是"Doing something! someValue" 这个someValue就是通过Context传递给doSomething方法里面的                。 如果ctx.Value的key不存在        ,也不会报错            ,会返回nil.

上面key的写法                        ,Visual Studio Code会提示                ”should not use built-in type int as key for value; define your own type to avoid collisions“

就是说这个key可能冲突            ,比如你的程序用String "userID", 别人也用String                         ”userID“, 你取的时候取到别人用的了        ,那样就给程序带来隐患                        , 我们需要自定义key, 如下代码示例 type ctxKey int const ( ctxUserKey ctxKey = iota ctxAuthToken ) func main() { var ctx = context.Background() ctx = context.WithValue(ctx, ctxUserKey, "someValue") doSomething(ctx) } func doSomething(ctx context.Context) { fmt.Printf("Doing something! %s \n", ctx.Value(ctxUserKey)) }

我们定义了常量ctxUserKey                , 它是ctxKey类型    ,ctxUserKey它的实际值是0                        , 在取值和赋值的时候我们同意用ctxUserKey                    , 这样就能很清楚明白的拿到它的value. 如果我们用ctx.Value(0)是拿不到值的    。

最后

作者在书中说context包的包一直存在争议,为什么呢? 因为它可以放入任意类型                    ,这样就让有些偷懒的程序员把它当垃圾桶                        , 程序运行需要的参数也用它来传递    ,这样它就被赋予了太多它不应该的功能                        。 那么我们需要遵守一定的规则来使用它                ,作者给出了建议

1, 数据应该通过进程或API边界                        , 这句话感觉是翻译问题        ,不是特别清楚            ,我感觉作者的意思是通过明确的参数来传递数据                        ,而不是context

2            ,数据应该是不可变的                    。 意思就是只WithValue一次        ,不要去修改它

3                        ,数据应该趋向简单类型                , 保持简单很重要

4    ,数据应该是数据                        ,而不是类型与方法                    , 也是保持简单

5,数据应该用于装饰操作                    ,而不是驱动操作。意思就是你不要通过context里面的内容来决定你的代码逻辑                    。

作者最后说context提供的取消功能非常有用                        , 就是WithValue不要滥用                        。这一章感觉翻译的不好    ,读起来很晦涩                ,我还是看了另外一篇文章搞明白的    。 贴出链接在此https://www.digitalocean.com/community/tutorials/how-to-use-contexts-in-go

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
Pythonmap的用法(python中ChainMap是什么)