Fix #112 by using RWMutex per element

This commit is contained in:
Jae Kwon 2017-12-25 21:12:14 -08:00
parent 218acc2224
commit 2fd8f35b74

View File

@ -11,36 +11,38 @@ to ensure garbage collection of removed elements.
import ( import (
"sync" "sync"
"sync/atomic"
"unsafe"
) )
// CElement is an element of a linked-list // CElement is an element of a linked-list
// Traversal from a CElement are goroutine-safe. // Traversal from a CElement are goroutine-safe.
type CElement struct { type CElement struct {
prev unsafe.Pointer mtx sync.RWMutex
prev *CElement
prevWg *sync.WaitGroup prevWg *sync.WaitGroup
next unsafe.Pointer next *CElement
nextWg *sync.WaitGroup nextWg *sync.WaitGroup
removed uint32 removed bool
Value interface{}
Value interface{} // immutable
} }
// Blocking implementation of Next(). // Blocking implementation of Next().
// May return nil iff CElement was tail and got removed. // May return nil iff CElement was tail and got removed.
func (e *CElement) NextWait() *CElement { func (e *CElement) NextWait() *CElement {
for { for {
e.nextWg.Wait() e.mtx.RLock()
next := e.Next() next := e.next
if next == nil { nextWg := e.nextWg
if e.Removed() { removed := e.removed
return nil e.mtx.RUnlock()
} else {
continue if next != nil || removed {
}
} else {
return next return next
} }
nextWg.Wait()
// e.next doesn't necessarily exist here.
// That's why we need to continue a for-loop.
} }
} }
@ -48,82 +50,113 @@ func (e *CElement) NextWait() *CElement {
// May return nil iff CElement was head and got removed. // May return nil iff CElement was head and got removed.
func (e *CElement) PrevWait() *CElement { func (e *CElement) PrevWait() *CElement {
for { for {
e.prevWg.Wait() e.mtx.RLock()
prev := e.Prev() prev := e.prev
if prev == nil { prevWg := e.prevWg
if e.Removed() { removed := e.removed
return nil e.mtx.RUnlock()
} else {
continue if prev != nil || removed {
}
} else {
return prev return prev
} }
prevWg.Wait()
} }
} }
// Nonblocking, may return nil if at the end. // Nonblocking, may return nil if at the end.
func (e *CElement) Next() *CElement { func (e *CElement) Next() *CElement {
return (*CElement)(atomic.LoadPointer(&e.next)) e.mtx.RLock()
defer e.mtx.RUnlock()
return e.next
} }
// Nonblocking, may return nil if at the end. // Nonblocking, may return nil if at the end.
func (e *CElement) Prev() *CElement { func (e *CElement) Prev() *CElement {
return (*CElement)(atomic.LoadPointer(&e.prev)) e.mtx.RLock()
defer e.mtx.RUnlock()
return e.prev
} }
func (e *CElement) Removed() bool { func (e *CElement) Removed() bool {
return atomic.LoadUint32(&(e.removed)) > 0 e.mtx.RLock()
defer e.mtx.RUnlock()
return e.removed
} }
func (e *CElement) DetachNext() { func (e *CElement) DetachNext() {
if !e.Removed() { if !e.Removed() {
panic("DetachNext() must be called after Remove(e)") panic("DetachNext() must be called after Remove(e)")
} }
atomic.StorePointer(&e.next, nil) e.mtx.Lock()
defer e.mtx.Unlock()
e.next = nil
} }
func (e *CElement) DetachPrev() { func (e *CElement) DetachPrev() {
if !e.Removed() { if !e.Removed() {
panic("DetachPrev() must be called after Remove(e)") panic("DetachPrev() must be called after Remove(e)")
} }
atomic.StorePointer(&e.prev, nil) e.mtx.Lock()
defer e.mtx.Unlock()
e.prev = nil
} }
func (e *CElement) setNextAtomic(next *CElement) { // NOTE: This function needs to be safe for
for { // concurrent goroutines waiting on nextWg.
oldNext := atomic.LoadPointer(&e.next) func (e *CElement) SetNext(newNext *CElement) {
if !atomic.CompareAndSwapPointer(&(e.next), oldNext, unsafe.Pointer(next)) { e.mtx.Lock()
continue defer e.mtx.Unlock()
}
if next == nil && oldNext != nil { // We for-loop in NextWait() so race is ok oldNext := e.next
e.nextWg.Add(1) e.next = newNext
} if oldNext != nil && newNext == nil {
if next != nil && oldNext == nil { // See https://golang.org/pkg/sync/:
e.nextWg.Done() //
} // If a WaitGroup is reused to wait for several independent sets of
return // events, new Add calls must happen after all previous Wait calls have
// returned.
e.nextWg = waitGroup1() // WaitGroups are difficult to re-use.
}
if oldNext == nil && newNext != nil {
e.nextWg.Done()
} }
} }
func (e *CElement) setPrevAtomic(prev *CElement) { // NOTE: This function needs to be safe for
for { // concurrent goroutines waiting on prevWg
oldPrev := atomic.LoadPointer(&e.prev) func (e *CElement) SetPrev(newPrev *CElement) {
if !atomic.CompareAndSwapPointer(&(e.prev), oldPrev, unsafe.Pointer(prev)) { e.mtx.Lock()
continue defer e.mtx.Unlock()
}
if prev == nil && oldPrev != nil { // We for-loop in PrevWait() so race is ok oldPrev := e.prev
e.prevWg.Add(1) e.prev = newPrev
} if oldPrev != nil && newPrev == nil {
if prev != nil && oldPrev == nil { e.prevWg = waitGroup1() // WaitGroups are difficult to re-use.
e.prevWg.Done() }
} if oldPrev == nil && newPrev != nil {
return e.prevWg.Done()
} }
} }
func (e *CElement) setRemovedAtomic() { func (e *CElement) SetRemoved() {
atomic.StoreUint32(&(e.removed), 1) e.mtx.Lock()
defer e.mtx.Unlock()
e.removed = true
// This wakes up anyone waiting in either direction.
if e.prev == nil {
e.prevWg.Done()
}
if e.next == nil {
e.nextWg.Done()
}
} }
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
@ -132,7 +165,7 @@ func (e *CElement) setRemovedAtomic() {
// The zero value for CList is an empty list ready to use. // The zero value for CList is an empty list ready to use.
// Operations are goroutine-safe. // Operations are goroutine-safe.
type CList struct { type CList struct {
mtx sync.Mutex mtx sync.RWMutex
wg *sync.WaitGroup wg *sync.WaitGroup
head *CElement // first element head *CElement // first element
tail *CElement // last element tail *CElement // last element
@ -142,6 +175,7 @@ type CList struct {
func (l *CList) Init() *CList { func (l *CList) Init() *CList {
l.mtx.Lock() l.mtx.Lock()
defer l.mtx.Unlock() defer l.mtx.Unlock()
l.wg = waitGroup1() l.wg = waitGroup1()
l.head = nil l.head = nil
l.tail = nil l.tail = nil
@ -152,48 +186,55 @@ func (l *CList) Init() *CList {
func New() *CList { return new(CList).Init() } func New() *CList { return new(CList).Init() }
func (l *CList) Len() int { func (l *CList) Len() int {
l.mtx.Lock() l.mtx.RLock()
defer l.mtx.Unlock() defer l.mtx.RUnlock()
return l.len return l.len
} }
func (l *CList) Front() *CElement { func (l *CList) Front() *CElement {
l.mtx.Lock() l.mtx.RLock()
defer l.mtx.Unlock() defer l.mtx.RUnlock()
return l.head return l.head
} }
func (l *CList) FrontWait() *CElement { func (l *CList) FrontWait() *CElement {
for { for {
l.mtx.Lock() l.mtx.RLock()
head := l.head head := l.head
wg := l.wg wg := l.wg
l.mtx.Unlock() l.mtx.RUnlock()
if head == nil {
wg.Wait() if head != nil {
} else {
return head return head
} }
wg.Wait()
// l.head doesn't necessarily exist here.
// That's why we need to continue a for-loop.
} }
} }
func (l *CList) Back() *CElement { func (l *CList) Back() *CElement {
l.mtx.Lock() l.mtx.RLock()
defer l.mtx.Unlock() defer l.mtx.RUnlock()
return l.tail return l.tail
} }
func (l *CList) BackWait() *CElement { func (l *CList) BackWait() *CElement {
for { for {
l.mtx.Lock() l.mtx.RLock()
tail := l.tail tail := l.tail
wg := l.wg wg := l.wg
l.mtx.Unlock() l.mtx.RUnlock()
if tail == nil {
wg.Wait() if tail != nil {
} else {
return tail return tail
} }
wg.Wait()
// l.tail doesn't necessarily exist here.
// That's why we need to continue a for-loop.
} }
} }
@ -203,11 +244,12 @@ func (l *CList) PushBack(v interface{}) *CElement {
// Construct a new element // Construct a new element
e := &CElement{ e := &CElement{
prev: nil, prev: nil,
prevWg: waitGroup1(), prevWg: waitGroup1(),
next: nil, next: nil,
nextWg: waitGroup1(), nextWg: waitGroup1(),
Value: v, removed: false,
Value: v,
} }
// Release waiters on FrontWait/BackWait maybe // Release waiters on FrontWait/BackWait maybe
@ -221,9 +263,9 @@ func (l *CList) PushBack(v interface{}) *CElement {
l.head = e l.head = e
l.tail = e l.tail = e
} else { } else {
l.tail.setNextAtomic(e) e.SetPrev(l.tail) // We must init e first.
e.setPrevAtomic(l.tail) l.tail.SetNext(e) // This will make e accessible.
l.tail = e l.tail = e // Update the list.
} }
return e return e
@ -250,30 +292,26 @@ func (l *CList) Remove(e *CElement) interface{} {
// If we're removing the only item, make CList FrontWait/BackWait wait. // If we're removing the only item, make CList FrontWait/BackWait wait.
if l.len == 1 { if l.len == 1 {
l.wg.Add(1) l.wg = waitGroup1() // WaitGroups are difficult to re-use.
} }
// Update l.len
l.len -= 1 l.len -= 1
// Connect next/prev and set head/tail // Connect next/prev and set head/tail
if prev == nil { if prev == nil {
l.head = next l.head = next
} else { } else {
prev.setNextAtomic(next) prev.SetNext(next)
} }
if next == nil { if next == nil {
l.tail = prev l.tail = prev
} else { } else {
next.setPrevAtomic(prev) next.SetPrev(prev)
} }
// Set .Done() on e, otherwise waiters will wait forever. // Set .Done() on e, otherwise waiters will wait forever.
e.setRemovedAtomic() e.SetRemoved()
if prev == nil {
e.prevWg.Done()
}
if next == nil {
e.nextWg.Done()
}
return e.Value return e.Value
} }