go-datastructures
go-datastructures copied to clipboard
RingBuffer: 100% cpu usage
$uname -a Linux Lee-Ubuntu 4.4.0-28-generic #47-Ubuntu SMP Fri Jun 24 10:09:13 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux $cat /etc/issue Linux Lee-Ubuntu 4.4.0-28-generic #47-Ubuntu SMP Fri Jun 24 10:09:13 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux $go env Linux Lee-Ubuntu 4.4.0-28-generic #47-Ubuntu SMP Fri Jun 24 10:09:13 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
Test code:
import (
"fmt"
"sync"
"time"
"github.com/Workiva/go-datastructures/queue"
)
var wg sync.WaitGroup
func main() {
wg.Add(2)
q := queue.NewRingBuffer(4096)
go func() { defer wg.Done() for { time.Sleep(3 * time.Second) q.Offer("hello world") } }()
go func() { defer wg.Done() for { str, _ := q.Get() fmt.Println(str) } }()
wg.Wait()
}
$go env GOARCH="amd64" GOBIN="" GOEXE="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOOS="linux" GOPATH="/home/sheldon/Gopath" GORACE="" GOROOT="/usr/local/go" GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64" GO15VENDOREXPERIMENT="1" CC="gcc" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0" CXX="g++" CGO_ENABLED="1"
$go version go version go1.6.2 linux/amd64
Im seeing the same. (Go 1.8)
package main
import (
"github.com/Workiva/go-datastructures/queue"
)
func main() {
rb := queue.NewRingBuffer(5)
rb.Get()
}
100% cpu usage for above
100% CPU
any news for this?
I need do this:
for rb.Len() == 0 { time.Sleep(50 * time.MillSecond) }
item, err := rb.Get()
another patch use chan notification, run better now
diff --git a/queue/ring.go b/queue/ring.go
index dfd0b53..8b40dc9 100644
--- a/queue/ring.go
+++ b/queue/ring.go
@@ -57,6 +57,7 @@ type RingBuffer struct {
_padding2 [8]uint64
mask, disposed uint64
_padding3 [8]uint64
+ condC chan bool
nodes nodes
}
@@ -67,6 +68,7 @@ func (rb *RingBuffer) init(size uint64) {
rb.nodes[i] = &node{position: i}
}
rb.mask = size - 1 // so we don't have to do this with every put/get operation
+ rb.condC = make(chan bool, 0)
}
// Put adds the provided item to the queue. If the queue is full, this
@@ -115,6 +117,12 @@ L:
n.data = item
atomic.StoreUint64(&n.position, pos+1)
+ if rb.Len() < 3 {
+ select {
+ case rb.condC <- true:
+ default:
+ }
+ }
return true, nil
}
@@ -146,6 +154,12 @@ L:
return nil, ErrDisposed
}
+ if rb.Len() == 0 {
+ select {
+ case <-rb.condC:
+ }
+ }
+
n = rb.nodes[pos&rb.mask]
seq := atomic.LoadUint64(&n.position)
switch dif := seq - (pos + 1); {
@@ -186,6 +200,10 @@ func (rb *RingBuffer) Cap() uint64 {
// queue will return an error.
func (rb *RingBuffer) Dispose() {
atomic.CompareAndSwapUint64(&rb.disposed, 0, 1)
+ select {
+ case rb.condC <- true:
+ default:
+ }
}
// IsDisposed will return a bool indicating if this queue has been
Get()
has 100% CPU usage because it uses busy waiting strategy when the buffer is empty.
Ideally we should add a new method which returns as soon as we find out the buffer is empty, and returns a boolean to allow users to decide what they want to do in this case (busy waiting, sleep, or something else). This is we do in Put()
and Offer()
.
For now users can workaround this issue by replacing Get()
with Poll(1)
, which returns (nil, ErrTimeout) after the buffer has been empty for 1ns.