Go并发编程--sync包来实现环形缓冲队列

概述

环形缓冲区是另一个十分经典的生产者-消费者模型。其基本思想是:先开辟一块固定的内存作为保存元素(相同类型)的缓冲区,注意是一块固定的内存,开辟后大小就不能再修改了。也可以理解是一个数组。
当生产者往缓冲区中放入元素时,判断是否已经到了该队列的末尾,若到了末尾则应该,绕回到队列的首部放入元素,也就是第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中网卡的驱动的缓冲区模型也和这个类似。
以上代码只是一个示例,使用时,可以把以上代码进行封装。

猜你喜欢

转载自blog.csdn.net/zg_hover/article/details/81212064
今日推荐