中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go36-32-context.Context

發布時間:2020-07-24 05:20:04 來源:網絡 閱讀:455 作者:騎士救兵 欄目:編程語言

context.Context

sync.WaitGroup類型是一個實現一對多goroutine協作流程的同步工具。還有另一種工具也可以實現這種協作流程。

回顧sync.WaitGroup實現協作流程

在使用WaitGroup的時候,建議是用“先統一Add,再并發Done,最后Wait”的模式來構建協作流程。要避免并發的調用Add方法。這就帶來一個問題,需要在一開始就能確定執行子任務的goroutine的數量,至少也是在啟動goroutine之前。
下面是一個示例,稍微做了一些改造:

package main

import (
    "time"
    "fmt"
    "sync"
    "sync/atomic"
)

func coordinateWithWaitGroup() {
    total := 12
    var num int32
    var wg sync.WaitGroup
    // 定義好goroutine中返回前要執行的defer函數
    deferFunc := func() {
        wg.Done()
    }
    for i := 0; i < total; i++ {
        wg.Add(1)
        go addNum(&num, i, deferFunc)
    }
    wg.Wait()
}

// 這個函數的defer函數通過參數來給出
func addNum(numP *int32, id int, deferFunc func()) {
    defer deferFunc()
    for i := 1; ; i++ {
        currNum := atomic.LoadInt32(numP)
        newNum := currNum + 1
        time.Sleep(time.Millisecond * 200)
        if atomic.CompareAndSwapInt32(numP, currNum, newNum) {
            fmt.Printf("id: %02d 第 %02d 次更新num成功: %d\n", id, i, newNum)
            break
        }
    }
}

func main() {
    coordinateWithWaitGroup()
}

這里的改造是為了更像之后要使用context包時的用法,不過在使用規則上還是滿足WaitGroup的要求的。

通過context包實現協作流程

這里就是要在寫一個coordinateWithWaitContext函數,來代替上面的coordinateWithWaitGroup函數。兩個函數要具有相同的功能。
這里先直接給出示例代碼了:

func coordinateWithWaitContext() {
    total := 12
    var num int32
    cxt, cancelFunc := context.WithCancel(context.Background())
    // 定義好goroutine中返回前要執行的defer函數,這里用到了上面的cancelFunc
    deferFunc := func() {
        if atomic.LoadInt32(&num) == int32(total) {
            cancelFunc()
        }
    }
    for i := 0; i < total; i++ {
        go addNum(&num, i, deferFunc)
    }
    <- cxt.Done()
}

所有的變化都在上面這個函數里了。這里先后調用了context.Background函數和context.WithCancel函數。得到了一個可撤銷context.Context類型的值,賦值給了變量cxt。還有一個context.CancelFunc類型的撤銷函數,賦值給了變量cancelFunc。
這里在判斷goroutine執行完畢的依據是通過判斷num里的值。一旦判斷完成,就會調用之前準備好的cancelFunc函數,此時cxt.Done函數返回的通道就會接收到值,結束等待。

和WaitGroup的比較
WaitGroup需要事先知道所有goroutine的數量,而context這里更關心是否滿足某個條件,一旦條件滿足就可以退出。
這里我想提一下python,讓我想到了python中的for循環和while循環。能用for循環就不要用while循環。使用while循環可能由于條件判斷復雜了,造成條件永遠無法滿足而成了死循環。使用for循環的話就沒有這個問題了。不過當循環的退出和數量沒有關系時,只能用while循環了。
就好比WaitGroup,如果可以通過goroutine的數量判斷,那么應該還是使用WaitGroup好。如果遇到結束條件和goroutine數量無關的時候,就只能用context了。

context.Context類型

context.Context類型,是在Go 1.7發布時才被加入到標準庫的。而后,標準庫中的很多其他代碼包都為了支持它而進行了擴展,包括:os/exec包、net包、database/sql包、runtime/pprof包和runtime/trace包,等等。
之所以會收到眾多代碼包的積極支持,主要因為它是一種非常通用的同步工具。它的值不但可以任意的擴散,而且還可以被用來傳遞額外的信息和信號。就是Context類型可以提供一個代表上下文的值,之類值是并發安全的,也就是說它可以被傳播給多個goroutine。

