Faré wrote:
I found this very interesting discussion on sbcl-devel. It shows the difficulty of doing things right if we are to kill processes asynchronously, yet they want to do things properly wrt catching them, ensuring atomicity of memory operations, freeing resources (notably alien data), etc.
In the case of shared memory concurrency, killing processes asynchronously is a minefield; it is so difficult to do right that IMO it should not be attempted.
Fortunately, in an Erlang-like message passing language we don't have to deal with shared memory at the semantic level. The only effect that a process can have on other processes is by sending messages. That is:
- sending a message is an atomic event, - the desired semantics of asynchronous termination is that all messages that the process attempts to send before a given point in its history are sent, and no messages that it attempts to send after that point are sent.
Of course, given that we have to call code in libraries that do not just use pure message passing, implementing this model may be easier said than done. But at least it's a clean semantics to aim for.
The specific reason why asynchronous termination is harder for a language using shared memory concurrency is that, if a thread is killed while it is holding a lock on an object, that object will still be accessible to other threads afterward. At best, the invariants of the object may be violated; at worst, the invariants of the language implementation may be violated, leading to loss of dynamic type safety.
In a language using message passing concurrency, all objects are local to some process, and become inaccessible once that process is killed, so their state at that point does not matter.
At the implementation level, it's probably not a good idea to attempt to kill threads at arbitrary points; they should only be killed at safe points. Some Lisp implementations may already have a suitable notion of safe point that is used for GC, for example.