Channel Synchronization in Crystal

Channel synchronization is a powerful feature for coordinating execution across fibers. Here’s an example of using a blocking receive to wait for a fiber to finish. When waiting for multiple fibers to finish, you may prefer to use a Channel(Nil).

require "fiber"

# This is the function we'll run in a fiber. The
# `done` channel will be used to notify another
# fiber that this function's work is done.
def worker(done : Channel(Bool))
  print "working..."
  sleep 1
  puts "done"

  # Send a value to notify that we're done.
  done.send(true)
end

# Start a worker fiber, giving it the channel to
# notify on.
done = Channel(Bool).new(1)
spawn worker(done)

# Block until we receive a notification from the
# worker on the channel.
done.receive

To run the program:

$ crystal run channel_synchronization.cr
working...done

If you removed the done.receive line from this program, the program would exit before the worker even started.

In Crystal, we use spawn to create a new fiber, which is similar to a lightweight thread. The Channel class is used for communication between fibers, allowing them to pass messages and synchronize their execution.

The worker function simulates some work by sleeping for a second, then sends a true value on the done channel to signal that it has finished.

In the main fiber, we create a buffered channel with a capacity of 1, start the worker fiber, and then wait for it to finish by receiving from the done channel. This receive operation blocks until a value is sent on the channel, effectively synchronizing the main fiber with the worker fiber.

This pattern is useful for ensuring that background tasks complete before the program exits, or for coordinating multiple concurrent operations.