Fear, and respect, the absurdly difficult challenge that is writing correct concurrent code, even when your compiler is helping you out.
Fear, and respect, the absurdly difficult challenge that is writing correct concurrent code, even when your compiler is helping you out.
It's true that in the 90s it was important to cultivate a healthy fear of concurrency in the same way that parents in Cambodia must sadly teach their children to fear landmines. However, there's nothing inherent in the problem space that dictates that the concerns of one's ancestors must be the concerns of their descendants. One day we hope the landmines in Cambodia will be cleared, just as we hope the landmines in concurrent programming will be, and I'll be thankful when that day comes.
People have stopped using Java and Go?
That's what I mean by fear and respect for concurrent programming. That's the problem that hasn't been solved.
Also asynchronous systems can deadlock as well, it is just much harder to debug as the debugger won't show an obvious system thread blocked on some system call; the deadlocked threads of execution still exist but will have been subject to CPS and hidden from view (just some callback waiting forever on some wait queue).
Two processes blocking on a socket is not a deadlock. Surely there are timeouts on both sides, because using sockets without timeouts is just ignorance, and both will just timeout and move on.
Also strictly statically declared event handlers per actor are 100% mutual blocking and deadlock free. Because they can't wait for messages in a way, that blocks other event handlers.
Using timeouts to paper over issues is just wrong. I accept that timeouts are necessary to deal with network issues (and a timeout should cause the connection to be dropped, so won't solve the deadlock issue), but certainly they are not required for in-application message passing.
Finally if an actor won't send a message untill it has received another one, I fail to see how statically declared handlers will help.
Think of it as reacting to messages, not waiting. In that model actors of course can react by sending messages, but can't have a special waiting state for specific messages, making it impossible to block other handlers. I'm not sure why this is hard to understand.
We do have this problem solved in every possible way. But it's so not a big deal with actor model, that there is no point sacrificing any flexibility for it.
Edit: BTW, not sure why you got downvoted.
I mean, I'm no expert, but I guess you could statically analize the state machine and figure out, given a set of communicating actors, which sequence of messages would lead to a global state from which there is no progress. I assume that, because message ordering is not deterministic the analysis is probably non easy to do.
Personally I suspect generalized concurrency is always going to be something that "professional" programmers deal with, and non-professionals will only get pre-canned solutions for particular problems, because the general case will always be a bit more complicated than non-specialist programmers are going to want to handle. I think concurrency is worse in terms of what you need to keep in your head than raw pointers are, and those are already too much for a non-specialist.
There are plenty of safe and easy models for writing concurrent code. Here's a famous one that's easy to overlook:
gzip -cd compressed.gz | grep error
On Unix, this doesn't use a temporary file. It creates two concurrent processes. The first decompresses a file and writes it to a pipe, and the second reads from a pipe and searches for a string of text. You could call this "coroutines over a stream," I suppose.And of course, people have been writing shell pipes for decades without concurrency errors. Unix enforces process isolation, and makes sure all the data flows in one direction.
Now, there's no reason a programming language couldn't enforce similar restrictions. For example, I've spent the last few years at work writing highly concurrent Rust code, and I've never had a single case of a deadlock or memory corruption.
One slightly trickier problem in highly concurrent systems is error reporting. In the Unix example, either "gzip" or "grep" could fail. In the shell example, you could detect this by setting "set -o pipefail". In Erlang, you can use supervision trees. In Rust, sometimes you can use "crossbeam::scope" to automatically fail if any child thread fails. In other Rust code, or in Go, you might resort to reporting errors using a channel. And I've definitely seen channel-based code go subtly wrong—but not necessarily more wrong than single-threaded error-recovery code in C.
With the right abstractions, writing concurrent code doesn't require superhuman vigilance and perfection.
The halting problem does not need to be solved, since no program in practice runs forever. In some of the environments like C we don't have a way to specify finite things, where cases would be enumerable and safety guaranteed. To be fearless sometimes greatly reduces the mental workload of programmers and people who read other people's code.
Idris can already check for totality (i.e. guarantee its programs will halt) without solving the (unsolvable) halting problem. So we're almost there!
And even in Haskell, you can write certain classes of concurrent code without thinking thanks to 1) its great RTS and concurrency libraries and 2) the fact that Haskell programs compose so well: If I have thread safe programs A and B, Haskell gives me a variety of ways to compose their results in such a way that thread safety is always preserved.