您好,登錄后才能下訂單哦!
sync.WaitGroup類型是一個實現一對多goroutine協作流程的同步工具。還有另一種工具也可以實現這種協作流程。
在使用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的要求的。
這里就是要在寫一個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類型,是在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值的函數:
這些函數的第一個參數類型都是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值又無法攜帶數據。但是,由于它們共同組成了一個有機的整體,即上下文數,所以在功能上要比sync.WaitGroup強大的多。
這個系列偏重理論,就少了很多實際的應用,關于context包,我之前還有一篇:
https://blog.51cto.com/steed/2330218
在這篇里介紹了兩個主要功能:
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。