Why is this golang script giving me a deadlock ? + a few questions

Issue

I got this code from someone on github and I am trying to play around with it to understand concurrency.

package main

import (
    "bufio"
    "fmt"
    "os"
    "sync"
    "time"
)

var wg sync.WaitGroup

func sad(url string) string {
    fmt.Printf("gonna sleep a bit\n")
    time.Sleep(2 * time.Second)
    return url + " added stuff"
}

func main() {
    sc := bufio.NewScanner(os.Stdin)
    urls := make(chan string)
    results := make(chan string)

    for i := 0; i < 20; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for url := range urls {
                n := sad(url)
                results <- n
            }
        }()
    }

    for sc.Scan() {
        url := sc.Text()
        urls <- url
    }

    for result := range results {
        fmt.Printf("%s arrived\n", result)
    }

    wg.Wait()
    close(urls)
    close(results)
}

I have a few questions:

  1. Why does this code give me a deadlock?
  2. How does that for loop exist before the operation of taking in input from user does the go routines wait until anything is passes in the urls channel then start doing work? I don’t get this because it’s not sequential, like why is taking in input from user then putting every input in the urls channel then running the go routines is considered wrong?
  3. Inside the for loop I have another loop which is iterating over the urls channel, does each go routine deal with exactly one line of input? or does one go routine handle multiple lines at once? how does any of this work?
  4. Am i gathering the output correctly here?

Solution

Mostly you’re doing things correctly, but have things a little out of order. The for sc.Scan() loop will continue until Scanner is done, and the for result := range results loop will never run, thus no go routine (‘main’ in this case) will be able to receive from results. When running your example, I started the for result := range results loop before for sc.Scan() and also in its own go routine–otherwise for sc.Scan() will never be reached.

go func() {
    for result := range results {
        fmt.Printf("%s arrived\n", result)
    }
}()

for sc.Scan() {
    url := sc.Text()
    urls <- url
}

Also, because you run wg.Wait() before close(urls), the main goroutine is left blocked waiting for the 20 sad() go routines to finish. But they can’t finish until close(urls) is called. So just close that channel before waiting for the waitgroup.

close(urls)
wg.Wait()
close(results)

Answered By – Benny Jobigan

Answer Checked By – Dawn Plyler (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.