當然,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()
}
