Go by Example: Stateful Goroutines有状态的协程

英文源地址
在前面的例子中, 我们使用了带有互斥锁的显式锁来同步跨多个协程共享状态的访问.另一种选择是使用协程和通道的内置同步特性来实现相同的效果.这种基于通道的方法符合Go语言的思想,即通过通信共享内存, 并使每个数据块由一个协程拥有.

package main

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

// 在这个例子中, 我们的状态由一个单独的协程拥有
// 这将保证数据永远不会因并发访问而损坏.
// 为了读取或写入状态, 其他协程将向所属的协程发送消息并接收相应的回复.
// 这些readOp和writeOp结构封装了这些请求, 并为所属的程序提供了一种响应方式.
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

	// 其他协程分别使用读和写通道来发出读和写请求
	reads := make(chan readOp)
	writes := make(chan writeOp)

	// 下面是拥有状态的运行协程, 它和前面的例子一样是一个映射.
	// 但现在是有状态运行协程私有的.
	// 该协程反复select读和写通道, 在请求到达时进行响应.
	// 响应的执行方式是, 首先执行请求的操作, 然后在响应通道resp上发送一个值来表示成功(以及读取的情况下的期望值)
	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
			}
		}
	}()

	// 这将启动100个协程, 通过读通道向拥有state的协程发出读操作.
	// 每次读取都需要构造一个readOp,通过读取通道发送, 然后通过提供的响应通道接受结果.
	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)
			}
		}()
	}

	// 我们也开始10次写入, 使用类似的方法
	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)
			}
		}()
	}
	// 让协程运行一秒钟
	time.Sleep(time.Second)

	// 最终, 捕获并打印操作次数
	readOpsFinal := atomic.LoadUint64(&readOps)
	fmt.Println("readOps:", readOpsFinal)
	writeOpsFinal := atomic.LoadUint64(&writeOps)
	fmt.Println("writeOps", writeOpsFinal)
}

运行我们的程序显示, 基于运行协程的状态管理示例总共完成了大约80000次操作.

$ go run stateful-goroutines.go
readOps: 71708
writeOps: 7177

对于这种特殊情况, 基于协程的方法比基于互斥锁的方法更复杂一点. 但是, 在某些情况下它可能是有用的, 例如当涉及到其他通道时, 或者当管理多个这样的互斥锁容易出错时.你应该使用感觉最自然的方法, 特别是在理解程序的正确性方面.
下一节将介绍: 排序

猜你喜欢

转载自blog.csdn.net/weixin_43547795/article/details/130874268