go超時控制有4種寫法,你知道嗎?


當然,go語言的超時控制肯定不止4種方法,起這個標題是我的一種自嘲,讓我想起了孔乙己說的茴香的茴有4種寫法。

本文寫的4種方程都借助於同一個套路:

workDoneCh := make(chan struct{}, 1)
go func() { 
	LongTimeWork()	//這是我們要控制超時的函數
	workDoneCh <- struct{}{}
}()

select { //下面的case只執行最早到來的那一個
case <-workDone:	//LongTimeWork運行結束
	fmt.Println("LongTimeWork return")
case <-timeoutCh:	//timeout到來
	fmt.Println("LongTimeWork timeout")
}

   比如我們希望100ms超時,那么100ms之后<-timeoutCh這個讀管道的操作需要解除阻塞,而解除阻塞有2種方式,要么有人往管道里寫入了數據,要么管道被close了。下面的4種方法就圍繞<-timeoutCh如何解除阻塞展開。

式一:

這種方式最簡單直接

timeoutCh := make(chan struct{}, 1)
go func() { 
	time.Sleep(100 * time.Millisecond)
	timeoutCh <- struct{}{}
}()

式二:

不需要像方式一那樣顯式地創建一個timeoutCh管道,借助於time.After(duration),這個函數會返回一個管道,並且經過一段時間duration后它還會自動向管道send一個數據。

select { //下面的case只執行最早到來的那一個
case <-workDone:	//LongTimeWork運行結束
	fmt.Println("LongTimeWork return")
case <-time.After(100 * time.Millisecond):	//timeout到來
	fmt.Println("LongTimeWork timeout")
}

     比式一優雅簡潔了不少。 

式三:

go語言Context是一個接口,它的Done()成員方法返回一個管道。

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Value(key interface{}) interface{}
}

  cancelCtx是Context的一個具體實現,當調用它的cancle()函數時,會關閉Done()這個管道,<-Done()會解除阻塞。

ctx, cancel := context.WithCancel(context.Background())
go func() { 
	time.Sleep(100 * time.Millisecond)
	cancel()
}()
select { //下面的case只執行最早到來的那一個
case <-workDone:
	fmt.Println("LongTimeWork return")
case <-ctx.Done(): //ctx.Done()是一個管道,調用了cancel()都會關閉這個管道,然后讀操作就會立即返回
	fmt.Println("LongTimeWork timeout")
}

式四:

跟式三類似,timerCtx也是Context的一個具體實現,當調用它的cancle()函數或者到達指定的超時時間后,都會關閉Done()這個管道,<-Done()會解除阻塞。

ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*100)
select { //下面的case只執行最早到來的那一個
case <-workDone:
	fmt.Println("LongTimeWork return")
case <-ctx.Done(): //ctx.Done()是一個管道,context超時或者調用了cancel()都會關閉這個管道,然后讀操作就會立即返回
	fmt.Println("LongTimeWork timeout")
}

  總體來看,式二和式四的代碼量是最少的。最后附上完整代碼:

package main

import (
	"context"
	"fmt"
	"time"
)

const (
	WorkUseTime = 500 * time.Millisecond
	Timeout     = 100 * time.Millisecond
)

//模擬一個耗時較長的任務
func LongTimeWork() {
	time.Sleep(WorkUseTime)
	return
}

//模擬一個接口處理函數
func Handle1() {
	deadline := make(chan struct{}, 1)
	workDone := make(chan struct{}, 1)
	go func() { //把要控制超時的函數放到一個協程里
		LongTimeWork()
		workDone <- struct{}{}
	}()
	go func() { //把要控制超時的函數放到一個協程里
		time.Sleep(Timeout)
		deadline <- struct{}{}
	}()
	select { //下面的case只執行最早到來的那一個
	case <-workDone:
		fmt.Println("LongTimeWork return")
	case <-deadline:
		fmt.Println("LongTimeWork timeout")
	}
}

//模擬一個接口處理函數
func Handle2() {
	workDone := make(chan struct{}, 1)
	go func() { //把要控制超時的函數放到一個協程里
		LongTimeWork()
		workDone <- struct{}{}
	}()
	select { //下面的case只執行最早到來的那一個
	case <-workDone:
		fmt.Println("LongTimeWork return")
	case <-time.After(Timeout):
		fmt.Println("LongTimeWork timeout")
	}
}

//模擬一個接口處理函數
func Handle3() {
	//通過顯式sleep再調用cancle()來實現對函數的超時控制
	ctx, cancel := context.WithCancel(context.Background())

	workDone := make(chan struct{}, 1)
	go func() { //把要控制超時的函數放到一個協程里
		LongTimeWork()
		workDone <- struct{}{}
	}()

	go func() {
		//100毫秒后調用cancel(),關閉ctx.Done()
		time.Sleep(Timeout)
		cancel()
	}()

	select { //下面的case只執行最早到來的那一個
	case <-workDone:
		fmt.Println("LongTimeWork return")
	case <-ctx.Done(): //ctx.Done()是一個管道,調用了cancel()都會關閉這個管道,然后讀操作就會立即返回
		fmt.Println("LongTimeWork timeout")
	}
}

//模擬一個接口處理函數
func Handle4() {
	//借助於帶超時的context來實現對函數的超時控制
	ctx, cancel := context.WithTimeout(context.Background(), Timeout)
	defer cancel() //純粹出於良好習慣,函數退出前調用cancel()
	workDone := make(chan struct{}, 1)
	go func() { //把要控制超時的函數放到一個協程里
		LongTimeWork()
		workDone <- struct{}{}
	}()
	select { //下面的case只執行最早到來的那一個
	case <-workDone:
		fmt.Println("LongTimeWork return")
	case <-ctx.Done(): //ctx.Done()是一個管道,context超時或者調用了cancel()都會關閉這個管道,然后讀操作就會立即返回
		fmt.Println("LongTimeWork timeout")
	}
}

func main() {
	Handle1()
	Handle2()
	Handle3()
	Handle4()
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM