Getting Started With Go Part 8: Channels

This is part 8 in a series called Getting Started With Go. If you would like to check out parts 1-7 you may find them here:

  1. Hello World
  2. Variables & Constants
  3. Functions
  4. Arrays & Slices
  5. Pointers & Structs 
  6. Interfaces
  7. Goroutines
  8. Channels (you are here now)

Channels

In the last post we introduced goroutines, so it’s only right to follow up with channels. Think of channels as tunnels that data gets sent through. They are really useful when combined with goroutines as they are able to receive data from one goroutine and send it to another. Let’s take a look at channels in action.

messages := make(chan string)

In this one line we have defined a channel called messages. The channel has a type of string and it is created via Go’s make function. This makes it easier to avoid passing the wrong data type into a channel. For example:

go func() {messages <- "hello"}()

This line creates a goroutine and sends a string into the messages channel. As you can see, this is done with <- where the data flows in the direction of the arrow. If you were to swap out “hello” for something else that isn’t a string like an integer, boolean, or struct, you would get an immediate error. Once data has been passed into the channel, we can assign it to other data types the same way. When we put it all together it looks like this.

func main() {
messages := make(chan string)
go func() {messages <- "hello"}()

msg := <- messages
fmt.Println(msg)
}

Using the arrow while assigning value, we have now passed the hello string into the msg variable. 

Buffered Channels

If we wanted to pass multiple values into the channel above, we would have a pretty hard time making that happen unless we used a buffered channel. Buffered channels are able to accept a certain number of data types before having to send them anywhere. These are created similar to regular channels, except you would pass an integer along with the type of channel. This integer represents the number of types that can be held without a receiver. 

bufferedMessages := make(chan string, 5)

With the new bufferedMessages, we can send 5 strings before needing to assign them to some type of variable or act on them.

func main() {

    bufferedMessages := make(chan string, 5)

    bufferedMessages <- "This"
    bufferedMessages <- "is"
    bufferedMessages <- "a"
    bufferedMessages <- "buffered"
    bufferedMessages <- "channel"

    fmt.Println(<-bufferedMessages)
    fmt.Println(<-bufferedMessages)
    fmt.Println(<-bufferedMessages)
    fmt.Println(<-bufferedMessages)
    fmt.Println(<-bufferedMessages)
}

WaitGroups

In addition to buffered channels, Go also has something called WaitGroups. This may have been mentioned before in a previous post, so I figured this would be a good time to explore what they are. WaitGroups can wait for multiple goroutines to finish executing before the program moves on to other code. Defining a WaitGroup looks like this:

var wg sync.WaitGroup

func doSomeJob(id int, wg *sync.WaitGroup) {

  defer wg.Done()

  fmt.Printf("Job %d starting\n", id)

  time.Sleep(time.Second)
  fmt.Printf("Job %d done\n", id)
}

I also have created a function to simulate a goroutine job above. This looks really familiar to one of the functions from the previous post, except for little addition. We are using a defer line in the function. The keyword defer means to run the code in that line after everything else in the function. The function being deferred (wg.Done()) will signal to the WaitGroup being passed in to wrap up safely, this avoids memory leaks in Go.  WaitGroups also have two other functions we’ll be using today. The first one is the add function, this simply tallies the number of goroutines we eventually want to wait for. And last, the wait function, which is responsible for waiting on all the goroutines to finish before moving on.

var wg sync.WaitGroup

for i := 1; i <= 5; i++ {
  wg.Add(1)
  go doSomeJob(i, &wg)
}

wg.Wait()
fmt.Println("done")

When running this in main() you will get the following result:

Job 5 starting
Job 1 starting
Job 2 starting
Job 3 starting
Job 4 starting
Job 4 done
Job 2 done
Job 3 done
Job 1 done
Job 5 done
done

That’s it! Today we learned about channels in Go, how to create them, and how to use buffered channels. As always, questions, comments, and any other feedback is always appreciated. If you haven’t already, feel free to check out the first 7 parts. I hope you found this tutorial helpful!

Leave a Comment

Your email address will not be published. Required fields are marked *