Why don't these goroutines block?

Issue

I expect these two goroutines to block forever for the reasons as below but it doesn’t. Why?

  1. The channel has no buffer and will be waiting for receive() to receive.

  2. send() hold the lock so num := <-s.ch in receive() has no chance to execute.

  3. Block forever

What’s wrong?

package main

import (
    "sync"
    "fmt"
)

type S struct {
    mu sync.Mutex
    ch chan int
    wg sync.WaitGroup
}

func (s *S) send() {
    s.mu.Lock()
    s.ch <- 5
    s.mu.Unlock()
    s.wg.Done()
}
func (s *S) receive() {
    num := <-s.ch
    fmt.Printf("%d\n", num)
    s.wg.Done()
}

func main() {
    s := new(S)
    s.ch = make(chan int)
    s.wg.Add(2)
    go s.send()
    go s.receive()
    s.wg.Wait()
}

Solution

Your receive() method doesn’t use the lock, so send() holding the lock has no effect on receive().

And since both send() and receive() run in their own goroutine, send() will make it to the point where it sends the value 5 on the channel, and so the receive in receive() can proceed, and it will, and will print it in the next line.

Also note that to use channels from multiple goroutines, you do not need “external” synchronization. Channels are safe for concurrent use, data races cannot occur by design. For details, see If I am using channels properly should I need to use mutexes?

If the receive() method would also use the lock like this:

func (s *S) receive() {
    s.mu.Lock()
    num := <-s.ch
    s.mu.Unlock()
    fmt.Printf("%d\n", num)
}

Then yes, nothing would be printed, because the receive cannot happen until send() releases the lock, but that can’t happen until someone receives from the channel.

And in this case the program would terminate after 1 second without printing anything, because when the sleep is over the main goroutine ends, so does your whole app with it. It does not wait for other non-main goroutines to complete. For details, see No output from goroutine in Go.

Edit:

Yes, you misunderstand the lock. Locking a sync.Mutex only locks the mutex value itself, it does not lock the whole struct value (it can’t). And “locks the value itself” means if another goroutine also calls its Mutex.Lock() method, that call will block until the lock is released by calling its Mutex.Unlock() method. When it’s unlocked, the goroutine that is blocked at the Mutex.Lock() call will proceed to lock the mutex and return.

Answered By – icza

Answer Checked By – Katrina (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.