Fiber::ExecutionContext::Concurrent
Inherits Fiber::ExecutionContext::Parallel / Fiber::ExecutionContext / Reference / Object
Concurrent-only execution context.
Fibers running in the same context can only run concurrently and never in parallel to each others. However, they still run in parallel to fibers running in other execution contexts.
Fibers in this context can use simpler and faster synchronization primitives
between themselves (for example no atomics or thread safety required), but
data shared with other contexts needs to be protected (see Sync), and
communication with fibers in other contexts requires safe primitives, for
example Channel.
A blocking fiber blocks the entire context, and thus all the other fibers in the context.
For example: we can start a concurrent context to run consumer fibers, while the default context produces values. Because the consumer fibers will never run in parallel and don't yield between reading result then writing it, we are not required to synchronize accesses to the value:
require "wait_group"
consumers = Fiber::ExecutionContext::Concurrent.new("consumers")
channel = Channel(Int32).new(64)
wg = WaitGroup.new(32)
result = 0
32.times do
consumers.spawn do
while value = channel.receive?
# safe, but only for this example:
result = result + value
end
ensure
wg.done
end
end
1024.times { |i| channel.send(i) }
channel.close
# wait for all workers to be done
wg.wait
p result # => 523776
In practice, we still recommended to always protect shared accesses to a
variable, for example using Atomic#add to increment result or a Sync
primitive for more complex operations.
NOTE: The Concurrent execution context isn't tied to a system thread, and
may switch to another system thread, for example when a fiber is blocked on
a syscall.