39-stateful-goroutine

//  在前面的例子中,使用显示定义的互斥锁来跨多个goroutine同步对共享状态的访问。

// 另一种方式是使用goroutine和通道内置的同步功能来实现相同的效果,
// 这种基于通道的方式和Go共享内存的想法一致,通过通信使每块数据只被一个goroutine拥有。

package main

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

// 在这个例子中,状态只会被一个goroutine拥有,
// 这将保证数据永远不会因为并发访问而损坏。
// 为了读取或者写入状态,其他的goroutine需要发送消息给状态的持有goroutine并收到相应的响应

// readOp和WriteOp结构体封装了这些请求和持有状态的goroutine的一个响应方法
type readOp struct {
	key  int
	resp chan int
}

type writeOp struct {
	key  int
	val  int
	resp chan bool
}

func main() {
	// 记录总共执行的读取和写入次数
	var readOps uint64
	var writeOps uint64

	// 读取和写入通道将被其他的goroutine使用来分别发出读取或写入请求
	reads := make(chan readOp)
	writes := make(chan writeOp)

	// 这是持有状态的goroutine
	go func() {
		// goroutine私有的状态map
		var state = make(map[int]int)
		for {
			// 这个goroutine不断的从读取和写入通道中进行选择,并响应获取到的结果
			select {
			case read := <-reads:
				read.resp <- state[read.key]
			case write := <-writes:
				state[write.key] = write.val
				write.resp <- true
			}
		}
	}()

	for r := 0; r < 100; r++ {
		// 启动100个goroutine通过reads通道向持有状态的goroutine发送读取请求
		go func() {
			// 每次读取请求都会构造一个readOp结构,并通过reads通道进行发送,然后通过resp通道获取结果
			for {
				read := readOp{
					key:  rand.Intn(5),
					resp: make(chan int)}
				reads <- read
				<-read.resp
				atomic.AddUint64(&readOps, 1)
				time.Sleep(time.Millisecond)
			}
		}()
	}

	for w := 0; w < 10; w++ {
		// 启动10个goroutine进行写入请求
		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)
			}
		}()
	}

	// 等待上述goroutine执行一秒钟
	time.Sleep(time.Second)

	// 捕获并返回操作的总次数
	readOpsFinal := atomic.LoadUint64(&readOps)
	fmt.Println("readOps:", readOpsFinal)
	writeOpsFinal := atomic.LoadUint64(&writeOps)
	fmt.Println("writeOps:", writeOpsFinal)
}

// 程序运行结果表明,给予goroutine的状态管理完成了10万次操作

// 这种情况下,基于goroutine的方法比基于互斥锁的方法更复杂一些,
// 在某些情况下它可能很有用例如涉及多个管道或管理多个互斥锁时容易出错
上次修改: 10 January 2020