机制1:select+time.After
通过select+time.After方法实现超时机制,示例代码如下:
package main
import (
"context"
"fmt"
"runtime"
"time"
)
func main() {
timeout1()
println("Goroutine数量:",runtime.NumGoroutine())
time.Sleep(time.Second*10)
println("Goroutine数量:",runtime.NumGoroutine())
}
func timeout1() {
//通过select+time.After方法实现超时机制
ch := make(chan struct{}, 1)
go func() {
//具体业务逻辑
time.Sleep(time.Second*3)
ch <- struct{}{}
}()
select {
case <-ch:
fmt.Println("业务逻辑执行完成!")
case <-time.After(time.Second * 2):
fmt.Println("超时...")
}
return
}
运行效果
分析:上述代码,超时时间到,执行case <-time.After(time.Second * 2),timeout1()函数退出。而
Goroutine需要等到时间到了,执行ch <- struct{}{}之后才能结束。因此在main中输出的Goroutine分别为2(包括一个主线程)、1。
特别注意:如果将
ch := make(chan struct{}, 1)
改为:
ch := make(chan struct{})
若发生超时,由于timeout1()函数退出已经退出,Goroutine中的ch将一直无法写入数据,因此该Goroutine将一直无法退出(内存泄露,有的时候也说成Goroutine泄露)。
上述代码中写入channel的数据为:struct{},主要作用是为了节省内存。
func main() {
testSize()
}
func testSize() {
a := 1
b := true
c := "1"
d := struct{}{}
sizeofa := unsafe.Sizeof(a)
sizeofb := unsafe.Sizeof(b)
sizeofc := unsafe.Sizeof(c)
sizeofd := unsafe.Sizeof(d)
fmt.Println(sizeofa, sizeofb, sizeofc, sizeofd)
}
即:struct{} 大小为0.
机制2:select+context
通过select+context方法实现超时机制,示例代码如下:
package main
import (
"context"
"fmt"
"runtime"
"time"
)
func main() {
timeout2(context.Background())
println("Goroutine数量:",runtime.NumGoroutine())
time.Sleep(time.Second*10)
println("Goroutine数量:",runtime.NumGoroutine())
}
func timeout2(ctx context.Context) {
c, cancelFunc := context.WithTimeout(ctx, time.Second*5)
go func() {
//具体业务逻辑
time.Sleep(time.Second*6)
defer cancelFunc() //业务逻辑执行完之后,直接调用context结束
}()
select {
//如果时间到,变量c(类型为:context)会自动调用。如果业务执行完成,cancelFunc()也会调用
case <-c.Done():
fmt.Println("执行结束")
}
return
}
运行效果
代码分析:与机制1的相似,业务超时完成时,timeout2已经退出,此时该Goroutine才退出。