Example 1

In the below code, <-messages will block until it gets something on the channel. This prevents main() from exiting until one item is received on the channel.

package main

import (
    "fmt"
    "time"
)

func waitThenSend(msg chan string) {
    time.Sleep(2 * time.Second)
    msg <- "badwolf"
}

func main() {
    messages := make(chan string) // make unbuffered channel

    go func() {
        waitThenSend(messages) // make goroutine, pass in channel
    }()

    <-messages // blocks until channel receives a value

    fmt.Println("done")
}

Example 2

The below example demonstrates using a sync.WaitGroupto keep the program running until all of the goroutines have exited. Data is sent to the function via the unbuffered channel named numbers.

It is important to realize that fmt.Println("after:", i) does not get executed until fmt.Println("printing:", <-num) receives data because of the blocking nature of the unbuffered channel.

If we change numbers := make(chan int) to numbers := make(chan int, 10) to make it a buffered channel, it will not block our program because we only put 10 items in the channel. If we use 5 instead of 10, it will block after the channel receives 5 objects.

package main

import (
    "fmt"
    "sync"
    "time"
)

func print(num chan int, wg *sync.WaitGroup) {
    for {
        time.Sleep(1 * time.Second)
        fmt.Println("printing:", <-num)
        wg.Done()  // decrement waitgroup
    }
}

func main() {
    numbers := make(chan int)  // make unbuffered channel
    var wg sync.WaitGroup
    go print(numbers, &wg)

    for i := 0; i < 10; i++ {
        i := i
        wg.Add(1) // increment waitgroup

        go func() {
            fmt.Println("before:", i)
            numbers <- i // blocks if nothing is listening to the *unbuffered* channel!
            fmt.Println("after:", i)
        }()
    }

    wg.Wait() // blocks until waitgroup is zero again

    fmt.Println("done")
}

Neat!