Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range a channel finishes with deadlock

Tags:

go

The following code ends with a fatal error: all goroutines are asleep - deadlock!

// Package letter returns the frequency of letters in texts using parallel computation.
package letter

import "fmt"

const testVersion = 1

type FreqMap map[rune]int

func Frequency(s string) FreqMap {
    m := FreqMap{}
    for _, r := range s {
        m[r]++
    }
    return m
}

func ConcurrentFrequency(l []string) FreqMap {
    ch := make(chan FreqMap)
    for _, s := range l {
        go func() {
            ch <- Frequency(s)
        }()
    }
    m := FreqMap{}
    for c := range ch {
        fmt.Println("channel:", c)
        for k, v := range c {
            m[k] += v
        }
    }
    return m
}

This is output:

channel: map[121:8 101:42 98:8 104:28 102:6 71:1 59:1 97:33 110:15 103:15 112:6 109:5 116:38 10:7 87:2 107:1 108:17 99:3 117:7 39:5 79:4 114:27 105:15 44:7 119:8 100:12 63:2 65:1 118:3 45:1 32:72 111:18 115:24]
channel: map[97:33 110:15 111:18 117:7 108:17 45:1 115:24 10:7 79:4 32:72 121:8 100:12 105:15 63:2 107:1 71:1 119:8 114:27 103:15 102:6 65:1 44:7 87:2 118:3 101:42 98:8 116:38 39:5 112:6 104:28 109:5 99:3 59:1]
channel: map[99:3 116:38 104:28 108:17 44:7 117:7 119:8 114:27 10:7 87:2 110:15 100:12 103:15 107:1 32:72 111:18 102:6 59:1 45:1 101:42 109:5 63:2 115:24 97:33 105:15 112:6 65:1 71:1 79:4 121:8 98:8 39:5 118:3]
fatal error: all goroutines are asleep - deadlock!

My understanding is that range waits for data from channel until the channel is closed, but adding close(ch) inside the for loop, or inside the func() or after those, make things worse (the fmt.Println does not get anything) - tried also with defer (same problem)

What would be the correct approach? Isn't range the right solution in this case ?

Thanks a lot !

like image 203
user8285681 Avatar asked Jul 10 '17 19:07

user8285681


People also ask

Can we use for range through a channel?

The range keyword can also be used on a channel. By doing so, it will iterate over every item thats send on the channel. You can iterate on both buffered and unbuffered channels, but buffered channels need to be closed before iterating over them.

What is Channel deadlock?

A deadlock happens when a group of goroutines are waiting for each other and none of them is able to proceed. For example: When one goroutine try to receive message from channel. And channel is empty.


2 Answers

Range only stops when the channel is closed. You're hitting a deadlock because nothing is writing to the channel, but you're sitting waiting for something to be written. You could add a sync.WaitGroup and close the channel after all of the goroutines writing to it have finished.

You should probably also change the loop as there is potential for the wrong values to be passed because you're closing over the loop variable which can change concurrently; this will be more reliable:

for _, s := range l {
    go func(s rune) {
        ch <- Frequency(s)
    }(s)
}
like image 165
Adrian Avatar answered Oct 17 '22 16:10

Adrian


I would prefer to use for...select to receive the message in channel.

func main() {

    in := State{[]What{A,B,C,D},[]What{},nil}
    out := make(chan State)
    go in.think(out)

    life:=5
    for {
        select {
        case s:=<-out:
            fmt.Printf("out : %+v\n", s)
        default:
            time.Sleep(50 * time.Millisecond)
            if life--;life==0 {
                return
            }
        }
    }
}
like image 1
Kevin Wong Avatar answered Oct 17 '22 15:10

Kevin Wong