接口類型
Context最新實際是一個接口類型,在context包中實現該接口的所有私有類型,都是基于某個數據類型的指針類型。所以,如此傳播并不會影響該類型值的功能和安全。

可繁衍的
Context類型的值是可以繁衍的,這意味著可以通過一個Context值產生出任意個子值。這些子值可以攜帶父值的屬性和數據,也可以相應通過其父值傳達的信號。如此,所有的Context值共同構成了一顆代表了上下文全貌的屬性結構。樹的根節點是一個已經在context包中預定義好的context值,它是全局唯一的。通過調用context.Background函數,就可以獲取到它。

包內的函數
在context包中,包含了4個用于繁衍Context值的函數:

  • WithCancel,產生一個可撤銷的parent的子值
  • WithDeadline,產生一個會定時撤銷的parent的子值
  • WithTimeout,同上,也是定時撤銷的parent的子值
  • WithValue,產生一個會攜帶額外數據的parent的子值

這些函數的第一個參數類型都是context.Context,而名稱都為parent。顧名思義,這個位置上的參數對應的都是產生Context值的父值。

撤銷信號在上下文樹中的傳播

context包中的WithCancel、WithDeadline和WithTimeout都是被用來基于給定的COntext值產生可撤銷的子值的。

WithCancel
這個函數在被調用后,產生兩個結果值。第一個是可撤銷的Context值,第二個是用于觸發撤銷信號的函數。
撤銷函數被調用后,對應的Context值會先關閉它內部的接收通道,通道關閉了接收該通道的操作就會立即返回,就是Done方法返回的那個通道。然后,它還會向它的所有子值傳達撤銷信號。這些子值如果還有子值,就會一級一級把撤銷信號傳遞下去。最后,這個Context值會斷開它與其父值之間的關聯。

WithDeadline和WithTimeout
通過調用WithDeadline函數或者WithTimeout函數生成的Context值也是可撤銷的。它們不但可以被手動撤銷,還會依據在生成是給定的過期時間,自動地進行定時撤銷。這里的定時撤銷功能是借助它們內部的計時器來實現的。
當過期時間到達時,兩種Context值的行為與手動撤銷是的行為是幾乎一致的,只是多了一步停止并釋放掉內部的計時器。
WithDeadline和WithTimeout是相似的。都是通過設置,會在某個時間自動觸發,就是ctx.Done()能夠取到值。差別是,DeadLine是設置一個時間點,時間對上了就到期。Timeout是設置一段時間,比如幾秒,過個這段時間,就超時。其實底層的Timeout也是通過Deadlin實現的。

WithValue
這個函數得到的值是不可撤銷的。撤銷信號在傳播時,若遇到它們會直接跨過,并試圖將信息直接傳給它們的子值。

傳遞數據

通過WithValue函數產生新的Context值的時候需要3個參數:父值、鍵和值。這里鍵必須是可判斷等的,類似字典的鍵。不過Context值并不是用字典來存儲鍵和值的,而是簡單地存儲在父值相應的字段中。
通過Value方法,可以獲取數據。在調用包含屬性的Context值的Value方法是,會先判斷給定的鍵,如有有就返回存儲的值,否則會到其父值中繼續查找,會一直沿著上下文根節點的方法一直查找。因為其他幾種Context值都是無法攜帶數據的,所以Value方法在查找的時候,會跨過這這些Context值。

無法改變數據
Context接口沒有提供改變數據的方法,所以通常只能通過在上下文數中添加含數據的Context值來存儲新的數據,或者通過撤銷此種值的父值丟棄掉相應的數據。如果存儲在這里的數據可以從外部改變,那么必須自信保證安全。

下面這個示例展示了Context值里數據的傳遞:

package main

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

type myKey int

