Add all the A tasks

This commit is contained in:
Ingy döt Net 2013-04-10 14:58:50 -07:00
parent 2dd7375f96
commit 051504d65b
1608 changed files with 18584 additions and 0 deletions

View file

@ -0,0 +1,106 @@
package main
import (
"fmt"
"math/rand"
"time"
)
// Data type required by task.
type bucketList interface {
// Two operations required by task. Updater parameter not specified
// by task, but useful for displaying update counts as an indication
// that transfer operations are happening "as often as possible."
bucketValue(bucket int) int
transfer(b1, b2, ammount, updater int)
// Operation not specified by task, but needed for synchronization.
snapshot(bucketValues []int, transferCounts []int)
// Operation not specified by task, but useful.
buckets() int // number of buckets
}
// Total of all bucket values, declared as a const to demonstrate that
// it doesn't change.
const originalTotal = 1000
// Updater ids, used for maintaining transfer counts.
const (
idOrder = iota
idChaos
nUpdaters
)
func main() {
// Create a concrete object implementing the bucketList interface.
bl := newChList(10, originalTotal, nUpdaters)
// Three concurrent tasks.
go order(bl)
go chaos(bl)
buddha(bl)
}
// The concurrent tasks exercise the data operations by going through
// the bucketList interface. They do no explicit synchronization and
// are not responsible for maintaining invariants.
// Exercise (1.) required by task: make values more equal.
func order(bl bucketList) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
nBuckets := bl.buckets()
for {
b1 := r.Intn(nBuckets)
b2 := r.Intn(nBuckets)
v1 := bl.bucketValue(b1)
v2 := bl.bucketValue(b2)
if v1 > v2 {
bl.transfer(b1, b2, (v1-v2)/2, idOrder)
} else {
bl.transfer(b2, b1, (v2-v1)/2, idOrder)
}
}
}
// Exercise (2.) required by task: redistribute values.
func chaos(bl bucketList) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
nBuckets := bl.buckets()
for {
b1 := r.Intn(nBuckets)
b2 := r.Intn(nBuckets)
bl.transfer(b1, b2, r.Intn(bl.bucketValue(b1)+1), idChaos)
}
}
// Exercise (3.) requred by task: display total.
func buddha(bl bucketList) {
nBuckets := bl.buckets()
s := make([]int, nBuckets)
tc := make([]int, nUpdaters)
var total, nTicks int
fmt.Println("sum ---updates--- mean buckets")
tr := time.Tick(time.Second / 10)
for {
var sum int
<-tr
bl.snapshot(s, tc)
for _, l := range s {
if l < 0 {
panic("sob") // invariant not preserved
}
sum += l
}
// Output number of updates per tick and cummulative mean
// updates per tick to demonstrate "as often as possible"
// of task exercises 1 and 2.
total += tc[0] + tc[1]
nTicks++
fmt.Printf("%d %6d %6d %7d %v\n", sum, tc[0], tc[1], total/nTicks, s)
if sum != originalTotal {
panic("weep") // invariant not preserved
}
}
}

View file

