2

I have this piece of Go code. I need to have this ability: write into channels in one place, and read them out in another place(or vice versa):

package main
import "fmt"
var ch1=make(chan int)
var ch2=make(chan int)

func f1() {
    select {
    case <- ch1:fmt.Println("ch1")
    default: fmt.Println("default")
    }
}
func f2() {
    select {
    case <- ch2:fmt.Println("ch2")
    default: fmt.Println("default")
    }
}
func main() {
    go f1()
    go f2()
    ch1<-1
    ch2<-2
}

It always prints sth like this:

default
ch1
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /tmp/sandbox970110849/prog.go:22 +0xa0

Further more I tried this:

package main
import (
    "fmt"
    "sync"
)
var ch1=make(chan int)
var ch2=make(chan int)

func f1() {
    select {
    case <- ch1:fmt.Println("ch1")
    default: fmt.Println("default")
    }
}
func f2() {
    select {
    case <- ch2:fmt.Println("ch2")
    default: fmt.Println("default")
    }
}

func w1() {
    ch1 <-1
}

func w2() {
    ch2 <-1
}
func main() {
    var wg sync.WaitGroup
    wg.Add(4)
    go f1()
    go f2()
    go w1()
    go w2()
    wg.Wait()
}

Even more errors this time:

default
ch2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x40e028, 0x0)
    /usr/local/go/src/runtime/sema.go:56 +0x40
sync.(*WaitGroup).Wait(0x40e020, 0x14b720)
    /usr/local/go/src/sync/waitgroup.go:130 +0x60
main.main()
    /tmp/sandbox916639182/prog.go:36 +0x100

goroutine 8 [chan send]:
main.w1()
    /tmp/sandbox916639182/prog.go:23 +0x40
created by main.main
    /tmp/sandbox916639182/prog.go:34 +0xc0

Where did I go wrong and how to fix it?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Troskyvs
  • 7,537
  • 7
  • 47
  • 115

2 Answers2

5

Your main() function tries to send on all channels, and there is only a one-time attempt to read from those channels in separate, concurrent goroutines. It's up to the goroutine scheduler if this will succeed. If the non-blocking receive in f2() is scheduled earlier than the send in main(), then the later send in main() will block forever (no one will attempt to receive from ch2 again).

One way to get rid of the deadlock is to use receive operation instead of the non-blocking receive (try it on the Go Playground):

func f1() {
    <-ch1
    fmt.Println("ch1")
}
func f2() {
    <-ch2
    fmt.Println("ch2")
}

So no matter when main() gets to the point to send values on these channels, there will always be a receiver ready to proceed, so the main() won't get stuck.

Note that when main() returns, the app ends, it does not wait for non-main goroutines to finish. So you might not see ch1 and ch2 printed on your console. For details, see No output from goroutine in Go.

If your intent is to wait for all goroutines to finish their work before your app exists, use sync.WaitGroup for that (try it on the Go Playground):

var ch1 = make(chan int)
var ch2 = make(chan int)

var wg sync.WaitGroup

func f1() {
    defer wg.Done()
    <-ch1
    fmt.Println("ch1")
}

func f2() {
    defer wg.Done()
    <-ch2
    fmt.Println("ch2")
}

func main() {
    wg.Add(1)
    go f1()
    wg.Add(1)
    go f2()

    ch1 <- 1
    ch2 <- 2
    wg.Wait()
}

See more examples here: Solving goroutines deadlock; and Prevent the main() function from terminating before goroutines finish in Golang.

Another option is to give a buffer of 1 to the channels, so the main() can send 1 value on them without a receiver ready to receive from the channels (try this one on the Go Playground):

var ch1 = make(chan int, 1)
var ch2 = make(chan int, 1)

With this the main() can proceed without f1() and f2(), so again, there is no guarantee you will see anything printed.

icza
  • 389,944
  • 63
  • 907
  • 827
3

Channels used here are unbuffered channels.

  • when main goroutines starts it creates two new goroutine f1 and f2.
  • when f1 or f2 is executed it will check whether we have value in the channel else it will print the default message and exit.
  • In ideal situation,channels will be published with value first and then it's is received via the goroutine
  • Actual situation is that goroutines are exiting by printing the default case but main goroutine is trying to publish value in a channel but since no receiver is there it faces a deadlock situation.

Removed deadlock from first example, Refer the link:https://play.golang.org/p/6RuQQwC9JkA

However,if main routine exits all the associated goroutines will be killed automatically. That's why we can see that value is printed arbitrarily.

Removed deadlock from Second example, Refer the link:https://play.golang.org/p/yUmc_jjZMgV

sync.WaitGroup is making main goroutine wait that's why we can observe the output everytime our program executes.