docs-source/actor/main.md
This Actor model implementation makes actors very cheap to create and discard. Thousands of actors can be created, allowing you to break the program into smaller maintainable pieces, without violating the single responsibility principle.
Actor-based concurrency is all the rage in some circles. Originally described in 1973, the actor model is a paradigm for creating asynchronous, concurrent objects that is becoming increasingly popular. Much has changed since actors were first written about four decades ago, which has led to a serious fragmentation within the actor community. There is no universally accepted, strict definition of "actor" and actor implementations differ widely between languages and libraries.
Wiki definition is pretty good: The actor model in computer science is a mathematical model of concurrent computation that treats actors as the universal primitives of concurrent digital computation: in response to a message that it receives, an actor can make local decisions, create more actors, send more messages, and determine how to respond to the next message received.
Concurrency is hard to get right, actors are one of many ways how to simplify the problem.
An example:
class Counter < Concurrent::Actor::Context
# Include context of an actor which gives this class access to reference
# and other information about the actor
# use initialize as you wish
def initialize(initial_value)
@count = initial_value
end
# override on_message to define actor's behaviour
def on_message(message)
if Integer === message
@count += message
end
end
end #
# Create new actor naming the instance 'first'.
# Return value is a reference to the actor, the actual actor is never returned.
counter = Counter.spawn(:first, 5)
# Tell a message and forget returning self.
counter.tell(1)
counter << 1
# (First counter now contains 7.)
# Send a messages asking for a result.
counter.ask(0).class
counter.ask(0).value
{include:file:docs-source/actor/quick.out.rb}
Messages are processed in same order as they are sent by a sender. It may interleaved with messages from other senders though.
Messages sent between actors should be immutable. Gems like
are very helpful.
{include:file:docs-source/actor/messaging.out.rb}
{include:Concurrent::Actor::AbstractContext}
{include:Actor::Reference}
Spawned actor cannot be garbage-collected until it's terminated. There is a reference held in the parent actor.
{include:Actor::Behaviour}
Actors are running on shared thread poll which allows user to create many actors cheaply.
Downside is that these actors cannot be directly used to do IO or other blocking operations.
Blocking operations could starve the global_fast_executor. However there are two options:
global_io_executor
(which is intended for blocking operations) sending results back to self in messages.global_io_executor instead of global_fast_executor, e.g.
AnIOActor.spawn name: :blocking, executor: Concurrent.global_io_executor.{include:file:docs-source/actor/io.out.rb}
see {AbstractContext#dead_letter_routing} description:
{include:Actor::AbstractContext#dead_letter_routing}
Alleged supervisor will receive errors from its supervised actors. They'll have to be handled manually.
Use option behaviour_definition: Behaviour.restarting_behaviour_definition(:resume!) or
behaviour_definition: Behaviour.restarting_behaviour_definition(:reset!, :one_for_all)
Any existing behavior can be subclassed
By subclassing {Behaviour::Pausing} and overriding {Behaviour::Pausing#restart!}. Implementing {AbstractContext#on_event} could be also considered.
We'll be happy to answer any other questions, just open an Issue or find us on https://gitter.im/ruby-concurrency/concurrent-ruby.
Simple benchmark Actor vs Celluloid, the numbers are looking good
but you know how it is with benchmarks. Source code is in
examples/actor/celluloid_benchmark.rb. It sends numbers between x actors
and adding 1 until certain limit is reached.
Benchmark legend:
Rehearsal ---------------------------------------------------------
50000 2 concurrent 26.140000 0.610000 26.750000 ( 7.761000)
50000 2 celluloid 41.440000 5.270000 46.710000 ( 17.535000)
50000 500 concurrent 11.340000 0.180000 11.520000 ( 3.498000)
50000 500 celluloid 19.310000 10.680000 29.990000 ( 14.619000)
50000 1000 concurrent 10.640000 0.180000 10.820000 ( 3.563000)
50000 1000 celluloid 17.840000 19.850000 37.690000 ( 18.892000)
50000 1500 concurrent 14.120000 0.290000 14.410000 ( 4.618000)
50000 1500 celluloid 19.060000 28.920000 47.980000 ( 25.185000)
---------------------------------------------- total: 225.870000sec
mes. act. impl. user system total real
50000 2 concurrent 7.320000 0.530000 7.850000 ( 3.637000)
50000 2 celluloid 13.780000 4.730000 18.510000 ( 10.756000)
50000 500 concurrent 9.270000 0.140000 9.410000 ( 3.020000)
50000 500 celluloid 16.540000 10.920000 27.460000 ( 14.308000)
50000 1000 concurrent 9.970000 0.160000 10.130000 ( 3.445000)
50000 1000 celluloid 15.930000 20.840000 36.770000 ( 18.272000)
50000 1500 concurrent 11.580000 0.240000 11.820000 ( 3.723000)
50000 1500 celluloid 19.440000 29.060000 48.500000 ( 25.227000) (1)
Rehearsal ---------------------------------------------------------
50000 2 concurrent 4.180000 0.080000 4.260000 ( 4.269435)
50000 2 celluloid 7.740000 3.100000 10.840000 ( 10.043875)
50000 500 concurrent 5.900000 1.310000 7.210000 ( 6.565067)
50000 500 celluloid 12.820000 5.810000 18.630000 ( 17.320765)
50000 1000 concurrent 6.080000 1.640000 7.720000 ( 6.931294)
50000 1000 celluloid 17.130000 8.320000 25.450000 ( 23.786146)
50000 1500 concurrent 6.940000 2.030000 8.970000 ( 7.927330)
50000 1500 celluloid 20.980000 12.040000 33.020000 ( 30.849578)
---------------------------------------------- total: 116.100000sec
mes. act. impl. user system total real
50000 2 concurrent 3.730000 0.100000 3.830000 ( 3.822688)
50000 2 celluloid 7.900000 2.910000 10.810000 ( 9.924014)
50000 500 concurrent 5.420000 1.230000 6.650000 ( 6.025579)
50000 500 celluloid 12.720000 5.540000 18.260000 ( 16.889517)
50000 1000 concurrent 5.420000 0.910000 6.330000 ( 5.896689)
50000 1000 celluloid 16.090000 8.040000 24.130000 ( 22.347102)
50000 1500 concurrent 5.580000 0.760000 6.340000 ( 6.038535)
50000 1500 celluloid 20.000000 11.680000 31.680000 ( 29.590774) (1)
Note (1): Celluloid is using thread per actor so this bench is creating about 1500 native threads. Actor is using constant number of threads.