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.