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
需要保护,但不能完全确定。)
众所周知,完全通用的优化多线程代码很难编写。
最简单的解决方案通常是使用锁来保护并发修改,如您提供的示例所示:(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上)来优化执行速度。
它有一些警告:
它只能在事务感知类型,tvar
,tcell
,tcons
,tlist
,tmap
,tfifo
,tchannel
与定义,类(stmx:transactional (defclass ...))
或结构与定义(stmx:transactional (defstruct ...))
(stmx:atomic ...)
如果多个线程之间发生冲突,则内部代码可能会多次重试,因此不应执行I / O。
它通常比使用原子操作的手动优化代码慢,但编写起来却容易得多,这还因为它是可组合的:(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错误,但确切的版本需要解决此问题。