Search code examples
goconcurrencydeadlockgoroutine

How to avoid deadlock in printing alphanumeric numbers concurrently


I'm new to golang and I just wish to print out 10 alphanumeric numbers combining elements from numeber-range and character-range.

I decided to do it concurrently, but I've been running into an error regarding deadlock.

package main

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

type alphanumeric struct {
    anAlphabet string
    aNumber    string
}

func (someStruct alphanumeric) pairAlphanumeric() string {

    return someStruct.aNumber + someStruct.anAlphabet

}

func main() {

    var wg sync.WaitGroup

    numbers := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}
    alphabets := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}

    //var aleph alphanumeric
    //var alephS []alphanumeric

    wg.Add(len(alphabets))
    go func(numbers []string, alphabets []string) {
        defer wg.Done()
        for i := 0; i < 10; i++ {
            makeAleph(numbers, alphabets)
        }
    }(numbers, alphabets)

    wg.Wait()
} // end of main()

func makeAleph(numbers []string, alphabets []string) {

    var aleph alphanumeric

    aleph.anAlphabet = aNum(numbers)
    aleph.aNumber = anAlph(alphabets)

    fmt.Println(aleph.pairAlphanumeric())

    //return aleph.pairAlphanumeric()
}

func randomIndex() int {
    randTime := time.Time.UnixNano(time.Now())

    rand.Seed(randTime)

    return rand.Intn(10)
}

func aNum(numbers []string) string {

    return numbers[randomIndex()]

}

func anAlph(alphabets []string) string {

    return alphabets[randomIndex()]

}

And the error that it throws after printing the required numbers correctly is:

❯ go run aleph.go
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc42000e2dc)
    /Users/eklavya/.gvm/gos/go1.8/src/runtime/sema.go:47 +0x34
sync.(*WaitGroup).Wait(0xc42000e2d0)
    /Users/eklavya/.gvm/gos/go1.8/src/sync/waitgroup.go:131 +0x7a
main.main()
    /Users/eklavya/Projects/Polyglot/TedTalks/goTED/experiments/async/aleph.go:38 +0x14c

goroutine 5 [chan receive (nil chan)]:
main.makeAleph(0xc420084000, 0xa, 0xa, 0xc420001520, 0x1a, 0x1a)
    /Users/eklavya/Projects/Polyglot/TedTalks/goTED/experiments/async/aleph.go:61 +0x134
main.main.func1(0xc42000e2d0, 0xc420084000, 0xa, 0xa, 0xc420001520, 0x1a, 0x1a)
    /Users/eklavya/Projects/Polyglot/TedTalks/goTED/experiments/async/aleph.go:35 +0x94
created by main.main
    /Users/eklavya/Projects/Polyglot/TedTalks/goTED/experiments/async/aleph.go:37 +0x13e

goroutine 6 [chan send (nil chan)]:
main.aNum(0x0, 0xc420084000, 0xa, 0xa)
    /Users/eklavya/Projects/Polyglot/TedTalks/goTED/experiments/async/aleph.go:79 +0x5b
main.makeAleph.func1(0xc42000e2e0, 0x0, 0xc420084000, 0xa, 0xa)
    /Users/eklavya/Projects/Polyglot/TedTalks/goTED/experiments/async/aleph.go:51 +0x73
created by main.makeAleph
    /Users/eklavya/Projects/Polyglot/TedTalks/goTED/experiments/async/aleph.go:52 +0xad

goroutine 7 [chan send (nil chan)]:
main.anAlph(0x0, 0xc420001520, 0x1a, 0x1a)
    /Users/eklavya/Projects/Polyglot/TedTalks/goTED/experiments/async/aleph.go:85 +0x5b
main.makeAleph.func2(0xc42000e2e0, 0x0, 0xc420001520, 0x1a, 0x1a)
    /Users/eklavya/Projects/Polyglot/TedTalks/goTED/experiments/async/aleph.go:56 +0x73
created by main.makeAleph
    /Users/eklavya/Projects/Polyglot/TedTalks/goTED/experiments/async/aleph.go:57 +0xff
exit status 2

How can I avoid deadlock in printing alphanumeric numbers concurrently?


Solution

  • Your current code is not concurrent at all. All the alphanumeric codes are generated sequentially in a single for loop running in the sole goroutine you are creating besides main.

    You are adding len(alphabets) == 26 to wg.Wait. That means you need to call 26 wg.Done for the call to wg.Wait to complete. Each wg.Done call reduces the waitgroup counter by one.

    In your code, you are calling wg.Done only once. That means waitgroup counter stays at 25 once your goroutine returns and the call to wg.Wait would never return since no other goroutine is running that may reduce the waitgroup counter by making further calls to wg.Done.

    To (vaguely) get what you want, you can try something like this:

    // ...
    
    n := 10 // number of codes you want to print
    
    wg.Add(n)
    
    for i := 0; i < n; i++ {
      go func(numbers []string, alphabets []string) {
        defer wg.Done()
        makeAleph(numbers, alphabets)
      }(numbers, alphabets)
    
    wg.Wait()
    
    // ...
    

    Now, n goroutines will be spawned, each responsible for printing one code by calling makeAleph. As soon as a goroutine returns, wg.Done is called. A total n wg.Dones are called allowing call to wg.Wait in main to complete.