testing495
testing495

Reputation: 272

How to fix the deadlock in this code, and extend it so that it can wait on a channel which has no writers?

I'm trying to learn channels in Go, so this is a contrived example of having a channel where there are multiple writers, but only one reader. This is a very basic example, but I would like to take it further, by imagining its a http server, where there is a goroutine created for each new request, and each goroutine makes a write on a channel.

func Start2(){
    var wg sync.WaitGroup
    wg.Add(2)
    c := make(chan int)
    go func(){
        defer wg.Done()
        i := 0
        for {
            
            i+=2
            c<-i
            time.Sleep(time.Duration(rand.Intn(5)) * time.Second)
            if i > 10 {
                break
            }
        }
    }()

    go func(){
        defer wg.Done()
        i := 1
        for {
            i+=2
            c<-i
            time.Sleep(time.Duration(rand.Intn(5)) * time.Second)
            if i > 10 {
                break
            }
        }
    }()
    

    for a := range c {
        log.Println(a)
    }

    wg.Wait()
    
}

Why does this deadlock? How could I organise the logic of reading from the channel, so that the code doesn't deadlock when there are no writers on the channel? (For instance, in the end goal example, if there isn't currently a request being made to the http server)?

Upvotes: 0

Views: 82

Answers (2)

wasmup
wasmup

Reputation: 16233

Unbuffered channel needs at least two goroutines to operate and by exiting one of then. the Go runtime is smart enough to detect the deadlock, so you have two options here:

  1. Since you want to use the channel in the http server - there is no deadlock here:
package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func main() {
    http.HandleFunc("/", home)
    go func() {
        err := http.ListenAndServe(":8080", nil)
        if err != nil {
            log.Fatal(err)
        }
    }()
    count := 0
    for a := range c {
        count++
        fmt.Println(count, a)
    }
}

var c = make(chan time.Time)

func home(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hi")
    c <- time.Now()
}

  1. Or close the channel to force the main goroutine to exit too - just for your current example code - try it:
package main

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

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(2)
    c := make(chan int)
    go routine(0, c, wg)
    go routine(1, c, wg)
    go func() {
        wg.Wait()
        close(c)
    }()

    for a := range c {
        log.Println(a)
    }
}

func routine(i int, c chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
        i += 2
        c <- i
        time.Sleep(time.Duration(rand.Intn(5)) * time.Second)
        if i > 10 {
            break
        }
    }
}

Upvotes: 1

MoiioM
MoiioM

Reputation: 1974

You are not closing your channel, you need to close it when you are done.

You can create a goroutine for reading from your channel and close it after the wait group.

Example:

var wg sync.WaitGroup
wg.Add(2)
c := make(chan int)
go func() {
    defer wg.Done()
    i := 0
    for {

        i += 2
        c <- i
        time.Sleep(time.Duration(rand.Intn(5)) * time.Second)
        if i > 10 {
            return
        }
    }
}()

go func() {
    defer wg.Done()
    i := 1
    for {
        i += 2
        c <- i
        time.Sleep(time.Duration(rand.Intn(5)) * time.Second)
        if i > 10 {
            break
        }
    }
}()
//read channel in a goroutine
go func() {
    for a := range c {
        log.Println(a)
    }
}()

wg.Wait()
//close channel when done
close(c)

Upvotes: 1

Related Questions