From 0852bbc97939a8b583f1605a722072e2f494e21d Mon Sep 17 00:00:00 2001 From: Paulo Felipe Date: Wed, 5 May 2021 16:05:53 -0300 Subject: [PATCH 1/3] Add the solution for the Producer-consumer problem from section 4.1 --- .../chapter4/producer-consumer/README.md | 23 +++++++++ .../chapter4/producer-consumer/go/main.go | 49 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 LittleBookOfSemaphores/chapter4/producer-consumer/README.md create mode 100644 LittleBookOfSemaphores/chapter4/producer-consumer/go/main.go 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() + } +} From f57c5bc5c48f50cea1b0f736b9ecccea04caf183 Mon Sep 17 00:00:00 2001 From: Paulo Felipe Date: Mon, 17 May 2021 17:27:00 -0300 Subject: [PATCH 2/3] Add golang solution for the dining philosophers problem from section 4.3 --- .../chapter4/dining-philosophers/README.md | 22 +++++++ .../chapter4/dining-philosophers/go/fork.go | 44 +++++++++++++ .../chapter4/dining-philosophers/go/main.go | 39 +++++++++++ .../dining-philosophers/go/philosopher.go | 65 +++++++++++++++++++ .../dining-philosophers/go/turnstile.go | 26 ++++++++ 5 files changed, 196 insertions(+) create mode 100644 LittleBookOfSemaphores/chapter4/dining-philosophers/README.md create mode 100644 LittleBookOfSemaphores/chapter4/dining-philosophers/go/fork.go create mode 100644 LittleBookOfSemaphores/chapter4/dining-philosophers/go/main.go create mode 100644 LittleBookOfSemaphores/chapter4/dining-philosophers/go/philosopher.go create mode 100644 LittleBookOfSemaphores/chapter4/dining-philosophers/go/turnstile.go diff --git a/LittleBookOfSemaphores/chapter4/dining-philosophers/README.md b/LittleBookOfSemaphores/chapter4/dining-philosophers/README.md new file mode 100644 index 0000000..903046f --- /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 9530710 times +Philosopher (1) ate 8549688 times +Philosopher (2) ate 9122172 times +Philosopher (3) ate 8380622 times +Philosopher (4) ate 9466254 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..3e41020 --- /dev/null +++ b/LittleBookOfSemaphores/chapter4/dining-philosophers/go/fork.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" +) + +type Forks struct { + forks map[int]*Fork +} + +type Fork struct { + id int + ch chan bool +} + +func NewForks(amount int) *Forks { + fs := &Forks{forks: map[int]*Fork{}} + for i := 0; i < amount; i++ { + fid := i + ch := make(chan bool, 1) + ch <- true + fs.forks[fid] = &Fork{id: fid, ch: ch} + } + 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.ch + forks[i] = f + } + return forks, nil +} + +func (fs *Forks) ReleaseForks(forks []*Fork) { + for _, fork := range forks { + fork.ch <- true + } +} 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 +} From c429c9cfe10d5d80a817c57d6fdd802e294636e3 Mon Sep 17 00:00:00 2001 From: Paulo Felipe Date: Mon, 17 May 2021 17:41:40 -0300 Subject: [PATCH 3/3] Use mutex instead of channel for forks --- .../chapter4/dining-philosophers/README.md | 10 +++++----- .../chapter4/dining-philosophers/go/fork.go | 13 ++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/LittleBookOfSemaphores/chapter4/dining-philosophers/README.md b/LittleBookOfSemaphores/chapter4/dining-philosophers/README.md index 903046f..21a94f7 100644 --- a/LittleBookOfSemaphores/chapter4/dining-philosophers/README.md +++ b/LittleBookOfSemaphores/chapter4/dining-philosophers/README.md @@ -14,9 +14,9 @@ como *eat* e *thinking*. A versão da runtime utilizada foi golang v1.16.3. ### Results ``` shell -Philosopher (0) ate 9530710 times -Philosopher (1) ate 8549688 times -Philosopher (2) ate 9122172 times -Philosopher (3) ate 8380622 times -Philosopher (4) ate 9466254 times +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 index 3e41020..1fd9ec5 100644 --- a/LittleBookOfSemaphores/chapter4/dining-philosophers/go/fork.go +++ b/LittleBookOfSemaphores/chapter4/dining-philosophers/go/fork.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "sync" ) type Forks struct { @@ -9,17 +10,15 @@ type Forks struct { } type Fork struct { - id int - ch chan bool + id int + lock *sync.Mutex } func NewForks(amount int) *Forks { fs := &Forks{forks: map[int]*Fork{}} for i := 0; i < amount; i++ { fid := i - ch := make(chan bool, 1) - ch <- true - fs.forks[fid] = &Fork{id: fid, ch: ch} + fs.forks[fid] = &Fork{id: fid, lock: &sync.Mutex{}} } return fs } @@ -31,7 +30,7 @@ func (fs *Forks) GetForks(ids []*int) ([]*Fork, error) { if !ok { return nil, fmt.Errorf("unable to find fork with id (%d)", *id) } - <-f.ch + f.lock.Lock() forks[i] = f } return forks, nil @@ -39,6 +38,6 @@ func (fs *Forks) GetForks(ids []*int) ([]*Fork, error) { func (fs *Forks) ReleaseForks(forks []*Fork) { for _, fork := range forks { - fork.ch <- true + fork.lock.Unlock() } }