hey
hey

Reputation: 7869

why is this a deadlock in golang / waitgroup?

I'm not sure what I'm missing but I get a deadlock error. I'm using a buffered channel that I range over after all go routines complete. The channel has the capacity of 4 and I'm running 4 go routines so I'm expecting it to be "closed" automatically once it reaches the max capacity.

package main

import "fmt"
import "sync"

func main() {
    ch := make(chan []int, 4)
    var m []int

    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            ch <- m
            return
        }()
    }
    wg.Wait()

    for c := range ch {
        fmt.Printf("c is %v", c)
    }
}

Upvotes: 6

Views: 5673

Answers (4)

Para
Para

Reputation: 1387

@Denys Séguret is right

but follow is anohter Solution easier to fix it:

func main() {
    ch := make(chan []int, 4)
    var m []int

    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            ch <- m
            return
        }()
    }
    go func() {
        wg.Wait()
        close(ch)
    }()
    for c := range ch {
        fmt.Printf("c is %v", c)
    }
}

form https://go.dev/blog/pipelines Fan-out, fan-in 's func merge()

func merge(cs ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)

    // Start an output goroutine for each input channel in cs.  output
    // copies values from c to out until c is closed, then calls wg.Done.
    output := func(c <-chan int) {
        for n := range c {
            out <- n
        }
        wg.Done()
    }
    wg.Add(len(cs))
    for _, c := range cs {
        go output(c)
    }

    // Start a goroutine to close out once all the output goroutines are
    // done.  This must start after the wg.Add call.
    go func() {
        wg.Wait()
        close(out)
    }()
    return out
}

Upvotes: 1

CGfly
CGfly

Reputation: 1

you can try run this code that don`t have wg ,and still deadlock ; why? Just because the main goroutine is blocked by the ch , so the program is suspended and timeout , and you get an deadlock error. The essential reason is that ch is blocking the main goroutine.

func main() {
    ch := make(chan []int, 4) // No matter which int number 
    var m []int
    for i := 0; i < 5; i++ {
        go func() {
            ch <- m
            return
        }()
    }
    for c := range ch {
        fmt.Printf("c is %v \n", c)
    }
}

Upvotes: 0

Denys S&#233;guret
Denys S&#233;guret

Reputation: 382514

You have two problems :

  • there's not enough place for all the goroutines as your channel is too small : when your channel is full, the remaining goroutines must wait for a slot to be freed
  • range ch is still waiting for elements to come in the channel and there's no goroutine left to write on it.

Solution 1 :

Make the channel big enough and, close it so that range stops waiting :

ch := make(chan []int, 5)
...
wg.Wait()
close(ch)

Demonstration

This works but this mostly defeats the purpose of channels here as you don't start printing before all tasks are done.

Solution 2 : This solution, which would allow a real pipelining (that is a smaller channel buffer), would be to do the Done() when printing :

func main() {
    ch := make(chan []int, 4)
    var m []int

    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            ch <- m
            return
        }()
    }
    go func() {
        for c := range ch {
            fmt.Printf("c is %v\n", c)
            wg.Done()
        }
    }()
    wg.Wait()
}

Demonstration

Upvotes: 10

nos
nos

Reputation: 229342

I'm running 4 go routines

No, you're running 5 - thus the deadlock, as the channel buffers only 4 messages.

However, iterating over the channel will later on deadlock the program too. Since the channel isn't closed, it'll block forever once it read your 5 values.

So,

ch := make(chan []int, 5)

and

close(ch) 

before the range loop.

Upvotes: 9

Related Questions