并发、交互、可变性,三选其二。
Concurrency, interactivity, mutability, choose two

原始链接: https://www.n16f.net/blog/concurrency-interactivity-mutability-choose-two/

软件开发通常需要在三个理想特性之间权衡:**并发性**(并行执行)、**交互性**(运行时检查/修改)和**可变性**(直接数据操作)。将这三者结合会产生危险的竞争条件,例如在其他线程活跃时,从 REPL 修改共享的 Lisp 哈希表。 为了管理这种复杂性,编程语言通常会牺牲其中之一: * **舍弃交互性:** Rust 或 C 等语言通过消除实时运行时修改,优先考虑性能和线程安全。虽然效率很高,但调试复杂且长期运行的系统会变得困难。 * **舍弃并发性:** Python 和 Ruby 等语言使用全局解释器锁(GIL)。这使得开发过程安全且具有交互性,但限制了性能,因为该锁强制对数据进行串行访问。 * **舍弃可变性:** Erlang 等语言偏向于消息传递和不可变性。这确保了线程安全并支持交互性,但不断的数据复制引入了显著的性能开销。 归根结底,世上没有“免费的午餐”。开发者必须根据项目的优先级选择语言,并接受 Common Lisp 这种“鱼与熊掌兼得”的能力,同时也伴随着在实时操作期间意外导致系统不稳定的持续风险。

Sorry.
相关文章

原文

You have a Common Lisp program running, multiple threads listening for network connections, others processing data in the background. You were really careful making sure that there are no data access conflicts. Concurrency, check. You use Emacs and SLIME to connect to your server, inspect its state. Interactivity, check. You realize a global hash table contains an incorrect value; no problem, just evaluate a simple REMHASH expression. Mutability, check. Unfortunately another thread happens to read the hash table while the form is being evaluated. Game over.

You want concurrency, loosely defined as “having multiple execution threads making progress in the same unit of time”, because it allows for faster programs, processing more data faster. But now your programs are much more complex because you need to protect data accesses to avoid deadlocks, starvation, memory corruption and other unpleasant consequences.

You want interactivity, because it is so convenient to be able to inspect and update the state of your program at any time. But now you can access any data whenever you want, data that may be read or written by concurrent threads.

You want mutability, because it is practical.

But you cannot have it all.

Dropping interactivity

The most popular choice is to drop interactivity: since there is no one to randomly access runtime data, there are no concurrency issues. C, Rust, Go, the list of languages that go this way is long.

You are efficient and safe, but you lose the ability to work with your systems during execution. Frustrating during development, or to debug and fix complex systems where restarting is not always an option.

Dropping concurrency

You can still have multiple threads of course, but nothing in your program can be read or written at the same time. Python and Ruby are good examples of languages that drop concurrency by using a GIL (Global Interpreter Lock).

You can execute multiple concurrent threads, but any access to Python or Ruby data are protected by a global lock. You can run your program in a Python shell or with Pry in Ruby, reading and writing data at will in a safe way.

Unfortunately it is slow. Most concurrent programs only require synchronization for a tiny subset of all data accesses; tiny on purpose since synchronization is slow. Locking all accesses has a very real cost that must be paid.

Dropping mutability

What if you could not really change data? Instead of reading the value of a variable, you could have the runtime systematically (and safely) copy the value and return it to you. Threads would communicate by, again, sending copies of values. Of course you cannot go and directly modify any value anywhere; but still the message-based approach means you can still safely update state at any time by sending messages to threads.

Erlang made this choice, and on paper it solves a lot of problems. You can run threads (“processes” in Erlang) that operate concurrently safely since they communicate with messages containing copied values instead of accessing shared memory. And you can interact with the system at will.

But again, there is no free lunch: copying data is slow. Very slow. Of course you can optimize data representations, avoid copying binary blobs (they are ref-counted in Erlang) because it would be untenable. But it will still be horribly slow.

You cannot have it all

As usual choosing a language is choosing a set of trade-offs. Most performance-sensitive programs drop interactivity so that they can limit shared data accesses as much as possible. Developers wanting the convenience of interactivity have to tolerate the extra cost of a global lock, or of an immutable data model.

Common Lisp developers will of course brag about how they can have it all, but they will always have to keep in mind that anything they do in their REPL can potentially break their program.

联系我们 contact @ memedata.com