Skip to content

Commit 10c111b

Browse files
authored
chore(queue): replace channel queue with ring buffer queue. (#99)
1 parent 59d35a6 commit 10c111b

File tree

2 files changed

+93
-24
lines changed

2 files changed

+93
-24
lines changed

benchmark_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,43 @@ import (
99
"github.com/golang-queue/queue/job"
1010
)
1111

12+
var count = 1
13+
14+
type testqueue interface {
15+
Queue(task core.QueuedMessage) error
16+
Request() (core.QueuedMessage, error)
17+
}
18+
19+
func testQueue(b *testing.B, pool testqueue) {
20+
message := job.NewTask(func(context.Context) error {
21+
return nil
22+
},
23+
job.AllowOption{
24+
RetryCount: job.Int64(100),
25+
RetryDelay: job.Time(30 * time.Millisecond),
26+
Timeout: job.Time(3 * time.Millisecond),
27+
},
28+
)
29+
30+
b.ReportAllocs()
31+
b.ResetTimer()
32+
for n := 0; n < b.N; n++ {
33+
for i := 0; i < count; i++ {
34+
_ = pool.Queue(message)
35+
_, _ = pool.Request()
36+
}
37+
}
38+
}
39+
40+
func BenchmarkNewCusumer(b *testing.B) {
41+
pool := NewConsumer(
42+
WithQueueSize(b.N*count),
43+
WithLogger(emptyLogger{}),
44+
)
45+
46+
testQueue(b, pool)
47+
}
48+
1249
func BenchmarkQueueTask(b *testing.B) {
1350
b.ReportAllocs()
1451
q := NewPool(5, WithLogger(emptyLogger{}))

consumer.go

+56-24
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ var errMaxCapacity = errors.New("max capacity reached")
1515

1616
// Consumer for simple queue using buffer channel
1717
type Consumer struct {
18-
taskQueue chan core.QueuedMessage
18+
sync.Mutex
19+
taskQueue []core.QueuedMessage
1920
runFunc func(context.Context, core.QueuedMessage) error
20-
stop chan struct{}
21+
capacity int
22+
count int
23+
head int
24+
tail int
2125
exit chan struct{}
2226
logger Logger
2327
stopOnce sync.Once
@@ -36,52 +40,80 @@ func (s *Consumer) Shutdown() error {
3640
}
3741

3842
s.stopOnce.Do(func() {
39-
close(s.stop)
40-
close(s.taskQueue)
41-
if len(s.taskQueue) > 0 {
43+
if s.count > 0 {
4244
<-s.exit
4345
}
4446
})
4547
return nil
4648
}
4749

4850
// Queue send task to the buffer channel
49-
func (s *Consumer) Queue(task core.QueuedMessage) error {
51+
func (s *Consumer) Queue(task core.QueuedMessage) error { //nolint:stylecheck
5052
if atomic.LoadInt32(&s.stopFlag) == 1 {
5153
return ErrQueueShutdown
5254
}
53-
54-
select {
55-
case s.taskQueue <- task:
56-
return nil
57-
default:
55+
if s.count >= s.capacity {
5856
return errMaxCapacity
5957
}
58+
59+
s.Lock()
60+
if s.count == len(s.taskQueue) {
61+
s.resize(s.count * 2)
62+
}
63+
s.taskQueue[s.tail] = task
64+
s.tail = (s.tail + 1) % len(s.taskQueue)
65+
s.count++
66+
s.Unlock()
67+
68+
return nil
6069
}
6170

6271
// Request a new task from channel
6372
func (s *Consumer) Request() (core.QueuedMessage, error) {
64-
select {
65-
case task, ok := <-s.taskQueue:
66-
if !ok {
67-
select {
68-
case s.exit <- struct{}{}:
69-
default:
70-
}
71-
return nil, ErrQueueHasBeenClosed
73+
if atomic.LoadInt32(&s.stopFlag) == 1 && s.count == 0 {
74+
select {
75+
case s.exit <- struct{}{}:
76+
default:
7277
}
73-
return task, nil
74-
default:
78+
return nil, ErrQueueHasBeenClosed
79+
}
80+
81+
if s.count == 0 {
7582
return nil, ErrNoTaskInQueue
7683
}
84+
s.Lock()
85+
data := s.taskQueue[s.head]
86+
s.head = (s.head + 1) % len(s.taskQueue)
87+
s.count--
88+
89+
if n := len(s.taskQueue) / 2; n > 2 && s.count <= n {
90+
s.resize(n)
91+
}
92+
s.Unlock()
93+
94+
return data, nil
95+
}
96+
97+
func (q *Consumer) resize(n int) {
98+
nodes := make([]core.QueuedMessage, n)
99+
if q.head < q.tail {
100+
copy(nodes, q.taskQueue[q.head:q.tail])
101+
} else {
102+
copy(nodes, q.taskQueue[q.head:])
103+
copy(nodes[len(q.taskQueue)-q.head:], q.taskQueue[:q.tail])
104+
}
105+
106+
q.tail = q.count % n
107+
q.head = 0
108+
q.taskQueue = nodes
77109
}
78110

79-
// NewConsumer for create new consumer instance
111+
// NewConsumer for create new Consumer instance
80112
func NewConsumer(opts ...Option) *Consumer {
81113
o := NewOptions(opts...)
82114
w := &Consumer{
83-
taskQueue: make(chan core.QueuedMessage, o.queueSize),
84-
stop: make(chan struct{}),
115+
taskQueue: make([]core.QueuedMessage, 2),
116+
capacity: o.queueSize,
85117
exit: make(chan struct{}),
86118
logger: o.logger,
87119
runFunc: o.fn,

0 commit comments

Comments
 (0)