Search code examples
godeadlockgoroutine

Race simulation fatal error: all goroutines are asleep - deadlock


I'm building a care simulation in Golang and I have bumped into a deadlock issue. Goroutines are used to represent each racer. The idea is that once a racer has reached the goal, a channel is used to communicate which racer has won. Can anyone spot my error?

package main

import (
    "fmt"
    "math/rand"
    "strconv"
    "sync"
    "time"
)

var wg sync.WaitGroup
var counter Counter
var tracks Tracks
var winner chan int

func newCounter(size int) Counter {
    return Counter{racers: make([]int, size+1)} // not using the zero'th index
}

func newTracks(size int) Tracks {
    return Tracks{racers: make([]string, size+1)}
}

func incrementCounter(n int) {
    counter.racers[n]++
}

func equalCounter() bool {
    rv := true
    for i, _ := range counter.racers {
        if counter.racers[0] != counter.racers[i] {
            rv = false
            break
        }
    }
    return rv
}

func smallerCounter(racerNumber int) bool {
    var rv bool
    for v := range counter.racers {
        if counter.racers[racerNumber] < v {
            rv = true
        } else {
            rv = false
        }
    }

    return rv
}

func checkCounter(racerNumber int) bool {
    var rv bool
    if equalCounter() || smallerCounter(racerNumber) {
        rv = true
    } else {
        rv = false
    }

    return rv
}

func racer(racerNum int) {
    steps := rand.Intn(5) + 1
    distance := 0
    goal := 100
    if checkCounter(racerNum) {
        for j := distance; distance < goal; j++ {
            for i := 0; i < steps; i++ {
                tracks.racers[racerNum] += "x"
            }
            distance += steps
            duration := rand.Int31n(200)
            time.Sleep(time.Duration(duration) * time.Millisecond)
            fmt.Println(tracks.racers[racerNum] + "[" + strconv.Itoa(racerNum) + "]")
        }

        incrementCounter(racerNum)
    }

    if distance >= goal {
        winner <- racerNum
        close(winner)
    }

    wg.Done()
}

func main() {
    racers := 5
    counter = newCounter(racers)
    tracks = newTracks(racers)

    for i := 1; i <= racers; i++ {
        wg.Add(1)
        go racer(i)
    }

    wg.Wait()

    fmt.Print("Winner is:")
    fmt.Print(<-winner)
}


type Counter struct {
    racers []int
}

type Tracks struct {
    racers []string
}

The code is also running here:

https://play.golang.org/p/Skr5Es3ItFQ


Solution

  • Deadlock because:

    • results channel is unbuffered, so multiple writes will block, and thus wg.Done() never runs
    • nothing is reading from results channel until all 5 wg.Done() run (chicken/egg problem)

    Quick fix, make results channel buffered and the size of all total writes:

    winner = make(chan int, racers)
    

    https://play.golang.org/p/sbHfOr9YS_z

    Channel will have all the runner-up results in the channel queue too.

    If you only care about the winner, there are more efficient ways to cater to just that scenario e.g. create a referee go-routine to watch for first write (winner) to results channel, then cancel the race context (each racer watches for context cancelation and will abandon their task once a single winner has been declared)