3 ms·
>Does the actor model make it easy to write concurrent spaghetti code? I think you could write concurrent spaghetti code in any environment just as easily, to
by fearthetelomere 6y ago
>Does the actor model make it easy to write concurrent spaghetti code?
I think you could write concurrent spaghetti code in any environment just as easily, to be honest. I also think it really depends on which actor-model implementation you're using. Each offers their own unique experience, as I've found that there's a noticeable difference in the Scala+Akka approach vs Elixir+OTP, for example.
The actor model is just one way of reasoning about concurrency. Your mailbox serializes/linearizes your interactions so you don't necessarily need locks. Because you get that for free, you can write "single-threaded" code to handle each message. Messages can be sent across a network, so now you can concurrently interact with remote actors too. The simplicity of the model stops there.
Concurrent code gets complicated very quickly by nature. If you architect your application to have messages causing ripple effects in your system, your application behavior is going to be very difficult to reason about. But that's nothing new, either. Instead of sending messages, concurrent function calls could produce the same issue.
Is reasoning about message flows and behaviors hard? Yes, but that's just a byproduct of concurrency. Like you said, "programmers simply need to use discipline", but everyone will disagree on what discipline looks like. Each actor model implementation will have their "best practices" to mitigate the complexity of concurrent interactions, as will each organization using said implementation for their project. At my work, the way we use Scala+Akka was very structured and not at all the way I expected, having been used to Elixir+OTP.
As I think the issues of the actor model are actually issues with reasoning about concurrency in general, I would pick actors over coroutines just for the up-front structure and simplicity it provides.
- toast0 6y ago> Is reasoning about message flows and behaviors hard? Yes, but that's just a byproduct of concurrency. Maybe it's just me, but message flows and behavior is a lot easier to think about than concurrent access to shared state. In an Erlang world, it's fairly easy to look at a process's code and see what it does for each message it might get, and think about if it's correct or not. Of course, it's sometimes hard to see which messages it might get from other code; also the system behavior can be a challenge to understand at times, although generally going back to making sure each actor does the right thing, and making sure the right number are running gets you most of the way there. With the re-entrant Swift actors proposed in the link, I'm not so sure it will be easy. It looks like if your actor A's method needs to call another actor and read the response, that opens actor A to running other methods between the call and response; that makes it hard to think about again.
- fearthetelomere 6y ago>Maybe it's just me, but message flows and behavior is a lot easier to think about than concurrent access to shared state. In general, I agree with you. I think the actor model makes concurrent code readable, understandable, and potentially easier to maintain due to that readability. People inexperienced with concurrency might find it a lot more approachable than the alternatives; an actor feels so similar to an object at times and are easier to use than threads or green-threads. I've personally found that actors also give me performance right out of the box, when I could have really screwed something up with locking. But other than that, doesn't it all boil down to the same thing? Actors serialize access/modification of state through ordered messages. Locks serialize access/modification of state through acquiring the lock. And when you have complex access or modification patterns in your code, the actor model doesn't help all that much. To me it's like each message acquires the lock to the actor state. Sure, you can see what messages cause what behavior or state change for an actor A, but the rest of the system can still be complex. Which actors send a given message to actor A, and why or when do they do this? It's not obvious, and the same complexity can be found in event-driven systems, lock-y threaded systems, etc. >With the re-entrant Swift actors proposed in the link, I'm not so sure it will be easy. I'm personally not convinced about re-entrant actors. I don't see why you'd give up the guarantee of blocking until a message is fully handled. They state: "Moreover, it helps ensure that actors can make timely progress within a concurrent system, and that a particular actor does not end up unnecessarily blocked on a long-running asynchronous operation (say, downloading a file)." This example is particularly weak in my opinion. An actor could block on the long-running async operation, of course. But isn't that the brute-force/naive approach? I can't see any one doing this in practice. You could create a long-running actor whose only job is to sequentially handle requests to download files and send the file descriptors back as a message response. You could create a worker pool to do that as well. You could decompose your one actor into a staged pipeline of actors, so any requests that block can go to one pipeline, and other swiftly handled messages can go to another pipeline. Of course, none of these are simple solutions, but neither is designing an actor with re-entrancy in mind. >that makes it hard to think about again I agree. However, they do seem to be pushing the envelope and not just reinventing the wheel. I'm curious to see how this plays out.