@ -0,0 +1,78 @@
// chList (ch for channel-synchronized) is a concrete type implementing
// the bucketList interface. The bucketList interface declared methods,
// the struct type here declares data. chList methods are repsonsible
// for synchronization so they are goroutine-safe. They are also
// responsible for maintaining the invariants that the sum of all buckets
// stays constant and that no bucket value goes negative.
type chList struct {
b []int // bucket data specified by task
s chan bool // syncronization object
tc []int // a transfer count for each updater
}
// Constructor.
func newChList(nBuckets, initialSum, nUpdaters int) *chList {
bl := &chList{
b: make([]int, nBuckets),
s: make(chan bool, 1),
tc: make([]int, nUpdaters),
}
// Distribute initialSum across buckets.
for i, dist := nBuckets, initialSum; i > 0; {
v := dist / i
i--
bl.b[i] = v
dist -= v
}
// Synchronization is needed to maintain the invariant that the total
// of all bucket values stays the same. This is an implementation of
// the straightforward solution mentioned in the task description,
// ensuring that only one transfer happens at a time. Channel s
// holds a token. All methods must take the token from the channel
// before accessing data and then return the token when they are done.
// it is equivalent to a mutex. The constructor makes data available
// by initially dropping the token in the channel after all data is
// initialized.
bl.s <- true
return bl
}
// Four methods implementing the bucketList interface.
func (bl *chList) bucketValue(b int) int {
<-bl.s // get token before accessing data
r := bl.b[b]
bl.s <- true // return token
return r
}
func (bl *chList) transfer(b1, b2, a int, ux int) {
if b1 == b2 { // null operation
return
}
// Get access.
<-bl.s
// Clamping maintains invariant that bucket values remain nonnegative.
if a > bl.b[b1] {
a = bl.b[b1]
}
// Transfer.
bl.b[b1] -= a
bl.b[b2] += a
bl.tc[ux]++ // increment transfer count
// Release "lock".
bl.s <- true
}
func (bl *chList) snapshot(s []int, tc []int) {
<-bl.s
copy(s, bl.b)
copy(tc, bl.tc)
for i := range bl.tc {
bl.tc[i] = 0
}
bl.s <- true
}
func (bl *chList) buckets() int {
return len(bl.b)
}

View file

@ -0,0 +1,83 @@
// rwList, rw is for RWMutex-synchronized.
type rwList struct {
b []int // bucket data specified by task
// Syncronization objects.
m []sync.Mutex // mutex for each bucket
all sync.RWMutex // mutex for entire list, for snapshot operation
tc []int // a transfer count for each updater
}
// Constructor.
func newRwList(nBuckets, initialSum, nUpdaters int) *rwList {
bl := &rwList{
b: make([]int, nBuckets),
m: make([]sync.Mutex, nBuckets),
tc: make([]int, nUpdaters),
}
for i, dist := nBuckets, initialSum; i > 0; {
v := dist / i
i--
bl.b[i] = v
dist -= v
}
return bl
}
// Four methods implementing the bucketList interface.
func (bl *rwList) bucketValue(b int) int {
bl.m[b].Lock() // lock on bucket ensures read is atomic
r := bl.b[b]
bl.m[b].Unlock()
return r
}
func (bl *rwList) transfer(b1, b2, a int, ux int) {
if b1 == b2 { // null operation
return
}
// RLock on list allows other simultaneous transfers.
bl.all.RLock()
// Locking lowest bucket first prevents deadlock
// with multiple tasks working at the same time.
if b1 < b2 {
bl.m[b1].Lock()
bl.m[b2].Lock()
} else {
bl.m[b2].Lock()
bl.m[b1].Lock()
}
// clamp
if a > bl.b[b1] {
a = bl.b[b1]
}
// transfer
bl.b[b1] -= a
bl.b[b2] += a
bl.tc[ux]++ // increment transfer count
// release
bl.m[b1].Unlock()
bl.m[b2].Unlock()
bl.all.RUnlock()
// With current Go, the program can hang without a call to gosched here.
// It seems that functions in the sync package don't touch the scheduler,
// (which is good) but we need to touch it here to give the channel
// operations in buddha a chance to run. (The current Go scheduler
// is basically cooperative rather than preemptive.)
runtime.Gosched()
}
func (bl *rwList) snapshot(s []int, tc []int) {
bl.all.Lock() // RW lock on list prevents transfers during snap.
copy(s, bl.b)
copy(tc, bl.tc)
for i := range bl.tc {
bl.tc[i] = 0
}
bl.all.Unlock()
}
func (bl *rwList) buckets() int {
return len(bl.b)
}

View file

