简单的并发队列

时间:2015-03-02 11:49:20

标签: go

有人可以提一下像Queue一样的缺陷和性能缺陷吗?

type Queue struct {
    sync.Mutex
    Items  []interface {}
}

func (q *Queue)Push(item interface {}){
    q.Lock()
    defer q.Unlock()
    q.Items = append(q.Items,item)
}


func (q *Queue)Pop()interface {}{
    q.Lock()
    defer q.Unlock()
    if(len(q.Items)==0){
        return nil;
    }
    item := q.Items[0]
    q.Items= q.Items[1:]
    return item
}

我也有像PopMany和PushMany这样的方法,我关注的是,重新切片那么糟糕吗?

3 个答案:

答案 0 :(得分:5)

您可以简单地使用缓冲频道。

var queue = make(chan interface{}, 100)

缓冲区的大小可以根据经验确定足够大,以达到推动率与流行率的高水位标记。理想情况下,它应该不大于此,以避免浪费记忆。

实际上,如果相互作用的goroutines因其他原因没有死锁,那么较小的缓冲区大小也会起作用。如果使用较小的缓冲区大小,则通过goroutine时间片引擎(Go运行时的一部分)的运行队列有效地进行排队。 (很可能,缓冲区大小为零可能在许多情况下都有效。)

频道允许许多读者goroutines和许多作家goroutines。 Go运行时自动处理它们的访问并发性。对信道的所有写入都是交织的,以便成为顺序流。所有读取也是交错的,以它们排队的顺序依次提取值。这是关于这个主题的further discussion

答案 1 :(得分:1)

重新切片不是问题。你是否有一个线程安全或不安全的版本也没有区别,因为这几乎是重新调整大小的目的。

您可以通过使用容量初始化队列来减轻一些重新调整大小的开销:

func NewQueue(capacity int) *Queue {
    return &Queue {
        items: make([]interface{}, 0, capacity),
    }
}

此队列仍然可以超出容量,但在达到容量之前,您不会进行任何不必要的复制/重新分配。

可能导致许多并发访问问题的是互斥锁。在某些时候,您将花费更多时间等待锁定被释放,而不是实际工作。这是锁争用的一般问题,可以通过将队列实现为无锁数据结构来解决。

有一些第三方软件包可以提供基本数据结构的无锁实现。

这对您实际上是否有用只能通过一些基准确定。无锁结构可以具有更高的基本成本,但是当您获得许多并发用户时,它们的扩展性会更好。有一个截止点,互斥锁比无锁方法更昂贵。

答案 2 :(得分:1)

我认为解决此问题的最佳方法是使用链接列表,标准包中已有一个可用的链接here