Go Channels buffering, iteration, and synchronization




Welcome to part 23 of the Go programming tutorial, where we'll be getting a little deeper into Go channels. In the previous tutorial, we covered how to use channels to send and receive values with goroutines. That said, it was just a basic example. In reality, we're likely to have questions of synchronization and iterating through known, or unknown, numbers of channel returns. Code up to this point:

package main

import "fmt"

func foo(c chan int, someValue int) {
    c <- someValue * 5
}

func main() {
    fooVal := make(chan int)
    go foo(fooVal, 5)
    go foo(fooVal, 3)
    v1 := <-fooVal
    v2 := <-fooVal
    fmt.Println(v1, v2)
}

In the above example, immediately, you should be wondering something along the lines of "How come we didn't need to have a wait group for these routines to finish?"

Channels by default are blocking on sending and receiving values, so they will be waited on. Let's consider though that we might not actually know how many channels we have, or maybe we just have a lot and we know we shouldn't be hard-coding the returns. Instead, we might want to iterate over the channel data, however long it might be. Let's say instead, we want to do:

package main

import "fmt"

func foo(c chan int, someValue int) {
    c <- someValue * 5
}

func main() {
    fooVal := make(chan int)

    for i := 0; i < 10; i++ {
        go foo(fooVal, i)
    }
}

Once again, one of the very few magical operators in go, range works wonders with channels too! We can iterate over a channel, where our channel is called fooVal, by doing:

    for item := range fooVal {
        fmt.Println(item)
    }

All together:

package main

import "fmt"

func foo(c chan int, someValue int) {
    c <- someValue * 5
}

func main() {
    fooVal := make(chan int)

    for i := 0; i < 10; i++ {
        go foo(fooVal, i)
    }

    for item := range fooVal {
        fmt.Println(item)
    }
}

This will run, but will error out. Output is something like:

45
20
0
5
10
15
30
25
40
35
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        C:/Users/H/Desktop/golangstuff/gotut22-channels.go:16 +0xb2
exit status 2

We've seen this issue before. This happens when we're still waiting around for something, but there's nothing to be waited on anymore. What are we waiting for here? Well, our channel has no buffer, and the range statement doesn't have any sort of buffer or anything, it's just waiting and waiting. I am fairly impressed it even iterates at all, giving me the impression that range is actually even more magical than we were thinking. Anyway, the channel remains open. This is a problem. We can close the channel with close(), doing close(fooVal)

package main

import "fmt"

func foo(c chan int, someValue int) {
    c <- someValue * 5
}

func main() {
    fooVal := make(chan int)

    for i := 0; i < 10; i++ {
        go foo(fooVal, i)
    }

    close(fooVal)

    for item := range fooVal {
        fmt.Println(item)
    }
}

Oh, great, we have no more errors... but now we see nothing. Something is still wrong. You might be thinking "oh, I've seen this before, we just need to synchronize!" You are right that we need to synchronize, but we need to do it for more reasons than just because the goroutines aren't finishing before the program. We can illustrate that with adding a sleep:

package main

import (
    "fmt"
    "time"
)

func foo(c chan int, someValue int) {
    c <- someValue * 5
}

func main() {
    fooVal := make(chan int)
    for i := 0; i < 10; i++ {
        go foo(fooVal, i)
    }
    close(fooVal)
    for item := range fooVal {
        fmt.Println(item)
    }
    time.Sleep(time.Second*2)
}

This gives us a panic: panic: send on closed channel

This is because we're getting to the close(), not the end of the program, before the goroutines can finish and send the data. Still, however, we can use the sync package to solve our problem here. Let's import sync, create our wait group: var wg sync.WaitGroup, use wg.Add(1) before each goroutine, and do a defer wg.Done() inside the goroutine. Finally, do a wg.Wait() before we close the channel.

Finally, we want our channels to be non-blocking. We don't need them to be blocking. The blocking aspect of channels handles a form of synchronization, but we're controlling flow now, and letting the channels block each other will be problematic. Buffers on channels goes by items. In our case, we have 10 items, so we can add a buffer of 10 when we create our channel by doing: fooVal := make(chan int, 10). In this case, we need to be certain the buffer is higher than the amount of channels we're attempting to synchronize together here. We can do 10, or we could do 50, or 500. We couldn't do 5, for example.

With the following code:

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func foo(c chan int, someValue int) {
    defer wg.Done()
    c <- someValue * 5
}

func main() {
    fooVal := make(chan int, 10)
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go foo(fooVal, i)
    }
    wg.Wait()
    close(fooVal)
    for item := range fooVal {
        fmt.Println(item)
    }
}

We get:

45
20
25
30
35
40
15
5
0
10

Okay, so at this point, we're basically go channel pros. We're ready to apply goroutines and channels to our webapp, and hope for the best!

The next tutorial:





  • Introduction to the Go Programming Language
  • Go Language Syntax
  • Go Language Types
  • Pointers in Go Programming
  • Simple Web App in Go Programming
  • Structs in the Go Programming Language
  • Methods in Go Programming
  • Pointer Receivers in Go Programming
  • More Web Dev in Go Language
  • Acessing the Internet in Go
  • Parsing XML with Go Programming
  • Looping in Go Programming
  • Continuing our Go Web application
  • Mapping in Golang
  • Mapping Golang sitemap data
  • Golang Web App HTML Templating
  • Applying templating to our Golang web app
  • Goroutines - Concurrency in Goprogramming
  • Synchronizing Goroutines - Concurrency in Golang
  • Defer - Golang
  • Panic and Recover in Go Programming
  • Go Channels - Concurrency in Go
  • Go Channels buffering, iteration, and synchronization
  • Adding Concurrency to speed up our Golang Web Application