Mutexes in Crystal
Here’s the translated code and explanation in Crystal, formatted for Hugo:
In this example, we’ll explore how to use mutexes to safely access data across multiple fibers. This is useful for more complex state management than what atomic operations can provide.
require "mutex"
# Container holds a hash of counters; since we want to
# update it concurrently from multiple fibers, we
# add a Mutex to synchronize access.
class Container
@mu : Mutex
@counters : Hash(String, Int32)
def initialize
@mu = Mutex.new
@counters = {"a" => 0, "b" => 0}
end
# Lock the mutex before accessing @counters; unlock
# it at the end of the method.
def inc(name : String)
@mu.synchronize do
@counters[name] += 1
end
end
def counters
@counters
end
end
# This function increments a named counter
# in a loop.
def do_increment(c : Container, name : String, n : Int32, channel : Channel(Nil))
n.times do
c.inc(name)
end
channel.send(nil)
end
def main
c = Container.new
# Run several fibers concurrently; note
# that they all access the same Container,
# and two of them access the same counter.
channel = Channel(Nil).new
spawn { do_increment(c, "a", 10000, channel) }
spawn { do_increment(c, "a", 10000, channel) }
spawn { do_increment(c, "b", 10000, channel) }
# Wait for the fibers to finish
3.times { channel.receive }
puts c.counters
end
mainRunning the program shows that the counters are updated as expected:
$ crystal run mutexes.cr
{"a" => 20000, "b" => 10000}In this Crystal version, we’ve made a few adjustments to match the language’s idioms:
We use
require "mutex"to import the mutex functionality.The
Containerclass is defined with instance variables@muand@counters.The
incmethod uses thesynchronizemethod of the mutex, which automatically handles locking and unlocking.We use fibers (Crystal’s lightweight concurrency primitive) instead of goroutines.
We use a
Channelto synchronize the fibers, similar to howWaitGroupis used in the original example.The
do_incrementfunction is defined at the top level and takes the container, name, count, and channel as parameters.We use
spawnto create new fibers.
This example demonstrates how to use mutexes in Crystal to safely manage shared state across multiple concurrent fibers. The mutex ensures that only one fiber can access or modify the shared counters at a time, preventing race conditions.
Next, we’ll look at implementing this same state management task using only fibers and channels.