【问题标题】:Queue implementation using slices in Go在 Go 中使用切片实现队列
【发布时间】:2016-12-03 17:23:30
【问题描述】:

我在 Go 中看到了一些使用切片的 FIFO 队列实现。当项目退出队列时,是否可以在不重新分配底层数组的情况下释放此内存?如果这没有发生,在我看来,队列会泄漏大量内存。这就是我的意思:

type queue
{
  []int
  int head
}

func (q *queue) enqueue(val int) {
   q = append(q, val)
}

func (q *queue) dequeue() int {
   return (*q)[q.head++]
}

在多次调用 enqueue/dequeue 之后,位于 slice 下的数组的低索引不再可用,但我也不确定如何释放它们。有人可以指出一个不使用指针的正确队列实现,并且不会像这样泄漏内存或有性能问题吗?或者,也将赞赏这可能如何工作的描述。

谢谢, 普拉门

【问题讨论】:

  • 首先,如果你想要并发,你需要在这些方法中使用互斥锁,这已经开始有味道了。我使用 Channels 处理队列,因为通道基本上只是堆栈副本。 GC 会在使用后清理它们。
  • 对线程安全不感兴趣,只对这个问题的内存管理感兴趣。从我读到的内容来看,使用通道来实现队列是一个坏主意,因为它们与常规调度密切相关,并且可以挂起整个程序。在最好的情况下,它们效率低下。这是我读过的书:amazon.com/…
  • 上面的代码不是实现,只是为了说明我的问题
  • 实现的权衡是您必须决定的。这个例子似乎是故意泄漏内存,所以我不确定它试图说明什么时候你可以很容易地切掉出队的值。如果您想将 std 库堆用作示例,也经常将其用作优先级队列。
  • 如果您对内存管理感兴趣,可以看看this answer 及其 cmets。不确定它实际上会对您有多大帮助,但这通常是一个好处,因为您通常不希望仅仅添加一个值而导致执行时间损失......

标签: go data-structures queue slice


【解决方案1】:

您可以使用循环缓冲区。来自wikipedia

循环缓冲区、循环队列、循环缓冲区或环形缓冲区是一种使用单个固定大小缓冲区的数据结构,就好像它是端到端连接的一样。这种结构很容易缓冲数据流。

...

对于具有固定最大大小的队列,循环缓冲是一种很好的实施策略。如果队列采用最大大小,那么循环缓冲区是一个完全理想的实现;所有队列操作都是常数时间。然而,扩展一个循环缓冲区需要移动内存,这是比较昂贵的。对于任意扩展队列,可能首选链表方法。

这是一个实现此功能的包:https://github.com/eapache/queue

根据用例,通道也是实现队列的好方法。它会阻塞,但使用带有默认值的选择可以避免这种行为:

select {
case msg := <-queue:
default:
}

【讨论】:

    猜你喜欢
    • 2011-03-03
    • 2012-11-26
    • 2019-01-02
    • 1970-01-01
    • 2018-08-16
    • 2011-02-25
    相关资源
    最近更新 更多