学习golang绕不开并发,本身提供的goroutine搭配上chanel十分好用,但是总是有一些特殊情况,如果要再一定时间内返回结果而goroutine又没有像c的线程一样提供主动结束子进程的方法。
之前自己遇到这种情况的时候也找了一些资料,就用标准库自己写了一个超时,大致是这样的
for i := 0; i < len(data.Data); i++ {
select {
case data := <-c:
/* 处理数据*/
case <-time.After(200 * time.Millisecond):
/* 已超时 返回 */
break
}
}
自己用用是够了,需要知道自己创建了多少个goroutines,不然会造成不必要的延迟。
然后最近看了gogstash的源码。。。(看完后觉得自己写的真烂
里面有一些常用的第三方库,context就是其中一个,好像还是谷歌自己内部人员写的。。
有了这个写超时就简单很多了
示例看下面的代码吧。
package main
import (
"fmt"
"time"
"golang.org/x/net/context"
)
// 模拟一个最小执行时间的阻塞函数
func inc(a int) int {
res := a + 1 // 虽然我只做了一次简单的 +1 的运算,
time.Sleep(1 * time.Second) //强行超时
return res
}
// 如果计算被中断, 则返回 -1
func Add(ctx context.Context, a, b int) int {
res := 0
for i := 0; i < a; i++ {
res = inc(res)
select {
case <-ctx.Done():
return -1
default:
// 没有结束 ... 执行 ...
}
}
for i := 0; i < b; i++ {
res = inc(res)
select {
case <-ctx.Done():
return -1
default:
// 没有结束 ... 执行 ...
}
}
return res
}
func main() {
{
// 使用开放的 API 计算 a+b
a := 1
b := 2
timeout := 2 * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
res := Add(ctx, 1, 2)
fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)
}
{
// 手动取消
a := 1
b := 2
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancel() // 在调用处主动取消
}()
res := Add(ctx, 1, 2)
fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)
}
}
(网上找的)
用起来是很简单的,创建一个context,和参数一起传入,超时之后ctx.Done()就会返回一个信号,然后就可以把这个goroutine终止掉了。
在gogstash里作者还这个和信号量绑定在一起,用这个函数
func contextWithOSSignal(parent context.Context, logger logutil.LevelLogger, sig ...os.Signal) context.Context {
osSignalChan := make(chan os.Signal, 1)
signal.Notify(osSignalChan, sig...)
ctx, cancel := context.WithCancel(parent)
go func(cancel context.CancelFunc) {
select {
case sig := <-osSignalChan:
logger.Info(sig)
cancel()
}
}(cancel)
return ctx
}
当程序被杀死时能安全的退出。