Skip to content

Instantly share code, notes, and snippets.

@Charlyzzz
Last active May 28, 2021 19:02
Show Gist options
  • Save Charlyzzz/605a4052142d3ff85b618235c2b5acea to your computer and use it in GitHub Desktop.
Save Charlyzzz/605a4052142d3ff85b618235c2b5acea to your computer and use it in GitHub Desktop.
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.*
fun main() {
runBlocking {
table(5)
}
}
sealed interface TableCommand
data class WantChopsticks(val philosopher: Int) : TableCommand
data class DoneEating(val philosopher: Int) : TableCommand
fun CoroutineScope.table(nPhilosophers: Int) = launch {
val table = Channel<TableCommand>(nPhilosophers)
val seed = mutableListOf<Philosopher>()
val philosophers = (0 until nPhilosophers).fold(seed) { philosophers, i ->
philosophers.add(philosopher(i, table))
philosophers
}
philosophers.forEachIndexed { philosopher, _ -> table.send(WantChopsticks(philosopher)) }
val chopsticks = Array(nPhilosophers) { true }
val stats = stats(nPhilosophers)
val waiting: Queue<WantChopsticks> = LinkedList()
table.consumeEach {
when (it) {
is WantChopsticks -> {
val acquired = tryAcquire(it.philosopher, chopsticks)
if (acquired) {
philosophers[it.philosopher].send(ChopsticksReady)
} else {
waiting.add(it)
}
}
is DoneEating -> {
release(it.philosopher, chopsticks)
stats.send(Track(it.philosopher))
waiting.add(WantChopsticks(it.philosopher))
val p = waiting.poll()
table.send(p)
}
}
}
}
fun release(philosopher: Int, chopsticks: Array<Boolean>) {
val leftChopstick = leftChopstickIndex(philosopher)
val rightChopstick = rightChopstickIndex(philosopher, chopsticks)
chopsticks[leftChopstick] = true
chopsticks[rightChopstick] = true
}
fun tryAcquire(philosopher: Int, chopsticks: Array<Boolean>): Boolean {
val leftChopstick = leftChopstickIndex(philosopher)
val rightChopstick = rightChopstickIndex(philosopher, chopsticks)
val chopsticksAvailable = chopsticks[leftChopstick] && chopsticks[rightChopstick]
if (chopsticksAvailable) {
chopsticks[leftChopstick] = false
chopsticks[rightChopstick] = false
}
return chopsticksAvailable
}
object ChopsticksReady
typealias Philosopher = SendChannel<ChopsticksReady>
fun CoroutineScope.philosopher(id: Int, table: SendChannel<DoneEating>): Philosopher {
val doneEating = DoneEating(id)
val philosopher = Channel<ChopsticksReady>()
launch {
philosopher.consumeEach {
delay(10)
table.send(doneEating)
}
}
return philosopher
}
private fun leftChopstickIndex(philosopher: Int) = philosopher
private fun rightChopstickIndex(philosopher: Int, chopsticks: Array<Boolean>): Int {
return if (philosopher == chopsticks.size - 1) {
0
} else {
philosopher + 1
}
}
sealed interface StatsCommands
object PrintTimer : StatsCommands
data class Track(val philosopher: Int) : StatsCommands
fun CoroutineScope.stats(nPhilosophers: Int): SendChannel<Track> {
val stats = Array(nPhilosophers) { 0 }
val statsChannel = Channel<StatsCommands>()
launch {
while (true) {
delay(1000)
statsChannel.send(PrintTimer)
}
}
launch {
statsChannel.consumeEach {
when (it) {
is PrintTimer ->
println(stats.joinToString(", "))
is Track ->
stats[it.philosopher] += 1
}
}
}
return statsChannel
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment