概述
环形缓冲区是另一个十分经典的生产者-消费者模型。其基本思想是:先开辟一块固定的内存作为保存元素(相同类型)的缓冲区,注意是一块固定的内存,开辟后大小就不能再修改了。也可以理解是一个数组。
当生产者往缓冲区中放入元素时,判断是否已经到了该队列的末尾,若到了末尾则应该,绕回到队列的首部放入元素,也就是第0个位置。
而消费者也是类似的操作(当取元素的位置到了队列最后一个,则绕回到第0个位置),另外消费者需要判断队列是否已经已经满了,判断的条件是放元素的位置是否和取元素的位置相等,若相等则表示队列已经满了,此时需要等待新元素的放入。
可见,该模型是一个典型的共享内存模型,所以需要使用sync包的锁和型号等同步机制。
代码实现
package main
import (
"sync"
"math/rand"
"time"
"fmt"
)
// 定义两个变量
var c *sync.Cond
var cqueue []interface{}
var ppos int // 存放元素的位置
var gpos int // 获取元素的位置
var curlen int // 目前元素的个数
var total int // 总的元素个数
func main() {
c = sync.NewCond(&sync.Mutex{})
cqueue = make([]interface{}, 10)
total = len(cqueue)
go produce("p1")
go produce("p2")
go produce("p3")
go consumer("c1")
go consumer("c2")
go consumer("c3")
go consumer("c4")
// 等待一会看效果
time.Sleep(100e9)
}
// 生产者
func produce(name string) {
for {
time.Sleep(1e9)
c.L.Lock()
ppos += 1
if ppos == total { // roll back
ppos = 0
}
v := rand.Uint32()
cqueue[ppos] = v
fmt.Printf("%s put item[%d]: %d\n", name, ppos, v)
c.L.Unlock()
c.Signal()
}
}
// 消费者
func consumer(name string) {
time.Sleep(1e9)
for {
c.L.Lock()
for gpos == ppos {
c.Wait()
}
gpos += 1
if gpos == total { //roll back
gpos = 0
}
v := cqueue[gpos]
fmt.Printf("%s get item[%d]: %d\n", name, gpos, v)
c.L.Unlock()
}
}
运行以上代码,输出如下:
p1 put item[1]: 2596996162
p2 put item[2]: 4039455774
c1 get item[1]: 2596996162
c1 get item[2]: 4039455774
p3 put item[3]: 2854263694
c4 get item[3]: 2854263694
p2 put item[4]: 1879968118
p1 put item[5]: 1823804162
c4 get item[4]: 1879968118
c4 get item[5]: 1823804162
p3 put item[6]: 2949882636
c2 get item[6]: 2949882636
p1 put item[7]: 281908850
p2 put item[8]: 672245080
c1 get item[7]: 281908850
c1 get item[8]: 672245080
p3 put item[9]: 416480912
c3 get item[9]: 416480912
p2 put item[0]: 1292406600
c2 get item[0]: 1292406600
p1 put item[1]: 2212821389
c1 get item[1]: 2212821389
p3 put item[2]: 3494557023
c4 get item[2]: 3494557023
p2 put item[3]: 920256325
... ...
实现分析
从以上代码可以看出,我们通过两个变量来记录放的位置,和取元素的位置,当这两个变量到达队列的最后一个位置后,会回到第0个位置继续操作。
当gpos(放的位置)和ppos(取的位置)相等时,说明队列已经满了,此时消费者需要等待。
而生产者会一直队列中放入数据,即使队列满了,也不会停止,所以,若消费者消费的速度比生产者慢,可能会覆盖老的元素。在使用时,要注意根据使用场景来开辟缓冲区的大小,避免缓冲区过大而浪费,或过小而导致老数据被覆盖。
总结
通过sync包实现了一个环形队列的模型,该模型使用的场景相对较多。其实,kernel中网卡的驱动的缓冲区模型也和这个类似。
以上代码只是一个示例,使用时,可以把以上代码进行封装。