Go有狀態的goroutines實例

在前面的示例中,我們使用顯式鎖定互斥體來同步對多個goroutine的共享狀態的訪問。 另一個選項是使用goroutine和通道的內置同步功能來實現相同的結果。這種基於通道的方法與Go的共享內存的想法一致,通過溝通,擁有每個數據的goroutine恰好只有1個。

在這個例子中,狀態將由單個goroutine擁有。這將保證數據不會因併發訪問而損壞。爲了讀或寫狀態,其他goroutine將發送消息到擁有的goroutine並接收相應的回覆。這些readOpwriteOp結構封裝了這些請求,並擁有一個goroutine響應的方法。

和以前一樣,我們將計算執行的操作數。

讀寫通道將被其他goroutine分別用來發出讀和寫請求。

這裏是擁有狀態的goroutine,它是一個如前面示例中的映射,但現在對狀態goroutine是私有的。這個goroutine在讀取和寫入通道時重複選擇,在請求到達時響應請求。 通過首先執行所請求的操作,然後在響應信道上發送值以指示成功(以及在讀取的情況下的期望值)來執行響應。

這裏啓動了100goroutine來通過讀取通道向狀態擁有的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]