在多线程代码(通用Lisp)中使用库函数

dav*_*ugh 1 multithreading locking common-lisp shared-libraries

当可以从多个线程访问/更新变量时,通常需要保护它免受同时更改的影响。一种有效的方法是使用原子函数来保证互斥访问。例如(sb-ext:atomic-incf *count*)。另一种方法是像这样围绕更新操作包装一个锁(bt:with-lock-held (*lock*) (incf *count*)),但这有点昂贵。

有没有一种有效的方法可以在多线程代码中包含库函数(例如来自亚历山大图书馆)?例如,如果 (alexandria:deletef x *list*)要从多个线程中执行操作?还是需要锁?(ps:我假设a deletef需要保护,但不能完全确定。)

cos*_*s72 5

众所周知,完全通用的优化多线程代码很难编写。

最简单的解决方案通常是使用锁来保护并发修改,如您提供的示例所示:(bt:with-lock-held (*lock*) (incf *count*))。在大多数情况下,性能是可以接受的。如果您对特定用例进行基准测试,并且发现它对于您的需求而言太慢,我只会考虑以下其他选项。

原子操作(sb-ext:atomic-incf *count*)是一个很底层的原语:非常快,但是很难正确地组合成更复杂的操作。如果可以将所需功能一对一映射到原子操作,则只需使用它们就可以完成。但是大多数时候,您需要组合原子操作以提供更复杂的功能-随之而来的困难是:您需要深入了解所使用的体系结构,包括(缺少)内存排序保证和内存障碍。这是一条极其艰难的道路。

我的库STMX提供了据说直观且易于编写的原语,例如(stmx:atomic (incf *count*))。它在内部使用原子操作(如果可用)和Intel TSX事务性内存CPU指令(仅在sbcl x86-64上以及仅在具有它们的Intel CPU上)来优化执行速度。

它有一些警告:

  1. 它只能在事务感知类型,tvartcelltconstlisttmaptfifotchannel与定义,类(stmx:transactional (defclass ...))或结构与定义(stmx:transactional (defstruct ...))

  2. (stmx:atomic ...)如果多个线程之间发生冲突,则内部代码可能会多次重试,因此不应执行I / O。

  3. 它通常比使用原子操作的手动优化代码慢,但编写起来却容易得多,这还因为它是可组合的:(atomic (atomic (foo) (atomic (bar))是单个事务(不是三个事务),并且等效于(atomic (foo) (bar))

在您的特定情况下,您希望像(alexandria:deletef x *list*)在多线程代码中一样使用现有的非线程安全的库调用,即要使它们成为线程安全的。

如果这些库未在内部使用全局变量并对其进行突变,则可以成功使用锁和原子操作。相反,只有在您仅修改并发事务感知类型的情况下,可以使用STMX-可以使用库提供的类型,但应由并发代码将其视为只读。

相反,如果库在内部使用和更改全局变量,则您的约束会更多,并且锁可能是唯一可行的解​​决方案。

PS请在提交STMX问题https://github.com/cosmos72/stmx/issues @Svante刚刚更新https://github.com/cosmos72/stmx/issues/14与上述评论@davipough错误,但确切的版本需要解决此问题。