diff --git a/LittleBookOfSemaphores/chapter4/dining-philosophers/README.md b/LittleBookOfSemaphores/chapter4/dining-philosophers/README.md new file mode 100644 index 0000000..21a94f7 --- /dev/null +++ b/LittleBookOfSemaphores/chapter4/dining-philosophers/README.md @@ -0,0 +1,22 @@ +# 4.4 Dining philosophers + +Each philosopher must alternately think and eat. However, a philosopher can only eat spaghetti when they have both left and right forks. Each fork can be held by only one philosopher and so a philosopher can use the fork only if it is not being used by another philosopher. + +## Statistics for nerds + +### Testing machine +Computador Intel(R) Core(TM) i7-8550U 1.80GHz de 8 núcleos, +com 16GB de memória RAM executando o Ubuntu 18.04.5 LTS com +o kernel Linux v4.15.0-142-generic-x86_64. + +O experimento executou durante 15 segundos, sem logs e sem espera por operações +como *eat* e *thinking*. A versão da runtime utilizada foi golang v1.16.3. + +### Results +``` shell +Philosopher (0) ate 14056089 times +Philosopher (1) ate 13725014 times +Philosopher (2) ate 13604874 times +Philosopher (3) ate 13437191 times +Philosopher (4) ate 13595025 times +``` \ No newline at end of file diff --git a/LittleBookOfSemaphores/chapter4/dining-philosophers/go/fork.go b/LittleBookOfSemaphores/chapter4/dining-philosophers/go/fork.go new file mode 100644 index 0000000..1fd9ec5 --- /dev/null +++ b/LittleBookOfSemaphores/chapter4/dining-philosophers/go/fork.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "sync" +) + +type Forks struct { + forks map[int]*Fork +} + +type Fork struct { + id int + lock *sync.Mutex +} + +func NewForks(amount int) *Forks { + fs := &Forks{forks: map[int]*Fork{}} + for i := 0; i < amount; i++ { + fid := i + fs.forks[fid] = &Fork{id: fid, lock: &sync.Mutex{}} + } + return fs +} + +func (fs *Forks) GetForks(ids []*int) ([]*Fork, error) { + forks := make([]*Fork, len(ids)) + for i, id := range ids { + f, ok := fs.forks[*id] + if !ok { + return nil, fmt.Errorf("unable to find fork with id (%d)", *id) + } + f.lock.Lock() + forks[i] = f + } + return forks, nil +} + +func (fs *Forks) ReleaseForks(forks []*Fork) { + for _, fork := range forks { + fork.lock.Unlock() + } +} diff --git a/LittleBookOfSemaphores/chapter4/dining-philosophers/go/main.go b/LittleBookOfSemaphores/chapter4/dining-philosophers/go/main.go new file mode 100644 index 0000000..463f6fd --- /dev/null +++ b/LittleBookOfSemaphores/chapter4/dining-philosophers/go/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "time" +) + +func main() { + ps := make([]*Philosopher, 5) + amount := len(ps) + for i := range ps { + ps[i] = &Philosopher{Id: i, Amount: amount, MaxSleepMs: 10, Counter: 0} + } + turnstile := NewTurnstile(amount - 1) + forks := NewForks(amount + 1) + + quit := make(chan bool, amount) + done := &sync.WaitGroup{} + done.Add(amount) + for _, p := range ps { + go p.Run(forks, turnstile, quit, done) + } + + time.Sleep(time.Duration(15) * time.Second) + for range ps { + quit <- true + } + done.Wait() + for _, p := range ps { + fmt.Printf("Philosopher (%d) ate %d times\n", p.Id, p.Counter) + } +} + +func RandomSleep(maxMs int) { + sleep := time.Duration(rand.Intn(maxMs)+1) * time.Millisecond + time.Sleep(sleep) +} diff --git a/LittleBookOfSemaphores/chapter4/dining-philosophers/go/philosopher.go b/LittleBookOfSemaphores/chapter4/dining-philosophers/go/philosopher.go new file mode 100644 index 0000000..2917401 --- /dev/null +++ b/LittleBookOfSemaphores/chapter4/dining-philosophers/go/philosopher.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "sync" +) + +type Philosopher struct { + Id int + Amount int + MaxSleepMs int + Counter int +} + +func (p *Philosopher) Run(forks *Forks, turnstile *Turnstile, quit chan bool, done *sync.WaitGroup) { + for { + select { + case <-quit: + done.Done() + return + default: + p.dinner(forks, turnstile) + } + } +} + +func (p *Philosopher) dinner(forks *Forks, turnstile *Turnstile) { + turnstile.Pass(p.Id) + + p.think() + fs, err := p.getForks(forks) + if err != nil { + panic(err) + } + p.eat() + p.Counter++ + p.releaseForks(forks, fs) + + turnstile.Return() +} + +func (p *Philosopher) think() { + fmt.Printf("Philosopher (%d) is thinking\n", p.Id) + RandomSleep(p.MaxSleepMs) +} + +func (p *Philosopher) getForks(forks *Forks) ([]*Fork, error) { + fstForkId := p.Id + secForkId := (p.Id + 1) % p.Amount + fmt.Printf("Philosopher (%d) is getting the forks\n", p.Id) + fs, err := forks.GetForks([]*int{&fstForkId, &secForkId}) + fmt.Printf("Philosopher (%d) got the forks\n", p.Id) + return fs, err +} + +func (p *Philosopher) eat() { + fmt.Printf("Philosopher (%d) is eating\n", p.Id) + RandomSleep(p.MaxSleepMs) +} + +func (p *Philosopher) releaseForks(forks *Forks, fs []*Fork) { + fmt.Printf("Philosopher (%d) is releasing the forks\n", p.Id) + forks.ReleaseForks(fs) + fmt.Printf("Philosopher (%d) released the forks\n", p.Id) +} diff --git a/LittleBookOfSemaphores/chapter4/dining-philosophers/go/turnstile.go b/LittleBookOfSemaphores/chapter4/dining-philosophers/go/turnstile.go new file mode 100644 index 0000000..99bb715 --- /dev/null +++ b/LittleBookOfSemaphores/chapter4/dining-philosophers/go/turnstile.go @@ -0,0 +1,26 @@ +package main + +import "fmt" + +type Turnstile struct { + tableSits int + sitsCh chan bool +} + +func NewTurnstile(sits int) *Turnstile { + ch := make(chan bool, sits) + for i := 0; i < sits; i++ { + ch <- true + } + return &Turnstile{tableSits: sits, sitsCh: ch} +} + +func (t *Turnstile) Pass(fid int) { + fmt.Printf("Philosopher (%d) going through the turnstile\n", fid) + <-t.sitsCh + fmt.Printf("Philosopher (%d) released from the turnstile\n", fid) +} + +func (t *Turnstile) Return() { + t.sitsCh <- true +} diff --git a/LittleBookOfSemaphores/chapter4/producer-consumer/README.md b/LittleBookOfSemaphores/chapter4/producer-consumer/README.md new file mode 100644 index 0000000..d538d16 --- /dev/null +++ b/LittleBookOfSemaphores/chapter4/producer-consumer/README.md @@ -0,0 +1,23 @@ +# 4.1 Producer-consumer problem + +In multithreaded programs there is often a division of labor between +threads. In one common pattern, some threads are producers and some +are consumers. Producers create items of some kind and add them to a +data structure; consumers remove the items and process them. + +### Basic producer code +Assume that producers perform the following operations over and over: +``` +event = waitForEvent() +buffer.add(event) +``` + +### Basic consumer code +Also, assume that consumers perform the following operations: +``` +event = buffer.get() +event.process() +``` + +Puzzle: Add synchronization statements to the producer and consumer code +to enforce the synchronization constraints. \ No newline at end of file diff --git a/LittleBookOfSemaphores/chapter4/producer-consumer/go/main.go b/LittleBookOfSemaphores/chapter4/producer-consumer/go/main.go new file mode 100644 index 0000000..085155a --- /dev/null +++ b/LittleBookOfSemaphores/chapter4/producer-consumer/go/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "math/rand" + "time" +) + +func main() { + count := 0 + genEvent := func() *Event { + sleep := time.Duration(rand.Intn(999) + 1) * time.Millisecond + time.Sleep(sleep) + count++ + fmt.Printf("Producing event %d\n", count) + return &Event{Id: count, Delay: sleep} + } + bufferSize := 5 + buffer := make(chan *Event, bufferSize) // The secret is here, it ensures all synchronization constraints of the problem. + + go Producer(buffer, genEvent) + go Consumer(buffer) + + time.Sleep(time.Duration(15) * time.Second) +} + +type Event struct { + Id int + Delay time.Duration +} + +func (e *Event) Process() { + time.Sleep(2 * e.Delay) // Event consumption is 2x slower than production. + fmt.Printf("Processing event %d\n", e.Id) +} + +func Producer(buffer chan *Event, waitForEvent func() *Event) { + for { + event := waitForEvent() + buffer <- event + } +} + +func Consumer(buffer chan *Event) { + for { + event := <-buffer + event.Process() + } +}