The Association of Mad Scientists

Channels and Coroutines Make Event Loops Simple

There are many ways that programming languages are trying to add high-level concurrency features that help programmers write code that doesn't care as much about what order it's run in. My favorite of these methods is Channels. Go and Crystal support channels for their "goroutines" and "fibers" respectively. Here's a simple example of a loop that will keep performing a single action until it receives a message from another thread.

Crystal:

def async_action
  canceller = Channel::Buffered(Bool).new 1
  spawn do
    loop do
      break unless canceller.empty?
      puts "async action"
      sleep 1.second
    end
  end
  return canceller
end

canceller = async_action

sleep 5.second

canceller.send true
puts "done"

Go:

package main

import (
	"fmt"
	"time"
)

func main() {
	canceller := coroutine()
	time.Sleep(5 * time.Second)
	canceller <- true
	fmt.Println("done.")
}

func coroutine() chan bool {
	canceller := make(chan bool)
	go func() {
		for {
			select {
			case <-canceller:
				break
			default:
				fmt.Println("async action")
				time.Sleep(1 * time.Second)
			}
		}
	}()
	return canceller
}

The concept of simply saying "go do this" or "spawn do that", and being able to pass around your communications channel as a variable, makes programming asynchronously in Go and Crystal a breeze.