Go有狀態的goroutines實例
在前面的示例中,我們使用顯式鎖定互斥體來同步對多個goroutine的共享狀態的訪問。 另一個選項是使用goroutine
和通道的內置同步功能來實現相同的結果。這種基於通道的方法與Go的共享內存的想法一致,通過溝通,擁有每個數據的goroutine
恰好只有1
個。
在這個例子中,狀態將由單個goroutine
擁有。這將保證數據不會因併發訪問而損壞。爲了讀或寫狀態,其他goroutine
將發送消息到擁有的goroutine
並接收相應的回覆。這些readOp
和writeOp
結構封裝了這些請求,並擁有一個goroutine
響應的方法。
和以前一樣,我們將計算執行的操作數。
讀寫通道將被其他goroutine
分別用來發出讀和寫請求。
這裏是擁有狀態的goroutine
,它是一個如前面示例中的映射,但現在對狀態goroutine
是私有的。這個goroutine
在讀取和寫入通道時重複選擇,在請求到達時響應請求。 通過首先執行所請求的操作,然後在響應信道上發送值以指示成功(以及在讀取的情況下的期望值)來執行響應。
這裏啓動了100
個goroutine
來通過讀取通道向狀態擁有的goroutine
發出讀取。每次讀取都需要構造一個readOp
,通過讀取通道發送readOp
,並通過提供的resp
通道接收結果。
也使用類似的方法開始10
個寫操作。讓goroutine
工作一秒鐘。最後,捕獲和報告操作計數。
運行程序顯示,基於goroutine
的狀態管理示例程序,完成了大約80,000
次操作。
對於這種特殊情況,基於goroutine
的方法比基於互斥的方法更多一些。它在某些情況下可能是有用的,例如,當有其他通道涉及或管理多個此類互斥體將容易出錯。應該使用最自然的方法,有助於理解程序。
所有的示例代碼,都放在
F:\worksp\golang
目錄下。安裝Go編程環境請參考:http://www.yiibai.com/go/go\_environment.html
stateful-goroutines.go
的完整代碼如下所示 -
package main
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
// In this example our state will be owned by a single
// goroutine. This will guarantee that the data is never
// corrupted with concurrent access. In order to read or
// write that state, other goroutines will send messages
// to the owning goroutine and receive corresponding
// replies. These `readOp` and `writeOp` `struct`s
// encapsulate those requests and a way for the owning
// goroutine to respond.
type readOp struct {
key int
resp chan int
}
type writeOp struct {
key int
val int
resp chan bool
}
func main() {
// As before we'll count how many operations we perform.
var readOps uint64 = 0
var writeOps uint64 = 0
// The `reads` and `writes` channels will be used by
// other goroutines to issue read and write requests,
// respectively.
reads := make(chan *readOp)
writes := make(chan *writeOp)
// Here is the goroutine that owns the `state`, which
// is a map as in the previous example but now private
// to the stateful goroutine. This goroutine repeatedly
// selects on the `reads` and `writes` channels,
// responding to requests as they arrive. A response
// is executed by first performing the requested
// operation and then sending a value on the response
// channel `resp` to indicate success (and the desired
// value in the case of `reads`).
go func() {
var state = make(map[int]int)
for {
select {
case read := <-reads:
read.resp <- state[read.key]
case write := <-writes:
state[write.key] = write.val
write.resp <- true
}
}
}()
// This starts 100 goroutines to issue reads to the
// state-owning goroutine via the `reads` channel.
// Each read requires constructing a `readOp`, sending
// it over the `reads` channel, and the receiving the
// result over the provided `resp` channel.
for r := 0; r < 100; r++ {
go func() {
for {
read := &readOp{
key: rand.Intn(5),
resp: make(chan int)}
reads <- read
<-read.resp
atomic.AddUint64(&readOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
// We start 10 writes as well, using a similar
// approach.
for w := 0; w < 10; w++ {
go func() {
for {
write := &writeOp{
key: rand.Intn(5),
val: rand.Intn(100),
resp: make(chan bool)}
writes <- write
<-write.resp
atomic.AddUint64(&writeOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
// Let the goroutines work for a second.
time.Sleep(time.Second)
// Finally, capture and report the op counts.
readOpsFinal := atomic.LoadUint64(&readOps)
fmt.Println("readOps:", readOpsFinal)
writeOpsFinal := atomic.LoadUint64(&writeOps)
fmt.Println("writeOps:", writeOpsFinal)
}
執行上面代碼,將得到以下輸出結果 -
F:\worksp\golang>go run mutexes.go
readOps: 84546
writeOps: 8473
state: map[0:99 3:3 4:62 1:18 2:89]