多线程在 Cadence/Temporal 工作流程中如何工作?

Lon*_*eng 4 cadence-workflow temporal-workflow uber-cadence

在 Cadence/Temporal 工作流程编程中:

  • 不允许使用本机线程库。例如,在Java中,线程必须通过Async.procedure或创建,Async.function而在Golang中,线程必须通过 创建workflow.Go。所以为什么?
  • 是否存在像使用本机线程那样的竞争条件?例如,应该使用Hashtableor来代替线程安全?ConcurrentHashMapHashMap

Lon*_*eng 7

概括:

工作流执行必须是确定性的。这是历史重放以重建线程状态所必需的。为了确定性,Cadence/Temporal 以协作方式控制线程调度(而不是像大多数操作系统那样抢占式):

  • 任一时间点只能运行一个工作流线程
  • 仅当当前正在执行的工作流线程因其他原因而阻塞时,它才会让步并让下一个工作流线程运行。
  • “下一个工作流程线程”的顺序是确定的。

所以:

  • 工作流代码中永远不允许使用本机线程库,因为 Cadence/Temporal 将失去对确定性的控制
  • 由于协作多线程,我们通常遇到的赛车情况永远不会发生。HashMap在工作流代码中可以安全使用。

更多细节

Cadence/Temporal SDK 有一个DeterministicRunner 来操纵线程执行。例如Java SDKGolang SDK。这个确定性运行程序将决定以正确的顺序运行哪个工作流线程,并且一次运行一个。对于每个决策任务,它将循环执行,直到“所有线程都被阻塞”——RunUntilAllBlocked/ExecuteUntilAllBlocked。

Async.procedure//Async.functionworkflow.Go创建一个新的线程,并添加到definisticRunner中的链表中,这样执行就会受到控制。

因为任何时候只能执行一个线程,所以我们在常规代码中遇到的大多数竞争情况都不会发生。

然而,这并不意味着根本不存在赛车条件。在某些情况下,仍然会有一些条件导致一些死锁。

换句话说,合作并不意味着没有竞争条件或僵局。这只是意味着僵局的情况要少得多。并且不存在像抢占式线程调度那样导致脏读的竞争情况。

死锁示例

如果线程 A 获取锁 A 并等待活动,则它让位于线程 B,然后线程 B 获取锁 B 并等待活动;

活动结束后,线程A会在释放lockA之前尝试获取lockB,threadB会在释放lockA之前尝试获取lockA;

现在,当活动完成时,他们将陷入僵局。

其他的

使用iWF将使您远离这些复杂的概念。iWF 在 Cadence/Temporal 之上提供了一个很好的抽象,但保持相同的功能。

更多参考

https://community.temporal.io/t/how-does-workflow-thread-synchronization-work/504