Skip to content

Instantly share code, notes, and snippets.

@Sharadh
Last active July 19, 2020 23:14
Show Gist options
  • Save Sharadh/09b1768983b3ad2bf4f36c3b336d5b67 to your computer and use it in GitHub Desktop.
Save Sharadh/09b1768983b3ad2bf4f36c3b336d5b67 to your computer and use it in GitHub Desktop.
Share Auto Simulation that leverages some go concurrency patterns for better batching
package main
import (
"fmt"
"math/rand"
"time"
)
const (
simulationDurationSeconds = 10
autoCapacity = 12
autoDispatchMaxMilliseconds = 10
maxPassengersWaiting = 5000
passengerMaxWaitDuration = time.Duration(50) * time.Millisecond
numPassengers = 5000
)
type bufferItem struct {
val *int
done chan<- bool
}
type bufferBatchItem []bufferItem
type sendResult string
// makePassenger makes a passenger at some random point
// during the simulation.
func makePassenger(val int, buffer chan<- bufferItem, result chan<- sendResult) {
time.Sleep(time.Duration(rand.Intn(simulationDurationSeconds)) * time.Second)
// Adding a buffer size of 1 makes the send non-blocking.
done := make(chan bool, 1)
buffer <- bufferItem{&val, done}
result <- "sent"
// Block until done; only exit on confirmation of buffering.
<-done
result <- "done"
}
// gatherPassengers waits for 10 passengers or a timeout
// and sends whoever it has at that point.
func gatherPassengers(buffer <-chan bufferItem) {
passengers := make([]bufferItem, autoCapacity)
var count uint
for i := 1; ; i++ {
// Wait for the first passenger.
count = 0
item := <-buffer
//fmt.Printf("Received first passenger: %d\n", p.val)
passengers[count] = item
count++
// Start the timer.
fmt.Printf("Will wait for 50ms before leaving...\n")
timeout := time.After(passengerMaxWaitDuration)
// Collect messages until batch complete, or timeout.
waitForPassengers:
for ; count < autoCapacity; count++ {
select {
case <-timeout:
fmt.Printf("Time to leave!\n")
break waitForPassengers
case item = <-buffer:
//fmt.Printf("Received another passenger: %d\n", p.val)
passengers[count] = item
}
}
arrived := make([]bufferItem, count)
copy(arrived, passengers[:count])
go dispatchAuto(i, arrived)
}
}
// dispatchAuto will put a batch of passengers into an auto and leave.
func dispatchAuto(autoId int, passengers bufferBatchItem) {
// Leave.
fmt.Printf("Honk Honk! Auto %d leaving with %d passengers.\n", autoId, len(passengers))
time.Sleep(time.Duration(rand.Intn(autoDispatchMaxMilliseconds)) * time.Millisecond)
// Notify.
for _, p := range passengers {
p.done <- true
}
}
// main runs the shareAuto simulation.
//
// The shareAuto concept is simple - the shareAuto (tuk-tuk) can hold
// up to 10 passengers (seated on park benches - very safe!) and drivers
// really want to do trips with as many passengers as possible.
// However, it's not comfortable waiting inside the auto (it's ok if
// it's moving though!) so passengers will leave if they done for too
// long.
//
// Our well-natured dispatcher promises that no passenger will ever done
// more than a set amount of time; at that point, the auto will leave
// regardless of how full it is. This, our dispatcher assures, is the way
// to the optimal blend of ₹₹₹ as well as Net Promoter Score (NPS).
func main() {
fmt.Printf("Starting %d sec shareAuto simulation for %d passengers...\n", simulationDurationSeconds, numPassengers)
rand.Seed(time.Now().Unix())
buffer := make(chan bufferItem, maxPassengersWaiting)
go gatherPassengers(buffer)
results := make(chan sendResult, numPassengers)
for i := 0; i < numPassengers; i++ {
go makePassenger(i, buffer, results)
}
var sentCount, doneCount uint = 0, 0
exitTimer := time.After(time.Duration(simulationDurationSeconds+1) * time.Second)
countUntilExit:
for {
select {
case r := <-results:
if r == "sent" {
sentCount++
} else if r == "done" {
doneCount++
}
case <-exitTimer:
fmt.Println("Time's up!")
break countUntilExit
}
}
fmt.Printf("Dispatched %d/%d passengers\n", doneCount, sentCount)
fmt.Println("Bye bye!")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment