关键词:
context 使用介绍
主要功能:
- 控制超时时间
- 保存上下文数据
使用 context 处理超时
基本语法结构
import ""context""
// 生成和释放定时器
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
// 超时控制
select
case <- ctx.Done():
// 超时时要执行的代码
default:
// 其他情况执行的代码
示例-访问网站超时
这里用了底层的request来发送GET请求。&http.Client 的结构体里本身也有 Timeout 的设置,默认的0值就是不设置超时。并且Clinet要求它的 Transport 必须实现 CancelRequest 方法,默认的 Transport 是有这个方法的。所以下面的示例就是把底层的逻辑模拟了一遍,超时后手动调用 Transport 的 CancelRequest 方法:
package main
import (
"context"
"fmt"
"os"
"io/ioutil"
"net/http"
"time"
)
type Result struct
r *http.Response
err error
func process(host string)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // cancel是一个函数,执行后取消上面生成的定时器
c := make(chan Result)
tr := &http.Transport
client := &http.ClientTransport: tr
req, err := http.NewRequest("GET", "http://" + host, nil)
if err != nil
fmt.Fprintf(os.Stderr, "HTTP GET ERROR: %v
", err)
return
go func()
resp, err := client.Do(req)
c <- Resultr: resp, err: err
()
select
case <- ctx.Done():
tr.CancelRequest(req) // 取消请求
res := <- c
fmt.Println("Timeout...", res.err)
case res := <- c:
defer res.r.Body.Close()
out, _ := ioutil.ReadAll(res.r.Body) // 第二个参数是err,这里忽略错误
fmt.Printf("Server Response:
%s
", out)
return
func main()
host := os.Args[1]
process(host)
命令行接收第一个参数作为请求的服务器地址,执行结果:
PS H:Gosrcgo_devday12context> go run main.go google.com
Timeout... Get http://google.com: net/http: request canceled while waiting for connection
PS H:Gosrcgo_devday12context> go run main.go baidu.com
Server Response:
<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>
PS H:Gosrcgo_devday12context>
一次请求超时,一次有返回结果。
使用 context 保存上下文
使用 context 还可以做一些自定的参数传递。以key-value的形式存储到 context 的变量中,用的时候再取出来。
下面的例子试了 context 来传递变量:
package main
import (
"fmt"
"context"
)
func process(ctx context.Context)
age, ok := ctx.Value("age").(int) // 取出的值在使用之前做一下类型断言是比较好的做法
if !ok
age = 18 // 如果有错误,就默认设置成18
fmt.Println("Age:", age)
name, _ := ctx.Value("name").(string) // 忽略错误
fmt.Println("Name:", name)
fmt.Println(ctx.Value("gender")) // ctx不包括也不需要类型断言,但是使用前做一下类型断言比较好
fmt.Println(ctx.Value("gender1")) // 如果没有对应的key,就返回nil
func main()
ctx := context.WithValue(context.Background(), "name", "Adam") // 存的是键值对,后2个参数分别是key和value
ctx = context.WithValue(ctx, "age", 23) // 追加值,就使用之前的ctx,这样所有的值都有了
ctx = context.WithValue(ctx, "gender", "Male")
process(ctx) // 调用函数,把ctx传进去,函数里可以取出相应的值
这里只是演示用法,例子里的这类明确的变量,还是应该以传统的方式来传递的。那些全局需要用到的变量,可以使用 context 来进行维护。因为用了 context 之后,就把变量的信息给隐藏了,代码的可读性会变差。而且隐藏了变量的类型,也不符合go的习惯,所以上面在用之前,都在了类型断言。
使用 context 结束 goroutine
通过 context.WithCancel 方法,还可以控制goroutine的生命周期:
// 定义结束控制
ctx, cancel:= context.WithCancel(context.Background())
defer cancel()
// 执行cancel()后,ctx.Done()这个管道里就能取到值
go func()
// 下面是一个无限循环,直到context返回,否则就一直循环下去
for
select
case <-ctx.Done():
return // 从管道里取到值,就退出
default:
// 其他情况执行的代码
这里定义了2个变量,ctx和cancel。cancel是一个可以调用的函数,调用执行后。ctx.Done这个管道就能取出一个值了。在goroutine里就可以通过这个管道来控制退出goroutine。完整示例如下:
package main
import (
"time"
"fmt"
"context"
)
func test()
// 这是一个匿名函数的闭包,下面会调用
gen := func(ctx context.Context) <-chan int
dst := make(chan int)
n := 1
go func()
for
select
case <-ctx.Done():
fmt.Println("goroutine 结束")
return
case dst <- n:
n++
()
return dst
ctx, cancel:= context.WithCancel(context.Background())
defer cancel()
for n:= range gen(ctx)
fmt.Println(n)
if n == 5
break
func main()
test()
time.Sleep(time.Second * 3)
fmt.Println("main 结束")
上面这个示例的应用场景就是:当你要启用一个goroutine执行任务,并且还需要通知这个goroutine结束的时候,就可以通过这里的 context 来实现。
DeadLine超时
WithDeadline 和 WithTimeout 是相似的。都是通过设置,会在某个时间自动触发,就是ctx.Done()能够取到值。差别是,DeadLine是设置一个时间点,时间对上了就到期。Timeout是设置一段时间,比如几秒,过个这段时间,就超时。其实底层的Timeout也是通过Deadlin实现的,在Timeout里,直接 return WithDeadline(parent, time.Now().Add(timeout))。
下面的例子设置了50毫秒超时,通过WithDeadline设置。运行程序接收一个命令行参数,传入一个整数,进过这段毫秒的时间后,会输出自定义的内容。如果数字大了(大于50),就会超时,这里会输出context的Err方法返回的信息:
package main
import (
"context"
"fmt"
"time"
"os"
"strconv"
)
func main()
n, _ := strconv.Atoi(os.Args[1]) // 把第一个参数转成整数,忽略错误
d := time.Now().Add(50 * time.Millisecond) // 50毫秒后过期
ctx, cancel := context.WithDeadline(context.Background(), d) // 如果是Timeout,就只直接传上面Add里的部分
defer cancel()
select
case <- time.After(time.Millisecond * time.Duration(n)): // 第一个参数小于50,进入这个分支。参数不是数字就当做0
fmt.Println("时间到了")
case <- ctx.Done():
fmt.Println(ctx.Err())
/* 执行结果
PS H:Gosrcgo_devday12contextdeadline> go run main.go 123
context deadline exceeded
PS H:Gosrcgo_devday12contextdeadline> go run main.go 12
时间到了
PS H:Gosrcgo_devday12contextdeadline>
*/
这个例子还是使用Timeout更方便,不过这里主要演示Deadline的用法。两个方法的效果一样,根据实际请求选择合适的方法。TImeout应该更好用,所以才会对Deadine再封装一层,提供一个Timeout方法来给更多的应用场景使用。
sync.WaitGroup 介绍
之前都是通过管道来和goroutine传递数据的,也能通过管道实现等待。不过如果只是等待goroutine执行完毕,现在还有个方法可以实现。
通过使用 sync.WaitGroup ,可以方便的等待一组goroutine结束,具体就是下面的3步:
- 使用Add方法设置等待的数量,计数加1
- 使用Done方法设置等待数量,计数减1
- 当等待数量等于0时,Wait方法返回
示例1
下面是一个发 http 请求的示例,等待所有请求返回后,才会退出主函数:
package main
import (
"os"
"sync"
"fmt"
"net/http"
)
var wg sync.WaitGroup
func main()
var urls = []string
"baidu.com",
"51cto.com",
"go-zh.org",
for _, url := range urls
wg.Add(1) // 每开一个goroutine,计数加1
go func(url string)
defer wg.Done() // 退出时计数减1
resp, err := http.Head("http://" + url)
if err != nil
fmt.Fprintf(os.Stderr, "%s Head ERROR: %v", url, err)
return
fmt.Println(*resp)
(url)
wg.Wait() // 在这里等待,所有任务完成,才继续
fmt.Println("All Requests Down")
示例2
下面的例子,展示另外一种风格的写法:
package main
import (
"fmt"
"sync"
"time"
)
func calc(w *sync.WaitGroup ,i int)
defer w.Done()
fmt.Println("calc:", i)
time.Sleep(time.Second)
func main()
// 另一种定义wg的方法,函数里一般用短变量声明
// 这次不是全局变量了,下面还要传参
wg := sync.WaitGroup
wg.Add(10) // 一次加10,不在for循环里每次加1了
for i := 0; i < 10; i++
go calc(&wg, i) // 结构体是值类型,用地址来传参
wg.Wait()
fmt.Println("All goroutine Done")
小结
注意:Add方法不能放在goroutine里面。看似没问题,不过有可能还没等goroutine运行起来,主函数就运行到Wait了。效果就是计数还没开始也就是0,主函数就可以继续执行下去了。是主函数先执行Wait还是goroutine先执行到Add就看运气了
相比管道用起来更方便也更好理解一些,而且可以等待一组goroutine。这个方法只能实现主函数等待goroutine执行结束。如果需要通知某个goroutine退出,还是要用管道来实现。管道可以用来交互数据,所以所有的情况都适用。而 sync.WaitGroup 场景比较单一,但是更好用。
go快速入门(代码片段)
入门Go语言需要多久?答案是——读完这篇文章的时间!不妨找一个周末的下午,踏上Go之旅吧!更新记录:2016.12.12:完成重制2016.11.02:增加重点理解和参考链接2016.08.11:完成初稿任务目标了解Go的设计哲学和与其他面向对象语言在... 查看详情
go语言学习笔记—基础—函数(12):防止程序崩溃——宕机恢复(recover)(代码片段)
...er实现错误捕捉和恢复,使代码在崩溃后继续运行。go语言没有异常系统,使用panic触发宕机类似其他语言的抛出异常,那么recover宕 查看详情
2023-03-12:mp3音频解码为pcm,代码用go语言编写,调用moonfdd/ffmpeg-go库。(代码片段)
2023-03-12:mp3音频解码为pcm,代码用go语言编写,调用moonfdd/ffmpeg-go库。答案2023-03-12:用github/moonfdd/ffmpeg-go库。命令如下:gorun./examples/a15.audio_decode_mp32pcm/main.go代码参考了15:mp3音频解码为pcm,代码用golang编写... 查看详情
go语言学习笔记-2(代码片段)
1.变量go不支持任何类型的隐式转换,必须使用显示转换,否则将会编译错误;go支持指针,但不支持指针运算。2、数组packagearrayimport"testing"funcTestArrayInit(t*testing.T) vara[3]int//未赋值默认为0 b:=[4]int1,2,2,1 c:... 查看详情
go语言学习笔记-2(代码片段)
1.变量go不支持任何类型的隐式转换,必须使用显示转换,否则将会编译错误;go支持指针,但不支持指针运算。2、数组packagearrayimport"testing"funcTestArrayInit(t*testing.T) vara[3]int//未赋值默认为0 b:=[4]int1,2,2,1 c:... 查看详情
go语言之通道(代码片段)
1packagemain23import(4"fmt"5)67funcmain()8//channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。9//声明通道类型10varachanint//声明一个int类型的通道,声明之后需要对它进行初始化11fmt.Println(a)12ch:=make(chanint,10)//进行初始化有... 查看详情
go语言fmt包详解(代码片段)
格式化输出函数 fmt包含有格式化I/O函数,类似于C语言的printf和scanf。格式字符串的规则来源于C,但更简单一些1.print和println方法print输出给定的字符串,如果是数值或字符,则输出对应的十进制表示fmt.Print("a","")//输出afmt.Prin... 查看详情
肝了10万字,go语言保姆级编程教程2021最新版(建议收藏)(代码片段)
...,收藏,一键三连支持,再学习。本文对比C语言进行学习Go语言,如果你有C语言基础,学习Go语言会容易很多。Go语言保姆级教程目录什么是Go语言Go语言优势Go语言发展史Go作者Go语言现状Go语言应用场景如何学... 查看详情
2022-12-29:nsq是go语言写的消息队列。请问k3s部署nsq,yaml如何写?(代码片段)
2022-12-29:nsq是go语言写的消息队列。请问k3s部署nsq,yaml如何写?答案2022-12-29:yaml如下:apiVersion:apps/v1kind:Deploymentmetadata:labels:app:nsqname:nsqnamespace:moonfddspec:replicas:1sele 查看详情
12.go语言标准库之fmt(代码片段)
1.fmtfmt包实现了类似C语言printf和scanf的格式化I/O。主要分为向外输出内容和获取输入内容两大部分。1.1向外输出1.1.1PrintPrint系列函数会将内容输出到系统的标准输出,区别在于Print函数直接输出内容,Printf函数支持格式化输出字... 查看详情
go语言字符串和正则表达式(代码片段)
字符串相关方法获取字符串长度注意:Go语言编码方式是UTF-8,在UTF-8中一个汉字占3个字节packagemainimport"fmt"funcmain() str1:="lnj" fmt.Println(len(str1))//3 str2:="lnj李南江" fmt.Println(len(str2))//12如果字符串中 查看详情
2022-09-28:以下go语言代码输出什么?a:11;b:12;c:22;d:不确定。packagemainimport(“fmt“)funcmain()var(代码片段)
2022-09-28:以下go语言代码输出什么?A:11;B:12;C:22;D:不确定。packagemainimport( "fmt")funcmain() varxint inc:=func()int x++ returnx 查看详情
2022-09-12:以下go语言代码输出什么?a:true;b:false;c:无法编译;d:运行时panic。packagemainfuncmain()varxchan<-(代码片段)
2022-09-12:以下go语言代码输出什么?A:true;B:false;C:无法编译;D:运行时panic。packagemainfuncmain() varxchan<-chanerror varychan(<-chanerror) println(x& 查看详情
2022-12-31:以下go语言代码输出什么?a:11;b:-11;c:-1-1;d:编译错误。packagemainimport“fmt“funcmain()a(代码片段)
2022-12-31:以下go语言代码输出什么?A:11;B:-11;C:-1-1;D:编译错误。packagemainimport"fmt"funcmain()a:=(-3)%2b:=(-3)%(-2)fmt.Println(a,b)答案选 查看详情
19.go语言基础学习(上)——2019年12月16日(代码片段)
...16:57:045.接口2019年11月01日15:56:095.1ducktyping1.2.接口3.介绍Go语言的接口设计是非侵入式的,接口编写者无须知道接口被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口,但无须指明实现哪一个接口。编译器知道最终... 查看详情
2022-08-25:以下go语言代码输出什么?a:10;b:12;c:不能编译;d:00。packagemainimport“fmt“funcnamed()(n,_int(代码片段)
2022-08-25:以下go语言代码输出什么?A:10;B:12;C:不能编译;D:00。packagemainimport"fmt"funcnamed()(n,_int)return1,2funcmain()fmt.Print(named())答案202 查看详情
2022-07-12:以下go语言代码输出什么?a:11;b:1.01.0;c:编译不通过;d:1.01。packagemainimport“fmt“funcmain()(代码片段)
2022-07-12:以下go语言代码输出什么?A:11;B:1.01.0;C:编译不通过;D:1.01。packagemainimport"fmt"funcmain()fmt.Println(1%2.0)fmt.Println(int(1)%2.0)答案2 查看详情
go语言学习——go语言介绍安装运行(代码片段)
go语言介绍什么是go语言?Google开源编译型语言21世纪的C语言解释型语言与编译型语言的区别go语言的特点语法简洁开发效率高执行性能好go语言真的很小众吗Go语言真的没人用吗GopherChina2019盛况百度、腾讯、知乎(使用Go语言重构... 查看详情