You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
3.0 KiB

  1. package buffered_queue
  2. import (
  3. "sync"
  4. )
  5. // ItemChunkNode represents a node in the linked list of job chunks
  6. type ItemChunkNode[T any] struct {
  7. items []T
  8. headIndex int
  9. tailIndex int
  10. next *ItemChunkNode[T]
  11. nodeId int
  12. }
  13. // BufferedQueue implements a buffered queue using a linked list of job chunks
  14. type BufferedQueue[T any] struct {
  15. chunkSize int // Maximum number of items per chunk
  16. head *ItemChunkNode[T]
  17. tail *ItemChunkNode[T]
  18. last *ItemChunkNode[T] // Pointer to the last chunk, for reclaiming memory
  19. count int // Total number of items in the queue
  20. mutex sync.Mutex
  21. nodeCounter int
  22. waitOnRead bool
  23. waitCond *sync.Cond
  24. }
  25. // NewBufferedQueue creates a new buffered queue with the specified chunk size
  26. func NewBufferedQueue[T any](chunkSize int, waitOnRead bool) *BufferedQueue[T] {
  27. // Create an empty chunk to initialize head and tail
  28. chunk := &ItemChunkNode[T]{items: make([]T, chunkSize), nodeId: 0}
  29. bq := &BufferedQueue[T]{
  30. chunkSize: chunkSize,
  31. head: chunk,
  32. tail: chunk,
  33. last: chunk,
  34. count: 0,
  35. mutex: sync.Mutex{},
  36. waitOnRead: waitOnRead,
  37. }
  38. bq.waitCond = sync.NewCond(&bq.mutex)
  39. return bq
  40. }
  41. // Enqueue adds a job to the queue
  42. func (q *BufferedQueue[T]) Enqueue(job T) {
  43. q.mutex.Lock()
  44. defer q.mutex.Unlock()
  45. // If the tail chunk is full, create a new chunk (reusing empty chunks if available)
  46. if q.tail.tailIndex == q.chunkSize {
  47. if q.tail == q.last {
  48. // Create a new chunk
  49. q.nodeCounter++
  50. newChunk := &ItemChunkNode[T]{items: make([]T, q.chunkSize), nodeId: q.nodeCounter}
  51. q.tail.next = newChunk
  52. q.tail = newChunk
  53. q.last = newChunk
  54. } else {
  55. // Reuse an empty chunk
  56. q.tail = q.tail.next
  57. q.tail.headIndex = 0
  58. q.tail.tailIndex = 0
  59. // println("tail moved to chunk", q.tail.nodeId)
  60. }
  61. }
  62. // Add the job to the tail chunk
  63. q.tail.items[q.tail.tailIndex] = job
  64. q.tail.tailIndex++
  65. q.count++
  66. if q.waitOnRead {
  67. q.waitCond.Signal()
  68. }
  69. }
  70. // Dequeue removes and returns a job from the queue
  71. func (q *BufferedQueue[T]) Dequeue() (T, bool) {
  72. q.mutex.Lock()
  73. defer q.mutex.Unlock()
  74. if q.waitOnRead {
  75. for q.count <= 0 {
  76. q.waitCond.Wait()
  77. }
  78. } else {
  79. if q.count == 0 {
  80. var a T
  81. return a, false
  82. }
  83. }
  84. job := q.head.items[q.head.headIndex]
  85. q.head.headIndex++
  86. q.count--
  87. if q.head.headIndex == q.chunkSize {
  88. q.last.next = q.head
  89. q.head = q.head.next
  90. q.last = q.last.next
  91. q.last.next = nil
  92. //println("reusing chunk", q.last.nodeId)
  93. //fmt.Printf("head: %+v\n", q.head)
  94. //fmt.Printf("tail: %+v\n", q.tail)
  95. //fmt.Printf("last: %+v\n", q.last)
  96. //fmt.Printf("count: %d\n", q.count)
  97. //for p := q.head; p != nil ; p = p.next {
  98. // fmt.Printf("Node: %+v\n", p)
  99. //}
  100. }
  101. return job, true
  102. }
  103. // Size returns the number of items in the queue
  104. func (q *BufferedQueue[T]) Size() int {
  105. q.mutex.Lock()
  106. defer q.mutex.Unlock()
  107. return q.count
  108. }
  109. // IsEmpty returns true if the queue is empty
  110. func (q *BufferedQueue[T]) IsEmpty() bool {
  111. return q.Size() == 0
  112. }