Question

Von neumann architecture allows sequential processing of instructions. So, a single core within a CPU executes instructions sequentially.

Consider, OS providing 1-1 threading model(here) in a multi-core processor system,

Properties of concurrent system

  • Multiple actors(say, each thread assigned to a different core)
  • Shared resource(heaps, global variables, devices)
  • Rules for access(Atomic/Conditional synchronization)

With atomic synchronization mechanism(Lock) or conditional synchronization mechanism(say Semaphore), messages are indirectly passed between actors, that help compliance with rules for accessing shared resource.

In this answer, it says, The actor model helps to force you to program concurrent portions of your code as self contained nuggets that can be performed in parallel and without depending on another piece of code.

Question:

To help understand the difference between concurrent vs actor model,

Using actor model, How can one program concurrent portion(critical section) of code as self contained nuggets?

Était-ce utile?

La solution

It's probably best to think of the actor model as the ancestor of object oriented programming; and compare it to OOP. However, because (I hope) you're already familiar with OOP I'm going to do the reverse.

For OOP, imagine you have a simple object with code vaguely like this:

integer currentValue = 0;

void incrementMethod(void) {
    currentValue = currentValue + 1;
}

void decrementMethod(void) {
    currentValue = currentValue - 1;
}

integer getValueMethod(void) {
    return currentValue;
}

void setValueMethod(integer value) {
    currentValue = value;
}

This has problems with concurrency, because multiple threads/CPUs could execute the methods at the same time and mess each other up. The "actor model" solution is to prevent direct access to the methods and provide isolation between "things" (objects/actors). The consequence is that you need some form of communication between the isolated things. That communication is message passing.

The silly example above could become:

integer currentValue = 0;

void main(void) {
    while(running) {
        message = getMessage();
        switch(messsage->type) {
            case INCREMENT_REQUEST:
                incrementMethod();
                break;
            case DECREMENT_REQUEST:
                decrementMethod();
                break;
            case GET_VALUE_REQUEST:
                getValueMethod(message->senderID);
                break;
            case SET_VALUE_REQUEST:
                setValueMethod();
                break;
            default:
                sendMessage(senderID, UNKNOWN_REQUEST, NULL);
        }
    }
}

void incrementMethod(void) {
    currentValue = currentValue + 1;
}

void decrementMethod(void) {
    currentValue = currentValue - 1;
}

void getValueMethod(messagePort senderID) {
    sendMessage(senderID, GETVALUE_REPLY, currentValue);
}

void setValueMethod(integer value) {
    currentValue = value;
}

Because the messaging serialises requests and because no data is shared between threads, no other form of concurrency control is necessary. Because no data is shared between threads, it's also highly distributable (e.g. you can have a system where different actors are on different physical computers). Because of the isolation between actors it's "more possible" (at least in theory) to support things like live update (e.g. by replacing an actor's code while the rest of the system is running) and fault tolerance (e.g. triple redundancy with 3 actors on 3 different computers instead of one). Finally, because an actor is able to accept an unknown request it's more extensible (e.g. you could send an ADD_VALUE_REQUEST message to an actor that it doesn't understand, and then make the actor understand that message type afterwards).

The main problem with (the naive implementation of) the actor model is performance - with one thread per actor plus the overhead of message passing the performance (as compared to OOP) would be extremely bad. There are multiple ways of fixing that, but the most common ways are to use many actors per thread and to allow actors within the same thread to call each other's methods directly (and avoid message passing in that case). With this in mind, you could define the origins of OOP as "actor model with only one thread where all actors can call each other's methods directly" (realising that multi-threading got retrofitted back on top of OOP later).

The other thing I need to point out is that for my example I'm being intentionally "low level" to help you understand what is happening behind the scenes. For a language designed for the actor model, the message handling loop shown above (main() in its entirety) would be implicit and not explicit; to save the programmer from typing so much stuff, and also so that the compiler can do the optimisations I mentioned.

Autres conseils

Eliminate the critical section

Divide the work into individual parts in such a way that they no longer require concurrent access to a shared memory pool. This is inevitable, since actors can and often do execute in their own process.

One way to do this is to rewrite the code so that it is functionally pure, rather than mutable, object-oriented code. Functional programming, by its very nature, demands referential transparency. Referentially-transparent functions can be easily parallelized, because there is no critical section.

If your actors must still share a state space, use a database with ACID capabilities. It will take care of the concurrency and locking issues for you.

Licencié sous: CC-BY-SA avec attribution
scroll top