Browse Source

Add Remove() implementation, CList is doubly-linked

pull/1842/head
Jae Kwon 9 years ago
parent
commit
76189fde0d
1 changed files with 134 additions and 32 deletions
  1. +134
    -32
      clist.go

+ 134
- 32
clist.go View File

@ -2,6 +2,8 @@ package common
/*
The purpose of CList is to provide a goroutine-safe linked-list.
This list can be traversed concurrently by any number of goroutines.
However, removed CElements cannot be added back.
NOTE: Not all methods of container/list are (yet) implemented.
*/
@ -14,24 +16,88 @@ import (
// CElement is an element of a linked-list
// Traversal from a CElement are goroutine-safe.
type CElement struct {
next unsafe.Pointer
wg *sync.WaitGroup
Value interface{}
next unsafe.Pointer
nextWg *sync.WaitGroup
prev unsafe.Pointer
prevWg *sync.WaitGroup
removed uint32
Value interface{}
}
// Blocking implementation of Next().
// If return is nil, this element was removed from the list.
// May return nil iff CElement was tail and got removed.
func (e *CElement) NextWait() *CElement {
e.wg.Wait()
return e.Next()
for {
e.nextWg.Wait()
next := e.Next()
if next == nil {
if e.Removed() {
return nil
} else {
continue
}
} else {
return next
}
}
}
// Blocking implementation of Prev().
// May return nil iff CElement was head and got removed.
func (e *CElement) PrevWait() *CElement {
for {
e.prevWg.Wait()
prev := e.Prev()
if prev == nil {
if e.Removed() {
return nil
} else {
continue
}
} else {
return prev
}
}
}
// Nonblocking, may return nil if at the end.
func (e *CElement) Next() *CElement {
next := atomic.LoadPointer(&e.next)
if next == nil {
return nil
return (*CElement)(atomic.LoadPointer(&e.next))
}
// Nonblocking, may return nil if at the end.
func (e *CElement) Prev() *CElement {
return (*CElement)(atomic.LoadPointer(&e.prev))
}
func (e *CElement) Removed() bool {
return atomic.LoadUint32(&(e.removed)) > 0
}
func (e *CElement) setNextAtomic(next *CElement) {
oldNext := (*CElement)(atomic.LoadPointer(&e.next))
if next == nil && oldNext != nil {
e.nextWg.Add(1) // NOTE: There is still a race condition for waiters, so we for-loop.
}
atomic.StorePointer(&(e.next), unsafe.Pointer(next))
if next != nil && oldNext == nil {
e.nextWg.Done()
}
}
func (e *CElement) setPrevAtomic(prev *CElement) {
oldPrev := (*CElement)(atomic.LoadPointer(&e.prev))
if prev == nil && oldPrev != nil {
e.prevWg.Add(1) // NOTE: There is still a race condition for waiters, so we for-loop.
}
atomic.StorePointer(&(e.prev), unsafe.Pointer(prev))
if prev != nil && oldPrev == nil {
e.prevWg.Done()
}
return (*CElement)(next)
}
func (e *CElement) setRemovedAtomic() {
atomic.StoreUint32(&(e.removed), 1)
}
// CList represents a linked list.
@ -104,45 +170,81 @@ func (l *CList) BackWait() *CElement {
}
func (l *CList) PushBack(v interface{}) *CElement {
e := &CElement{
next: nil,
wg: waitGroup1(),
Value: v,
}
l.mtx.Lock()
defer l.mtx.Unlock()
// Construct a new element
e := &CElement{
prev: nil,
prevWg: waitGroup1(),
next: nil,
nextWg: waitGroup1(),
Value: v,
}
// Release waiters on FrontWait/BackWait maybe
if l.len == 0 {
l.wg.Done()
}
l.len += 1
// Modify the tail
if l.tail == nil {
l.head = e
l.tail = e
l.wg.Done()
return e
} else {
oldTail := l.tail
atomic.StorePointer(&oldTail.next, unsafe.Pointer(e))
l.tail.setNextAtomic(e)
e.setPrevAtomic(l.tail)
l.tail = e
oldTail.wg.Done()
return e
}
return e
}
func (l *CList) RemoveFront() interface{} {
// NOTE: As per the contract of CList, removed elements cannot be added back.
func (l *CList) Remove(e *CElement) interface{} {
l.mtx.Lock()
defer l.mtx.Unlock()
if l.head == nil {
return nil
prev := e.Prev()
next := e.Next()
if l.head == nil || l.tail == nil {
PanicSanity("Remove(e) on empty CList")
}
oldFront := l.head
next := (*CElement)(oldFront.next)
l.head = next
if next == nil {
l.tail = nil
l.wg = waitGroup1()
if prev == nil && l.head != e {
PanicSanity("Remove(e) with false head")
}
if next == nil && l.tail != e {
PanicSanity("Remove(e) with false tail")
}
// If we're removing the only item, make CList FrontWait/BackWait wait.
if l.len == 1 {
l.wg.Add(1)
}
l.len -= 1
atomic.StorePointer(&oldFront.next, unsafe.Pointer(nil))
return oldFront.Value
// Modify e.prev and e.next and connect
if prev != nil {
prev.setNextAtomic(next)
}
if next != nil {
next.setPrevAtomic(prev)
}
// Mark e as removed so NextWait/PrevWait can return.
e.setRemovedAtomic()
// Set .Done() on e, otherwise waiters will wait forever.
if prev == nil {
e.prevWg.Done()
}
if next == nil {
e.nextWg.Done()
}
return e.Value
}
func waitGroup1() (wg *sync.WaitGroup) {


Loading…
Cancel
Save