1、使用質數定義計算 #version1import datetime #導入模塊計算效率start = datetime.datetime.now() count = 0 for x in range(2,100000): #求指定範圍內的質數 for i in range(2,x): #除以1和 ...
今天這篇筆記我們來學習一下context包
context包的一個應用場景是可以通過它控制goroutine的取消,超時等。
我們先來看一個取消的例子
context.WithCancel
func doSomething(ctx context.Context) {
ctx, cancelctx := context.WithCancel(ctx)
printCh := make(chan int)
go doAnother(ctx, printCh)
for i := 0; i < 3; i++ {
printCh <- i
}
cancelctx()
fmt.Println("do something finished")
}
func doAnother(ctx context.Context, printCh chan int) {
for {
select {
case <-ctx.Done():
if err := ctx.Err(); err != nil {
fmt.Printf("do Another with error, %s \n", err)
}
fmt.Println("do Another finished")
return
case data := <-printCh:
fmt.Printf("receive %d \n", data)
}
}
}
我們在doSomething方法中加了一個可以取消的Context, 然後定義了一個channel, 往channel裡面放入3個數,另外啟動一個goroutine doAnother 來接收它, 在完成放入數據後我們調用了取消方法, 通知doAnother 結束掉。 這樣程式就達到一個goroutine通知另外一個goroutine的效果, 程式運行結果如下
receive 0
receive 1
receive 2
do Another with error, context canceled
do Another finished
do something finished
通過列印的輸出我們可以看到doAnother是通過canneled來結束的。
如果我們再加一個goroutine,也能夠通過context來取消。 這裡比較簡單不貼代碼了。
context.WithTimeout
如果只是單純的通知另外一個goroutine,直接通過close channel也可以做到了, Context還可以通過WithTimeout設定超時時間來限定程式運行多久,我們簡單改造一下上面的代碼
func doSomething(ctx context.Context) {
ctx, cancelctx := context.WithTimeout(ctx, 1500*time.Millisecond)
defer cancelctx()
printCh := make(chan int)
go doAnother(ctx, printCh)
outer:
for i := 0; i < 3; i++ {
select {
case printCh <- i:
time.Sleep(1 * time.Second)
case <-ctx.Done():
break outer
}
}
cancelctx()
fmt.Println("do something finished")
我們把doSomething改造了一下, 通過context.WithTimeout(ctx, 1500*time.Millisecond)將context 改成運行1.5秒就退出, doAnother 保持不變, 然後發送的時候,發送一個就sleep 1秒。 運行效果如下
receive 0
receive 1
do Another with error, context deadline exceeded
do Another finished
do something finished
我們可以看到只發送了兩個數,就退出了,退出的原因是exceeded. 這樣就達到了控制goroutine運行時間的效果。
通過context.WithDeadline 也可以達到和WithTimeout一樣的效果,只是一個是按時間點來停,一個是按時間段來停, 示例代碼如下
deadLineTime := time.Now().Add(1500 * time.Millisecond)
ctx, cancelctx := context.WithDeadline(ctx, deadLineTime)
context.WithValue
context還有個用途是用來記錄和傳遞value, 比如我們可以向別的goroutine傳遞UserID,RequestID等要跟蹤的變數信息。 還是通過示例代碼來說明
func main() {
var ctx = context.Background()
key := 1
ctx = context.WithValue(ctx, key, "someValue")
doSomething(ctx)
}
func doSomething(ctx context.Context) {
fmt.Printf("Doing something! %s \n", ctx.Value(1))
}
輸出結果很簡單就是"Doing something! someValue" 這個someValue就是通過Context傳遞給doSomething方法裡面的。 如果ctx.Value的key不存在,也不會報錯,會返回nil.
上面key的寫法,Visual Studio Code會提示”should not use built-in type int as key for value; define your own type to avoid collisions“
就是說這個key可能衝突,比如你的程式用String "userID", 別人也用String ”userID“, 你取的時候取到別人用的了,那樣就給程式帶來隱患, 我們需要自定義key, 如下代碼示例
type ctxKey int
const (
ctxUserKey ctxKey = iota
ctxAuthToken
)
func main() {
var ctx = context.Background()
ctx = context.WithValue(ctx, ctxUserKey, "someValue")
doSomething(ctx)
}
func doSomething(ctx context.Context) {
fmt.Printf("Doing something! %s \n", ctx.Value(ctxUserKey))
}
我們定義了常量ctxUserKey, 它是ctxKey類型,ctxUserKey它的實際值是0, 在取值和賦值的時候我們同意用ctxUserKey, 這樣就能很清楚明白的拿到它的value. 如果我們用ctx.Value(0)是拿不到值的。
最後
作者在書中說context包的包一直存在爭議,為什麼呢? 因為它可以放入任意類型,這樣就讓有些偷懶的程式員把它當垃圾桶, 程式運行需要的參數也用它來傳遞,這樣它就被賦予了太多它不應該的功能。 那麼我們需要遵守一定的規則來使用它,作者給出了建議
1, 數據應該通過進程或API邊界, 這句話感覺是翻譯問題,不是特別清楚,我感覺作者的意思是通過明確的參數來傳遞數據,而不是context
2,數據應該是不可變的。 意思就是只WithValue一次,不要去修改它
3,數據應該趨向簡單類型, 保持簡單很重要
4,數據應該是數據,而不是類型與方法, 也是保持簡單
5,數據應該用於裝飾操作,而不是驅動操作。意思就是你不要通過context裡面的內容來決定你的代碼邏輯。
作者最後說context提供的取消功能非常有用, 就是WithValue不要濫用。這一章感覺翻譯的不好,讀起來很晦澀,我還是看了另外一篇文章搞明白的。 貼出鏈接在此https://www.digitalocean.com/community/tutorials/how-to-use-contexts-in-go