Lock
是一个低级别的并发控制构造。它提供互斥,这意味着一次只能有一个线程持有锁。一旦锁被解锁,另一个线程就可以锁定它。
Lock
通常用于保护对一个或多个状态部分的访问。例如,在此程序中
my = 0;my = Lock.new;await (^10).map:say ; # OUTPUT: «10»
Lock
用于保护对 $x
的操作。增量不是原子操作;如果没有锁,两个线程都可能读取数字 5,然后都将数字 6 存储回,从而丢失更新。使用 Lock
时,一次只能有一个线程运行增量。
Lock
是可重入的,这意味着持有锁的线程可以在不阻塞的情况下再次锁定它。该线程必须解锁相同次数,然后另一个线程才能获取锁(它的工作原理是保持递归计数)。
重要的是要理解,Lock
与任何特定数据部分之间没有直接联系;由程序员确保在涉及相关数据的操作期间始终持有 Lock
。OO::Monitors
模块虽然不是此问题的完整解决方案,但确实提供了一种避免显式处理锁并鼓励采用更结构化方法的方法。
Lock
类由操作系统提供的构造支持,因此,从操作系统的角度来看,等待获取锁的线程被阻塞。
使用高级 Raku 并发构造的代码应避免使用 Lock
。等待获取 Lock
会阻塞一个真正的 Thread
,这意味着线程池(由多个高级 Raku 并发机制使用)在此期间无法将该线程用于其他任何用途。
持有 Lock
时执行的任何 await
都将以阻塞方式运行;await
的标准非阻塞行为依赖于在不同的 Thread
上恢复 `await` 之后的代码,这与 Lock
必须由锁定它的同一线程解锁的要求不兼容。有关没有此缺点的替代机制,请参阅 Lock::Async
。除此之外,主要区别在于 Lock
主要映射到操作系统机制,而 Lock::Async
使用 Raku 原语来实现类似的效果。如果您正在执行低级别操作(本机绑定)和/或实际上想要阻塞真正的操作系统线程,请使用 Lock
。但是,如果您想要一个非阻塞互斥,并且不需要递归并且正在 Raku 线程池上运行代码,请使用 Lock::Async。
Lock
本质上不可组合,并且如果出现对锁的循环依赖,就有可能导致挂起。更喜欢对并发程序进行结构化,以便它们传达结果而不是修改共享数据结构,使用诸如 Promise
、Channel
和 Supply
之类的机制。
方法§
方法 protect§
multi method protect(Lock: )
获取锁,运行 &code
,然后释放锁。即使代码因异常而退出,也要确保释放锁。
请注意,Lock
本身需要在代码中被线程化和需要保护的部分之外创建。在下面的第一个示例中,Lock
首先被创建并分配给 $lock
,然后在 Promise
内部使用它来保护敏感代码。在第二个示例中,出现了一个错误:Lock
直接在 Promise
内部创建,因此代码最终会产生一堆独立的锁,在许多线程中创建,因此它们实际上并不能保护我们想要保护的代码。
# Right: $lock is instantiated outside the portion of the# code that will get threaded and be in need of protectionmy = Lock.new;await ^20 .map:# !!! WRONG !!! Lock is created inside threaded area!await ^20 .map:
方法 lock§
method lock(Lock:)
获取锁。如果当前不可用,则等待。
my = Lock.new;.lock;
由于 Lock
是使用操作系统提供的设施实现的,因此等待锁的线程在锁可供其使用之前不会被调度。由于 Lock
是可重入的,因此如果当前线程已经持有锁,则调用 lock
只会增加递归计数。
虽然使用 lock
方法很容易,但正确使用 unlock
却比较困难。相反,最好使用 protect
方法,它负责确保 lock
/unlock
调用始终同时发生。
方法 unlock§
method unlock(Lock:)
释放锁。
my = Lock.new;.lock;.unlock;
确保始终释放 Lock
非常重要,即使抛出异常也是如此。确保这一点的最安全方法是使用 protect
方法,而不是显式调用 lock
和 unlock
。如果没有,请使用 LEAVE
相位器。
my = Lock.new;
方法 condition§
method condition(Lock: )
将条件变量作为 Lock::ConditionVariable
对象返回。查看 这篇文章 或 维基百科,了解有关条件变量及其与锁和互斥体的关系的背景信息。
my = Lock.new;.condition;
当您希望与锁的交互比简单获取或释放锁更复杂时,您应该在锁上使用条件。
constant ITEMS = 100;my = Lock.new;my = .condition;my = 0;my = 0;my = 1..ITEMS;my = 0 xx ITEMS;loop ( my = 0; < ; ++ ).protect();say .map: ;# OUTPUT: «2* 5* 10 17* 26 37* 50 65 82 101* … »
在本例中,我们使用条件变量 $cond
等待,直到所有数字都生成并检查,并且还 .signal
到另一个线程,以便在特定线程完成后唤醒它。