机制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才退出。