@ -0,0 +1,63 @@
// lf for lock-free
type lfList struct {
b []int32
sync.RWMutex
tc []int
}
// Constructor.
func newLfList(nBuckets, initialSum, nUpdaters int) *lfList {
bl := &lfList{
b: make([]int32, nBuckets),
tc: make([]int, nUpdaters),
}
for i, dist := int32(nBuckets), int32(initialSum); i > 0; {
v := dist / i
i--
bl.b[i] = v
dist -= v
}
return bl
}
// Four methods implementing the bucketList interface.
func (bl *lfList) bucketValue(b int) int {
return int(atomic.LoadInt32(&bl.b[b]))
}
func (bl *lfList) transfer(b1, b2, a int, ux int) {
if b1 == b2 {
return
}
bl.RLock()
for {
t := int32(a)
v1 := atomic.LoadInt32(&bl.b[b1])
if t > v1 {
t = v1
}
if atomic.CompareAndSwapInt32(&bl.b[b1], v1, v1-t) {
atomic.AddInt32(&bl.b[b2], t)
break
}
// else retry
}
bl.tc[ux]++
bl.RUnlock()
runtime.Gosched()
}
func (bl *lfList) snapshot(s []int, tc []int) {
bl.Lock()
for i, bv := range bl.b {
s[i] = int(bv)
}
for i := range bl.tc {
tc[i], bl.tc[i] = bl.tc[i], 0
}
bl.Unlock()
}
func (bl *lfList) buckets() int {
return len(bl.b)
}

View file

@ -0,0 +1,109 @@
// mnList (mn for monitor-synchronized) is a concrete type implementing
// the bucketList interface. The monitor is a goroutine, all communication
// with it is done through channels, which are the members of mnList.
// All data implementing the buckets is encapsulated in the monitor.
type mnList struct {
vrCh chan *valueReq
trCh chan *transferReq
srCh chan *snapReq
nbCh chan chan int
}
// Constructor makes channels and starts monitor.
func newMnList(nBuckets, initialSum, nUpdaters int) *mnList {
mn := &mnList{
make(chan *valueReq),
make(chan *transferReq),
make(chan *snapReq),
make(chan chan int),
}
go monitor(mn, nBuckets, initialSum, nUpdaters)
return mn
}
// Monitor goroutine ecapsulates data and enters a loop to handle requests.
// The loop handles one request at a time, thus serializing all access.
func monitor(mn *mnList, nBuckets, initialSum, nUpdaters int) {
// bucket representation
b := make([]int, nBuckets)
for i, dist := nBuckets, initialSum; i > 0; {
v := dist / i
i--
b[i] = v
dist -= v
}
// transfer count representation
count := make([]int, nUpdaters)
// monitor loop
for {
select {
// value request operation
case vr := <-mn.vrCh:
vr.resp <- b[vr.bucket]
// transfer operation
case tr := <-mn.trCh:
// clamp
if tr.amount > b[tr.from] {
tr.amount = b[tr.from]
}
// transfer
b[tr.from] -= tr.amount
b[tr.to] += tr.amount
count[tr.updaterId]++
// snap operation
case sr := <-mn.srCh:
copy(sr.bucketSnap, b)
copy(sr.countSnap, count)
for i := range count {
count[i] = 0
}
sr.resp <- true
// number of buckets
case nb := <-mn.nbCh:
nb <- nBuckets
}
}
}
type valueReq struct {
bucket int
resp chan int
}
func (mn *mnList) bucketValue(b int) int {
resp := make(chan int)
mn.vrCh <- &valueReq{b, resp}
return <-resp
}
type transferReq struct {
from, to int
amount int
updaterId int
}
func (mn *mnList) transfer(b1, b2, a, ux int) {
mn.trCh <- &transferReq{b1, b2, a, ux}
}
type snapReq struct {
bucketSnap []int
countSnap []int
resp chan bool
}
func (mn *mnList) snapshot(s []int, tc []int) {
resp := make(chan bool)
mn.srCh <- &snapReq{s, tc, resp}
<-resp
}
func (mn *mnList) buckets() int {
resp := make(chan int)
mn.nbCh <- resp
return <-resp
}