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.

112 lines
2.7 KiB

  1. package util
  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. }
  23. // NewBufferedQueue creates a new buffered queue with the specified chunk size
  24. func NewBufferedQueue[T any](chunkSize int) *BufferedQueue[T] {
  25. // Create an empty chunk to initialize head and tail
  26. chunk := &ItemChunkNode[T]{items: make([]T, chunkSize), nodeId: 0}
  27. return &BufferedQueue[T]{
  28. chunkSize: chunkSize,
  29. head: chunk,
  30. tail: chunk,
  31. last: chunk,
  32. count: 0,
  33. mutex: sync.Mutex{},
  34. }
  35. }
  36. // Enqueue adds a job to the queue
  37. func (q *BufferedQueue[T]) Enqueue(job T) {
  38. q.mutex.Lock()
  39. defer q.mutex.Unlock()
  40. // If the tail chunk is full, create a new chunk (reusing empty chunks if available)
  41. if q.tail.tailIndex == q.chunkSize {
  42. if q.tail == q.last {
  43. // Create a new chunk
  44. q.nodeCounter++
  45. newChunk := &ItemChunkNode[T]{items: make([]T, q.chunkSize), nodeId: q.nodeCounter}
  46. q.tail.next = newChunk
  47. q.tail = newChunk
  48. q.last = newChunk
  49. } else {
  50. // Reuse an empty chunk
  51. q.tail = q.tail.next
  52. q.tail.headIndex = 0
  53. q.tail.tailIndex = 0
  54. // println("tail moved to chunk", q.tail.nodeId)
  55. }
  56. }
  57. // Add the job to the tail chunk
  58. q.tail.items[q.tail.tailIndex] = job
  59. q.tail.tailIndex++
  60. q.count++
  61. }
  62. // Dequeue removes and returns a job from the queue
  63. func (q *BufferedQueue[T]) Dequeue() (T, bool) {
  64. q.mutex.Lock()
  65. defer q.mutex.Unlock()
  66. if q.count == 0 {
  67. var a T
  68. return a, false
  69. }
  70. job := q.head.items[q.head.headIndex]
  71. q.head.headIndex++
  72. q.count--
  73. if q.head.headIndex == q.chunkSize {
  74. q.last.next = q.head
  75. q.head = q.head.next
  76. q.last = q.last.next
  77. q.last.next = nil
  78. //println("reusing chunk", q.last.nodeId)
  79. //fmt.Printf("head: %+v\n", q.head)
  80. //fmt.Printf("tail: %+v\n", q.tail)
  81. //fmt.Printf("last: %+v\n", q.last)
  82. //fmt.Printf("count: %d\n", q.count)
  83. //for p := q.head; p != nil ; p = p.next {
  84. // fmt.Printf("Node: %+v\n", p)
  85. //}
  86. }
  87. return job, true
  88. }
  89. // Size returns the number of items in the queue
  90. func (q *BufferedQueue[T]) Size() int {
  91. q.mutex.Lock()
  92. defer q.mutex.Unlock()
  93. return q.count
  94. }
  95. // IsEmpty returns true if the queue is empty
  96. func (q *BufferedQueue[T]) IsEmpty() bool {
  97. return q.Size() == 0
  98. }