func main() {
    keys := []myKey{
        myKey(20),
        myKey(30),
        myKey(60),
        myKey(61),
    }
    values := []string{
        "value in node2",
        "value in node3",
        "value in node6",
        "value in node6Branch",
    }

    rootNode := context.Background()
    node1, cancelFunc1 := context.WithCancel(rootNode)
    defer cancelFunc1()

    node2 := context.WithValue(node1, keys[0], values[0])
    node3 := context.WithValue(node2, keys[1], values[1])
    fmt.Printf("The value of the key %v found in the node3: %v\n",
        keys[0], node3.Value(keys[0]))
    fmt.Printf("The value of the key %v found in the node3: %v\n",
        keys[1], node3.Value(keys[1]))
    fmt.Printf("The value of the key %v found in the node3: %v\n",
        keys[2], node3.Value(keys[2]))
    fmt.Println()

    node4, cancelFunc4 := context.WithCancel(node3)
    defer cancelFunc4()
    node5, cancelFunc5 := context.WithTimeout(node4, time.Hour)
    defer cancelFunc5()
    fmt.Printf("The value of the key %v found in the node5: %v\n",
        keys[0], node5.Value(keys[0]))
    fmt.Printf("The value of the key %v found in the node5: %v\n",
        keys[1], node5.Value(keys[1]))
    fmt.Println()

    node6 := context.WithValue(node5, keys[2], values[2])
    fmt.Printf("The value of the key %v found in the node6: %v\n",
        keys[0], node6.Value(keys[0]))
    fmt.Printf("The value of the key %v found in the node6: %v\n",
        keys[2], node6.Value(keys[2]))
    fmt.Println()

    node6Branch := context.WithValue(node5, keys[3], values[3])
    fmt.Printf("The value of the key %v found in the node6Branch: %v\n",
        keys[1], node6Branch.Value(keys[1]))
    fmt.Printf("The value of the key %v found in the node6Branch: %v\n",
        keys[2], node6Branch.Value(keys[2]))
    fmt.Printf("The value of the key %v found in the node6Branch: %v\n",
        keys[3], node6Branch.Value(keys[3]))
    fmt.Println()

    node7, cancelFunc7 := context.WithCancel(node6)
    defer cancelFunc7()
    node8, cancelFunc8 := context.WithTimeout(node7, time.Hour)
    defer cancelFunc8()
    fmt.Printf("The value of the key %v found in the node8: %v\n",
        keys[1], node8.Value(keys[1]))
    fmt.Printf("The value of the key %v found in the node8: %v\n",
        keys[2], node8.Value(keys[2]))
    fmt.Printf("The value of the key %v found in the node8: %v\n",
        keys[3], node8.Value(keys[3]))
}

總結

Context類型是一個可以實現多goroutine協作流程同步的工具。還可以通過它的值傳達撤銷信號或傳遞數據。
Context類型的值大體可分3種:

  • 根Context值
  • 可撤銷的Context值
  • 含數據的Context值

所有的Context值共同構成了一顆上下文樹。這棵樹的作用域是全局的,根Context值就是樹的根,它也是全局唯一的,并且不提供任何額外的功能。
包含數據的Context值不能被撤銷,可撤銷的Context值又無法攜帶數據。但是,由于它們共同組成了一個有機的整體,即上下文數,所以在功能上要比sync.WaitGroup強大的多。

這個系列偏重理論,就少了很多實際的應用,關于context包,我之前還有一篇:
https://blog.51cto.com/steed/2330218
在這篇里介紹了兩個主要功能:

  • 控制超時時間
  • 保存上下文數據
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

栖霞市| 宝兴县| 平潭县| 赞皇县| 化德县| 宕昌县| 南召县| 定西市| 荔浦县| 肃宁县| 高青县| 青河县| 涟水县| 商水县| 黔西县| 开鲁县| 定远县| 诸城市| 呼图壁县| 吉水县| 成都市| 永福县| 石棉县| 巴塘县| 宝鸡市| 上思县| 民县| 河北省| 伽师县| 富源县| 铅山县| 扎兰屯市| 栾城县| 唐河县| 疏附县| 监利县| 海原县| 桂东县| 潼关县| 锦州市| 汤阴县|