Um dos problemas clássicos de coordenação mais conhecidos é o jantar dos filósofos, proposto inicialmente por Dijkstra [Raynal, 1986; Ben-Ari, 1990]. Neste problema, um grupo de cinco filósofos alternam suas vidas entre pensar e comer:
Cinco filósofos estão sentados em uma mesa redonda para janta, e os mesmos se alimentam e pensam durante o jantar. Cada filósofo tem um prato com macarrão à sua frente. Cada prato possui um garfo para pegar o macarrão. O macarrão está muito escorregadio e, para que um filósofo consiga comer, será necessário utilizar dois garfos.
Lembre-se que é apenas uma analogia. Nesse sentido, cada filósofo alterna entre duas tarefas: comer ou pensar. Quando um filósofo fica com fome, ele tenta pegar os garfos à sua esquerda e à sua direita; um de cada vez, independente da ordem. Caso ele consiga pegar dois garfos, ele come durante um determinado tempo e depois recoloca os garfos na mesa. Em seguida ele volta a pensar.
O problema do jantar dos filósofos é representativo de uma grande classe de problemas de sincronização entre vários processos e vários recursos sem usar um coordenador central. Resolver o problema do jantar dos filósofos consiste em encontrar uma forma de coordenar suas atividades de maneira que todos os filósofos consigam meditar e comer.
package main
import (
"hash/fnv"
"log"
"math/rand"
"os"
"sync"
"time"
)
// Number of philosophers is simply the length of this list.
var ph = []string{"Aristóteles", "Platão", "Sócrates", "Pitágoras", "Demócrito"}
const hunger = 3 // Numero de vezes que cada filósofo vai comer
const think = time.Second / 1000 // Tempo de pensamento
const eat = time.Second / 1000 // Tempo para comer
var fmt = log.New(os.Stdout, "", 0)
var dining sync.WaitGroup
func diningProblem(phName string, dominantHand, otherHand *sync.Mutex) {
fmt.Println(phName, "Sentado")
//time.Sleep(2* time.Second)
h := fnv.New64a()
h.Write([]byte(phName))
rg := rand.New(rand.NewSource(int64(h.Sum64())))
rSleep := func(t time.Duration) {
time.Sleep(t/2 + time.Duration(rg.Int63n(int64(t))))
}
for h := hunger; h > 0; h-- {
fmt.Println(phName, "Com fome")
dominantHand.Lock() // Pega o garfo
otherHand.Lock()
fmt.Println(phName, "Comendo")
rSleep(eat)
time.Sleep(2* time.Second)
dominantHand.Unlock() // Larga o garfo
otherHand.Unlock()
fmt.Println(phName, "Pensando")
rSleep(think)
}
fmt.Println(phName, "Satisfeito")
dining.Done()
fmt.Println(phName, "Sai da mesa")
}
func main() {
fmt.Println("Mesa vazia")
dining.Add(5)
fork0 := &sync.Mutex{}
forkLeft := fork0
for i := 1; i < len(ph); i++ {
forkRight := &sync.Mutex{}
go diningProblem(ph[i], forkLeft, forkRight)
forkLeft = forkRight
}
go diningProblem(ph[0], fork0, forkLeft)
dining.Wait() // Espera os filosofos terminarem
fmt.Println("Mesa vazia")
}
Para a realização do problema foi utilizada a linguagem Go (Golang da google), para isso foi necessario
utilizar duas bibliotecas importantes como a "os"
e "sync"
.
A biblioteca "os"
fornece uma interface independente da plataforma para a funcionalidade do
sistema operacional. O design é semelhante ao Unix, embora o tratamento de erros seja semelhante ao Go-like.
Já a biblioteca "sync"
fornece dados primitivos de sincronização, como bloqueios de exclusão
mútua (mutual exclusion locks). Além dos tipos Once e WaitGroup, a maioria é destinada a ser usada por
rotinas de biblioteca de baixo nível. A sincronização de nível superior é melhor feita através de canais e
comunicação.
No começo do código podemos ver a utilização a biblioteca "sync"
onde é utilizado o método
sync.WaitGroup
que é utilizada para chamar as funções Add, Done e Wait
. O
Wait()
serve para aguardar um bloco ou blocos até que o contador WaitGroup seja zero como visto
nas linhas 49 com Done e 65 com Wait, ou seja, senão tivessemos acrescido o valor de WaitGroup com o Add o
código nem executaria a função diningProblem()
. Os métodos Lock()
(linha 86) e Unlock()
(linha 90) serve para ele manter o estado atual sem que as proximas mudanças atrapalhem no valor recebido, ou seja, sem os métodos o programa poderia sobrepor valores antes das modificações anteriores, por exemplo, ele bloqueia o valor de otherHand feito isso não haverá mudança de valor até que o método Unlock seja chamado.