On Beyond Objects Programming in the 21th century
Description: On Beyond Objects Programming in the 21th century COMP 590-059 Fall 2024 David Stotts Computer Science Dept UNC Chapel Hill The Actor concurrency model Models: Actors Carl Hewitt, 1973 Alternative to Shared State concurrency model An
Related Topics
Download Presentation
"On Beyond Objects Programming in the 21th century" is the property of its rightful owner. Permission is granted to download and print the materials on this website for personal, non-commercial use only, and to display it on your personal computer provided you do not modify the materials and that you retain all copyright notices contained in the materials. By downloading content from our website, you accept the terms of this agreement.
Presentation Transcript
slide1. On Beyond ObjectsProgramming in the 21th centuryCOMP 590-059 Fall 2024 David Stotts
Computer Science Dept
UNC Chapel Hill<br>
slide2. The Actor concurrency model<br>
slide3. Models: Actors Carl Hewitt, 1973 Alternative to Shared State concurrency model An “actor” is the universal primitive of concurrent computation
Actors have private state, but do not share state with other actors
No need for lock-based synchronization
Actors communicate via message passing
When an actor receives a message it can:
make local decisions
create more actors
determine how to respond to the next message
Hewitt claims the actor model was inspired by laws of physics, and depends on them for its fundamental axioms
Includes general relativity, and quantum mechanics<br>
slide4. References YouTube videos Hewitt et al. discuss the actor model Talking concurrency with Carl Hewitt
Hoare, Hewitt, and Armstrong interview
The Actor model
Intro to the Actor model for concurrent computation
Actor model and the Queue Other documents…
Actors for distributed programs
Actors in Scala and Erlang ( I have borrowed from these )<br>
slide5. Actor Model of Concurrency Summary of the New View Don’t communicate by sharing memory, rather share memory (data) by communicating What can actors do? Hewitt’s summary… in video
“Process” ( compute something for local state )
Create new actors
Receive messages, then in response:
Make local decisions (alter local state)
Do arbitrary side-effecting actions (print)
Send messages
Respond to sender 0 or more times
Process exactly one message at a time<br>
slide6. Concurrency Models Message passing Workers share no data directly or, message channels are standalone entities that Communicate with message channels (often called mailboxes) unique to each computation agent computation agents send and receive from Erlang<br>
slide7. Other Concurrency Models Old and New Processes (heavy weight, OS support)
Threads (lighter weight, popular with multi-cores, OS support)
Co-routines (early idea, assumes one CPU)
Communicating Sequential Processes (CSP)
Process calculi
Petri nets (Place/Transition nets)
Dataflow
Tuple spaces
Futures (promises)
Actors<br>
slide8. Development of the Model https://en.wikipedia.org/wiki/History_of_the_Actor_model According to Hewitt the actor model is based on physics in contrast with other models of computation ( that were based on mathematical logic, set theory, algebra, etc.)
One issue is what can be observed about Actor systems.
The question does not have an obvious answer because it poses both theoretical and observational challenges similar to those that had arisen in constructing the foundations of quantum physics.
In Actor systems, typically we cannot observe the details by which the arrival order of messages for an Actor is determined (called indeterminacy ).
Attempting to do so affects the results and can even push the indeterminacy elsewhere. e.g., see metastability in electronics. Instead of observing the insides of arbitration processes of Actor computations, we await the outcomes.
Appropriate for modern multi-cores, as they work with arbiter circuits (“random selectors) A fundamental change in the Actor model is that it did not provide for global states
A computational step could not be defined as going from one global state to the next global state as had been done in all previous models of computation.<br>
slide9. Indeterminacy Indeterminacy is more like: impossible to even define interleavings
This arises in actors from message passing… message sending
A message can take an arbitrarily long amount of time to be delivered
There is (from physical machines hosting actors) actual simultaneity that cannot be viewed as interleaving Indeterminacy is defined as different from non-determinacy
Other concurrency models seems to think of concurrency as interleaving of events that could be all done in sequence, but are shuffled together… and we cant predict the shuffle
Interleaved concurrent events on critical regions gives rise to race conditions, for example
Different interleavings … some work, some conflict, all from one collection of concurrent processes<br>
slide10. Models: Message Passing Diagram<br>
slide11. Models: Message Passing Diagram<br>
slide12. Models: Message Passing Diagram<br>
slide13. Actors As noted, an actor is the primitive unit of computation An actor is the thing that receives a message and does some kind of computation based on it.
The idea is very similar to what we have in object-oriented languages:
an object receives a message (a method call)
it then does something depending on which message it receives (which method we are calling)
The main difference is that actors are completely isolated from each other and they will never share memory
It’s also worth noting that an actor can maintain a private state that can never be changed directly by another actor
Java allows one object to change another’s state ( public fields )
however, we consider this poor OO style (use private fields, getters/setters)<br>
slide14. Actors vs. Objects An actor is dynamically created (an actor can create more actors)
An actor exists as long as there are statements/expression to eval.
In Erlang (one actor implementation) this means tail recursion to make longer-lived actors
An actor ends when its code is fully executed
An actor can also end when it self-terminates… or is terminated by another actor
In OO languages, like Java, an object can be dynamically created, but only according to scope rules
Objects… once created… exists until the enclosing scope is done
Then an object either has a reference passed to some other scope
Or, it is cast adrift (ends) in the heap and garbage collected<br>
slide15. Comparative Example Checking Account Shared account balance (one balance total)
Multiple concurrent users making deposits and withdrawals
Traditional approach in shared-state model (like Java threads) requires locking and coordination to prevent race conditions, overdrawing Consider this scenario
Balance of $80.00
Person A wants to withdraw $60.00
Person B wants to withdraw $50.00<br>
slide16. Example: Checking Naive approach struct Checking { // a shared resource
balance int
}
. . . // withdrawal code
if (Checking.balance >= withdrawal.amount) {
Checking.balance -= withdrawal.amount;
return true; // success
}
else {
return false; // cant do it, NSF
}<br>
slide17. Example: Checking Interaction Diagram
with
one User<br>
slide18. Example: Checking Interaction Diagram
with
Two check writers<br>
slide19. Example: Checking Locking (threads) approach struct Checking { // a shared resource
balance int
lock Mutex // mutual exclusion lock
}
. . . // check cashing code
Checking.lock.Lock();
boolean successFlag = false;
if (Checking.balance >= withdrawal.amount) {
Checking.balance -= withdrawal.amount;
successFlag = true; // check is covered
}
Checking.lock.Free();
return successFlag;<br>
slide20. Example: Checking Actor approach actor Checking { // now not shared
var balance = 80
receive = {
case Withdrawal(amt) =>
if (balance >= amt) {
balance -= amt;
sender sendMsg true; // success
}
else {
sender sendMsg false; // cant do it, NSF
};
. . .
}
. . .
}<br>
slide21. Example: Checking Interaction Diagram
with an actor as account and
two actors writing checks<br>
slide22. Distribution Is an actor remote? Or local? It doesn’t matter
Another aspect of the actor model is that it doesn’t matter if the actor that I’m sending a message to is running locally or in another node.
If an actor is just a unit of code with a mailbox and an internal state, and it just responds to messages, it doesn’t alter the functional performance to have the code running on some specific machine
As long as we can make the message get there we are fine; timing performance might be affected (slow network, slow CPU vs. fast, etc.) but it will still compute the same function
So the model does not have a notion of local or remote… one machine or many… in the abstract it is not there
This allows us to create systems that leverage multiple computers and helps us to recover if one of them fail.<br>
slide23. Basics of Messages Message send No channel or intermediary with semantics (like CSP)
Best-effort delivery
At-most-once delivery
Messages can take arbitrarily long to be delivered (no timeout, or clock)
No message orderings guaranteed (can arrive “out of order”)
No automatic responses, no blocking waiting<br>
slide24. Addresses Location where message is sent Address “semantics” or decoding is not part of the model
Identifies an actor (process in Erlang)
May also represent a proxy or fan-out to other actors
Contains location and transport information
Gives location transparency (same process, diff process same machine, different local machine core, different network node)<br>
slide25. Addresses One address, many actors Address may represent a “pool” of many actors<br>
slide26. Addresses One actor, many addresses Address may be an “alias”, one of several<br>
slide27. Fault tolerance Actor model allows a “Let it crash” coding style Erlang introduced the “let it crash” philosophy. The idea is that you shouldn’t need to program defensively, trying to anticipate all the possible problems that could happen and find a way to handle them, simply because there is no way to think about every single failure point.
What Erlang does is simply letting it crash, but make this critical code be supervised by someone whose only responsibility is to know what to do when this crash happens (like resetting this unit of code to a stable state), and what makes it all possible is the actor model.
Every code run inside a process (that is basically how Erlang calls its actors). This process is completely isolated, meaning its state is not going to influence any other process. We have a supervisor, that is basically another process (everything is an actor, remember?), that will be notified when the supervised process crashes and then can do something about it.
This makes it possible to create systems that “self heal”, meaning that if an actor gets to an exceptional state and crashes, by whatever reason, a supervisor can do something about it to try to put it in a consistent state again (and there are multiple strategies to do that, the most common being just to restart the actor with its initial state).<br>
slide28. Fault tolerance: Supervision Actor model allows a “Let it crash” coding style Running state of an actor is monitored and managed by another actor
Called a supervisor
Can perform actions based on state on the monitored actor (was there an unhandled error? What caused the termination?)
Maybe restart the monitored actor…
Supervisor… who watches it? Supervisor trees<br>
slide29. Fault tolerance Other notes and issues Supervisor handling error conditions, rather than having the actor that crashes anticipate and manage them all… is a bit like AOP… separation of concerns
Actors concentrate on “business logic”
Special other actors (supervisors) concentrate on repair and recovery
Together, the business actor and the supervisor… comprise the code that might be in a more traditional process or object When one actor crashes… the whole system does not crash
One dead actor does not kill them all<br>
slide30. Persistence More “Let it crash” coding style Address of an actor does not change on re-start
Mailbox persists over many actor instantiations (of one address) “outside” world still sends messages successfully X fails<br>
slide31. Pros and Cons Good and Bad in the Actor Model Pros of the Actor Model
Easy to scale
Fault Tolerant
No shared state
Enforce encapsulation without needing locks
The exact order in which messages are processed in large systems is not controllable by the coder, but this is also not intended
Cons of Actor Model
Mailbox can overflow
Susceptible to deadlocks
Naturally, the exact order in which messages are processed in large systems is not controllable by the coder, but this is also not intended<br>
slide32. Actor Best Practices How to apply the model in specific languages Actors should be like nice co-workers: do their job efficiently without bothering everyone else needlessly and avoid hogging resources. Translated to programming this means to process events and generate responses (or more requests) in an event-driven manner. Actors should not block (i.e. passively wait while occupying a Thread) on some external entity—which might be a lock, a network socket, etc.—unless it is unavoidable
Do not pass mutable objects between actors. In order to ensure that, prefer immutable messages. If the encapsulation of actors is broken by exposing their mutable state to the outside, you are back in normal Java concurrency land with all the drawbacks.
Actors are made to be containers for behavior and state, embracing this means to not routinely send behavior within messages (which may be tempting using Scala closures). One of the risks is to accidentally share mutable state between actors, and this violation of the actor model unfortunately breaks all the properties which make programming in actors such a nice experience.
The top-level actor of the actor system is the innermost part of your Error Kernel, it should only be responsible for starting the various sub systems of your application, and not contain much logic in itself, prefer truly hierarchical systems. This has benefits with respect to fault-handling (both considering the granularity of configuration and the performance) and it also reduces the strain on the guardian actor, which is a single point of contention if over-used.<br>
slide33. END<br>
slide35. Models: Shared Data State Diagram<br>
slide36. Models: Shared Data State Diagram<br>
slide37. Notes Comments and Notes https://en.wikipedia.org/wiki/History_of_the_Actor_model According to Hewitt (2006), the Actor model is based on physics in contrast with other models of computation that were based on mathematical logic, set theory, algebra, etc. Physics influenced the Actor model in many ways, especially quantum physics and relativistic physics. One issue is what can be observed about Actor systems. The question does not have an obvious answer because it poses both theoretical and observational challenges similar to those that had arisen in constructing the foundations of quantum physics. In concrete terms for Actor systems, typically we cannot observe the details by which the arrival order of messages for an Actor is determined (see Indeterminacy in concurrent computation). Attempting to do so affects the results and can even push the indeterminacy elsewhere. e.g., see metastability in electronics. Instead of observing the insides of arbitration processes of Actor computations, we await the outcomes. https://en.wikipedia.org/wiki/Tuple_space https://en.wikipedia.org/wiki/Linda_(coordination_language)<br>
Computer Science Dept
UNC Chapel Hill<br>
slide2. The Actor concurrency model<br>
slide3. Models: Actors Carl Hewitt, 1973 Alternative to Shared State concurrency model An “actor” is the universal primitive of concurrent computation
Actors have private state, but do not share state with other actors
No need for lock-based synchronization
Actors communicate via message passing
When an actor receives a message it can:
make local decisions
create more actors
determine how to respond to the next message
Hewitt claims the actor model was inspired by laws of physics, and depends on them for its fundamental axioms
Includes general relativity, and quantum mechanics<br>
slide4. References YouTube videos Hewitt et al. discuss the actor model Talking concurrency with Carl Hewitt
Hoare, Hewitt, and Armstrong interview
The Actor model
Intro to the Actor model for concurrent computation
Actor model and the Queue Other documents…
Actors for distributed programs
Actors in Scala and Erlang ( I have borrowed from these )<br>
slide5. Actor Model of Concurrency Summary of the New View Don’t communicate by sharing memory, rather share memory (data) by communicating What can actors do? Hewitt’s summary… in video
“Process” ( compute something for local state )
Create new actors
Receive messages, then in response:
Make local decisions (alter local state)
Do arbitrary side-effecting actions (print)
Send messages
Respond to sender 0 or more times
Process exactly one message at a time<br>
slide6. Concurrency Models Message passing Workers share no data directly or, message channels are standalone entities that Communicate with message channels (often called mailboxes) unique to each computation agent computation agents send and receive from Erlang<br>
slide7. Other Concurrency Models Old and New Processes (heavy weight, OS support)
Threads (lighter weight, popular with multi-cores, OS support)
Co-routines (early idea, assumes one CPU)
Communicating Sequential Processes (CSP)
Process calculi
Petri nets (Place/Transition nets)
Dataflow
Tuple spaces
Futures (promises)
Actors<br>
slide8. Development of the Model https://en.wikipedia.org/wiki/History_of_the_Actor_model According to Hewitt the actor model is based on physics in contrast with other models of computation ( that were based on mathematical logic, set theory, algebra, etc.)
One issue is what can be observed about Actor systems.
The question does not have an obvious answer because it poses both theoretical and observational challenges similar to those that had arisen in constructing the foundations of quantum physics.
In Actor systems, typically we cannot observe the details by which the arrival order of messages for an Actor is determined (called indeterminacy ).
Attempting to do so affects the results and can even push the indeterminacy elsewhere. e.g., see metastability in electronics. Instead of observing the insides of arbitration processes of Actor computations, we await the outcomes.
Appropriate for modern multi-cores, as they work with arbiter circuits (“random selectors) A fundamental change in the Actor model is that it did not provide for global states
A computational step could not be defined as going from one global state to the next global state as had been done in all previous models of computation.<br>
slide9. Indeterminacy Indeterminacy is more like: impossible to even define interleavings
This arises in actors from message passing… message sending
A message can take an arbitrarily long amount of time to be delivered
There is (from physical machines hosting actors) actual simultaneity that cannot be viewed as interleaving Indeterminacy is defined as different from non-determinacy
Other concurrency models seems to think of concurrency as interleaving of events that could be all done in sequence, but are shuffled together… and we cant predict the shuffle
Interleaved concurrent events on critical regions gives rise to race conditions, for example
Different interleavings … some work, some conflict, all from one collection of concurrent processes<br>
slide10. Models: Message Passing Diagram<br>
slide11. Models: Message Passing Diagram<br>
slide12. Models: Message Passing Diagram<br>
slide13. Actors As noted, an actor is the primitive unit of computation An actor is the thing that receives a message and does some kind of computation based on it.
The idea is very similar to what we have in object-oriented languages:
an object receives a message (a method call)
it then does something depending on which message it receives (which method we are calling)
The main difference is that actors are completely isolated from each other and they will never share memory
It’s also worth noting that an actor can maintain a private state that can never be changed directly by another actor
Java allows one object to change another’s state ( public fields )
however, we consider this poor OO style (use private fields, getters/setters)<br>
slide14. Actors vs. Objects An actor is dynamically created (an actor can create more actors)
An actor exists as long as there are statements/expression to eval.
In Erlang (one actor implementation) this means tail recursion to make longer-lived actors
An actor ends when its code is fully executed
An actor can also end when it self-terminates… or is terminated by another actor
In OO languages, like Java, an object can be dynamically created, but only according to scope rules
Objects… once created… exists until the enclosing scope is done
Then an object either has a reference passed to some other scope
Or, it is cast adrift (ends) in the heap and garbage collected<br>
slide15. Comparative Example Checking Account Shared account balance (one balance total)
Multiple concurrent users making deposits and withdrawals
Traditional approach in shared-state model (like Java threads) requires locking and coordination to prevent race conditions, overdrawing Consider this scenario
Balance of $80.00
Person A wants to withdraw $60.00
Person B wants to withdraw $50.00<br>
slide16. Example: Checking Naive approach struct Checking { // a shared resource
balance int
}
. . . // withdrawal code
if (Checking.balance >= withdrawal.amount) {
Checking.balance -= withdrawal.amount;
return true; // success
}
else {
return false; // cant do it, NSF
}<br>
slide17. Example: Checking Interaction Diagram
with
one User<br>
slide18. Example: Checking Interaction Diagram
with
Two check writers<br>
slide19. Example: Checking Locking (threads) approach struct Checking { // a shared resource
balance int
lock Mutex // mutual exclusion lock
}
. . . // check cashing code
Checking.lock.Lock();
boolean successFlag = false;
if (Checking.balance >= withdrawal.amount) {
Checking.balance -= withdrawal.amount;
successFlag = true; // check is covered
}
Checking.lock.Free();
return successFlag;<br>
slide20. Example: Checking Actor approach actor Checking { // now not shared
var balance = 80
receive = {
case Withdrawal(amt) =>
if (balance >= amt) {
balance -= amt;
sender sendMsg true; // success
}
else {
sender sendMsg false; // cant do it, NSF
};
. . .
}
. . .
}<br>
slide21. Example: Checking Interaction Diagram
with an actor as account and
two actors writing checks<br>
slide22. Distribution Is an actor remote? Or local? It doesn’t matter
Another aspect of the actor model is that it doesn’t matter if the actor that I’m sending a message to is running locally or in another node.
If an actor is just a unit of code with a mailbox and an internal state, and it just responds to messages, it doesn’t alter the functional performance to have the code running on some specific machine
As long as we can make the message get there we are fine; timing performance might be affected (slow network, slow CPU vs. fast, etc.) but it will still compute the same function
So the model does not have a notion of local or remote… one machine or many… in the abstract it is not there
This allows us to create systems that leverage multiple computers and helps us to recover if one of them fail.<br>
slide23. Basics of Messages Message send No channel or intermediary with semantics (like CSP)
Best-effort delivery
At-most-once delivery
Messages can take arbitrarily long to be delivered (no timeout, or clock)
No message orderings guaranteed (can arrive “out of order”)
No automatic responses, no blocking waiting<br>
slide24. Addresses Location where message is sent Address “semantics” or decoding is not part of the model
Identifies an actor (process in Erlang)
May also represent a proxy or fan-out to other actors
Contains location and transport information
Gives location transparency (same process, diff process same machine, different local machine core, different network node)<br>
slide25. Addresses One address, many actors Address may represent a “pool” of many actors<br>
slide26. Addresses One actor, many addresses Address may be an “alias”, one of several<br>
slide27. Fault tolerance Actor model allows a “Let it crash” coding style Erlang introduced the “let it crash” philosophy. The idea is that you shouldn’t need to program defensively, trying to anticipate all the possible problems that could happen and find a way to handle them, simply because there is no way to think about every single failure point.
What Erlang does is simply letting it crash, but make this critical code be supervised by someone whose only responsibility is to know what to do when this crash happens (like resetting this unit of code to a stable state), and what makes it all possible is the actor model.
Every code run inside a process (that is basically how Erlang calls its actors). This process is completely isolated, meaning its state is not going to influence any other process. We have a supervisor, that is basically another process (everything is an actor, remember?), that will be notified when the supervised process crashes and then can do something about it.
This makes it possible to create systems that “self heal”, meaning that if an actor gets to an exceptional state and crashes, by whatever reason, a supervisor can do something about it to try to put it in a consistent state again (and there are multiple strategies to do that, the most common being just to restart the actor with its initial state).<br>
slide28. Fault tolerance: Supervision Actor model allows a “Let it crash” coding style Running state of an actor is monitored and managed by another actor
Called a supervisor
Can perform actions based on state on the monitored actor (was there an unhandled error? What caused the termination?)
Maybe restart the monitored actor…
Supervisor… who watches it? Supervisor trees<br>
slide29. Fault tolerance Other notes and issues Supervisor handling error conditions, rather than having the actor that crashes anticipate and manage them all… is a bit like AOP… separation of concerns
Actors concentrate on “business logic”
Special other actors (supervisors) concentrate on repair and recovery
Together, the business actor and the supervisor… comprise the code that might be in a more traditional process or object When one actor crashes… the whole system does not crash
One dead actor does not kill them all<br>
slide30. Persistence More “Let it crash” coding style Address of an actor does not change on re-start
Mailbox persists over many actor instantiations (of one address) “outside” world still sends messages successfully X fails<br>
slide31. Pros and Cons Good and Bad in the Actor Model Pros of the Actor Model
Easy to scale
Fault Tolerant
No shared state
Enforce encapsulation without needing locks
The exact order in which messages are processed in large systems is not controllable by the coder, but this is also not intended
Cons of Actor Model
Mailbox can overflow
Susceptible to deadlocks
Naturally, the exact order in which messages are processed in large systems is not controllable by the coder, but this is also not intended<br>
slide32. Actor Best Practices How to apply the model in specific languages Actors should be like nice co-workers: do their job efficiently without bothering everyone else needlessly and avoid hogging resources. Translated to programming this means to process events and generate responses (or more requests) in an event-driven manner. Actors should not block (i.e. passively wait while occupying a Thread) on some external entity—which might be a lock, a network socket, etc.—unless it is unavoidable
Do not pass mutable objects between actors. In order to ensure that, prefer immutable messages. If the encapsulation of actors is broken by exposing their mutable state to the outside, you are back in normal Java concurrency land with all the drawbacks.
Actors are made to be containers for behavior and state, embracing this means to not routinely send behavior within messages (which may be tempting using Scala closures). One of the risks is to accidentally share mutable state between actors, and this violation of the actor model unfortunately breaks all the properties which make programming in actors such a nice experience.
The top-level actor of the actor system is the innermost part of your Error Kernel, it should only be responsible for starting the various sub systems of your application, and not contain much logic in itself, prefer truly hierarchical systems. This has benefits with respect to fault-handling (both considering the granularity of configuration and the performance) and it also reduces the strain on the guardian actor, which is a single point of contention if over-used.<br>
slide33. END<br>
slide35. Models: Shared Data State Diagram<br>
slide36. Models: Shared Data State Diagram<br>
slide37. Notes Comments and Notes https://en.wikipedia.org/wiki/History_of_the_Actor_model According to Hewitt (2006), the Actor model is based on physics in contrast with other models of computation that were based on mathematical logic, set theory, algebra, etc. Physics influenced the Actor model in many ways, especially quantum physics and relativistic physics. One issue is what can be observed about Actor systems. The question does not have an obvious answer because it poses both theoretical and observational challenges similar to those that had arisen in constructing the foundations of quantum physics. In concrete terms for Actor systems, typically we cannot observe the details by which the arrival order of messages for an Actor is determined (see Indeterminacy in concurrent computation). Attempting to do so affects the results and can even push the indeterminacy elsewhere. e.g., see metastability in electronics. Instead of observing the insides of arbitration processes of Actor computations, we await the outcomes. https://en.wikipedia.org/wiki/Tuple_space https://en.wikipedia.org/wiki/Linda_(coordination_language)<br>