利用 channel 实现内存同步

前言

实现内存同步最常用的方式当然是使用锁(MutexRwLock,…),但在 golang 中我们知道,对于一个无缓冲的 channel,除非 receiver 准备就绪,否则 sender 的发送会一直阻塞,利用这一点,在一个新的 goroutine 中维护一个需要同步的状态,通过 select 去监听其他 goroutine 中发来的同步请求就可以实现内存同步。

读写分离

首先进行读写操作的抽象:

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

然后创建单独的读写 channel:

var readOps uint64  // 记录读的次数
var writeOps uint64 // 记录写的次数

reads := make(chan readOp)
writes := make(chan writeOp)

开辟一个 goroutine 去监听所有的读写请求:

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 个 goroutine 去读,10 个去写:

for r := 0; r < 100; r++ {
    go func() {
        for {
            read := readOp{
            key:  rand.Intn(5),
            resp: make(chan int)}
            reads <- read // 当 reads 接收端未准备好时,阻塞
            <-read.resp   // 取出结果,防止监听阻塞
            atomic.AddUint64(&readOps, 1)
            time.Sleep(time.Millisecond)
        }
    }()
}

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 // 当 writes 接收端未准备好时,阻塞
            <-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)

输出:

readOps: 82028
writeOps: 8208

版本控制

Version Action Time
1.0 Init 2021-12-31
1.0 Update title 2022-01-05