Skip to content

Commit 0c6fa27

Browse files
committed
add: lock-free stack
1 parent c3d0b96 commit 0c6fa27

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

stack.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package lockfree
2+
3+
import (
4+
"sync/atomic"
5+
"unsafe"
6+
)
7+
8+
// Stack implements lock-free freelist based stack.
9+
type Stack struct {
10+
top unsafe.Pointer
11+
len uint64
12+
}
13+
14+
// NewStack creates a new lock-free queue.
15+
func NewStack() *Stack {
16+
return &Stack{}
17+
}
18+
19+
// Pop pops value from the top of the stack.
20+
func (s *Stack) Pop() interface{} {
21+
var top, next unsafe.Pointer
22+
var item *stackitem
23+
for {
24+
top = atomic.LoadPointer(&s.top)
25+
if top == nil {
26+
return nil
27+
}
28+
item = (*stackitem)(top)
29+
next = atomic.LoadPointer(&item.next)
30+
if atomic.CompareAndSwapPointer(&s.top, top, next) {
31+
atomic.AddUint64(&s.len, ^uint64(0))
32+
return item.v
33+
}
34+
}
35+
}
36+
37+
// Push pushes a value on top of the stack.
38+
func (s *Stack) Push(v interface{}) {
39+
item := stackitem{v: v}
40+
var top unsafe.Pointer
41+
for {
42+
top = atomic.LoadPointer(&s.top)
43+
item.next = top
44+
if atomic.CompareAndSwapPointer(&s.top, top, unsafe.Pointer(&item)) {
45+
atomic.AddUint64(&s.len, 1)
46+
return
47+
}
48+
}
49+
}
50+
51+
type stackitem struct {
52+
next unsafe.Pointer
53+
v interface{}
54+
}

stack_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package lockfree_test
2+
3+
import (
4+
"fmt"
5+
"math/rand"
6+
"sync"
7+
"sync/atomic"
8+
"testing"
9+
10+
"github.com/changkun/lockfree"
11+
)
12+
13+
func TestStackPopEmpty(t *testing.T) {
14+
s := lockfree.NewStack()
15+
if s.Pop() != nil {
16+
t.Fatal("pop empty stack returns non-nil")
17+
}
18+
}
19+
20+
func ExampleStack() {
21+
s := lockfree.NewStack()
22+
23+
s.Push(1)
24+
s.Push(2)
25+
s.Push(3)
26+
27+
fmt.Println(s.Pop())
28+
fmt.Println(s.Pop())
29+
fmt.Println(s.Pop())
30+
31+
// Output:
32+
// 3
33+
// 2
34+
// 1
35+
}
36+
37+
type stackInterface interface {
38+
Push(interface{})
39+
Pop() interface{}
40+
}
41+
42+
type mutexStack struct {
43+
v []interface{}
44+
mu sync.Mutex
45+
}
46+
47+
func newMutexStack() *mutexStack {
48+
return &mutexStack{v: make([]interface{}, 0)}
49+
}
50+
51+
func (s *mutexStack) Push(v interface{}) {
52+
s.mu.Lock()
53+
s.v = append(s.v, v)
54+
s.mu.Unlock()
55+
}
56+
57+
func (s *mutexStack) Pop() interface{} {
58+
s.mu.Lock()
59+
v := s.v[len(s.v)]
60+
s.v = s.v[:len(s.v)-1]
61+
s.mu.Unlock()
62+
return v
63+
}
64+
65+
func BenchmarkStack(b *testing.B) {
66+
length := 1 << 12
67+
inputs := make([]int, length)
68+
for i := 0; i < length; i++ {
69+
inputs = append(inputs, rand.Int())
70+
}
71+
s, ms := lockfree.NewStack(), newMutexStack()
72+
b.ResetTimer()
73+
for _, s := range [...]stackInterface{s, ms} {
74+
b.Run(fmt.Sprintf("%T", s), func(b *testing.B) {
75+
var c int64
76+
b.RunParallel(func(pb *testing.PB) {
77+
for pb.Next() {
78+
i := int(atomic.AddInt64(&c, 1)-1) % length
79+
v := inputs[i]
80+
if v >= 0 {
81+
s.Push(v)
82+
} else {
83+
s.Pop()
84+
}
85+
}
86+
})
87+
})
88+
}
89+
}

0 commit comments

Comments
 (0)