Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[After 4.1] Add golang solution for the dining philosophers problem from section 4.3 #21

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions LittleBookOfSemaphores/chapter4/dining-philosophers/README.md
Original file line number Diff line number Diff line change
@@ -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
```
43 changes: 43 additions & 0 deletions LittleBookOfSemaphores/chapter4/dining-philosophers/go/fork.go
Original file line number Diff line number Diff line change
@@ -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()
}
}
39 changes: 39 additions & 0 deletions LittleBookOfSemaphores/chapter4/dining-philosophers/go/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions LittleBookOfSemaphores/chapter4/producer-consumer/README.md
Original file line number Diff line number Diff line change
@@ -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.
49 changes: 49 additions & 0 deletions LittleBookOfSemaphores/chapter4/producer-consumer/go/main.go
Original file line number Diff line number Diff line change
@@ -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()
}
}