7.6. 我们的能力边界

正如你所见,编写并行与并发代码远非“随心所欲地编写,然后交给工具和编译器处理”那么简单。也许未来借助人工智能的发展,我们能够实现这种理想状态,但从目前使用编程助手的经验来看,这一天似乎还很遥远。

实际上,你必须根据所选择的编程模型来结构化代码。如果你一开始将整个代码库设计为单线程应用,并且没有采用函数式或消息驱动的设计结构,那么后期想要改造它将会非常困难。我观察到对象与 actor 之间存在某种对应关系 —— 理论上或许可以将每个对象转化为一个 actor,把每个方法调用转化为一条事件消息,但这种想法过于理想化。现实中,当我们尝试将同步系统改为基于事件的系统时,往往会面临大量问题,其中许多不仅难以调试,还需要对 actor 模型及其具体实现框架有深入理解。

因此,最佳实践是:在你选择的编程范式内重新设计整个应用 —— 要么采用以数据为中心的函数式编程范式,要么选择以行为为核心的 actor 模型。

在以数据为中心的范式中,开发者需要关注输入数据以及达成预期输出所需的转换过程。每个转换步骤都应遵循不可变性原则 —— 即接收输入数据后返回新的数据结构,而不修改原始数据。正如我们之前看到的那样,这类操作天然具备可并行化特性,非常适合现代多核架构下的高效执行。

有时我们需要自定义算法或优化现有实现,此时仍可沿用相同的函数式模式进行开发。通过结合执行策略(execution policy)进行微调,我们可以构建出高度可定制、易于测试和优化的并行系统。

而以行为为核心的范式则将对象视为接收消息的 actor,这更接近 Alan Kay 对面向对象编程的原始构想(其核心思想可参考https://www.purl.org/stefan_ram/pub/doc_kay_oop_en的邮件论述)。这种不强调类结构、而聚焦于消息传递的编程风格,在 Smalltalk 中得到了最纯粹的体现。

在 actor 模型中,开发者需要围绕 actor 及其消息机制从头构建应用程序,并验证其行为是否符合预期。这要求对各种 actor 类型、消息传递机制以及错误处理方式有深刻理解,才能选择最适合当前问题域的设计方案。

如前面示例所示,actor 并不能保证执行顺序 —— 这一特性可能会成为系统设计中必须考虑的关键因素。虽然 actor 模型提供了良好的扩展性和解耦能力,但也带来了更高的复杂度和调试难度。

由此构建的系统通常具备高度可扩展性,但理解和调试的难度也随之增加。这意味着我们无法自动地将一个同步、单线程的应用直接转换为并行或并发系统。大多数情况下,这样的迁移都需要进行架构级别的重构,而非简单的代码调整。

因此,面对并行与并发编程的挑战,我们必须从一开始就做出明智的设计决策,并在开发早期阶段就考虑系统的可扩展性和可并行性。这不仅是技术层面的选择,更是软件工程思维的一次跃迁。