You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Common Lisp中lparallel与stmx兼容问题:如何让lparallel生成bordeaux-threads及解决方案

How to Make lparallel Work with stmx?

我最近尝试在lparallel环境中使用stmx库,执行以下代码时触发了异常:

(ql:quickload :stmx)
(ql:quickload :lparallel)
(setf lparallel:*kernel* (lparallel:make-kernel 4))
(lparallel:future (stmx:atomic (format t "~a ~%" "it works")))

报错信息如下:

Unhandled SIMPLE-ERROR in thread #<SB-THREAD:THREAD "lparallel" RUNNING {1003F8F073}>:
STMX internal error! stmx:current-thread contains a stale value: found #<SB-THREAD:THREAD "main thread" RUNNING {1004DC01F3}> expecting #<SB-THREAD:THREAD "lparallel" RUNNING {1003F8F073}>
Typical cause is: new threads were created with implementation-specific functions, as for example (sb-thread:make-thread) or (mp:process-run-function), that do not apply thread-local bindings stored in bordeaux-threads:default-special-bindings
Solution: use (bordeaux-threads:make-thread) instead.

问题根源

报错信息已经说得很清楚:lparallel默认使用SBCL原生的线程创建函数(sb-thread:make-thread),而stmx依赖bordeaux-threads(简称bt)的线程局部绑定机制来维护stmx.lang:*current-thread*变量。原生线程创建函数不会自动继承bt的*default-special-bindings*,导致新线程里的*current-thread*还是主线程的旧值,从而触发错误。

我一开始尝试了一个逻辑冗余的临时修复:

(lparallel:future (bt:make-thread (lambda () (stmx:atomic (format t "~a ~%" "it works")))))

这个方法虽然能跑通,但相当于在lparallel的线程里再嵌套一个bt线程,完全没必要。

后来我又写了一个更可行但不够优雅的临时宏:

(defmacro lpar-atomic (&body body)
  `(let ((stmx.lang:*current-thread* (lparallel.thread-util:current-thread)))
     (stmx:atomic ,@body)))
(lparallel:future (lpar-atomic (format t "~a ~%" "it works!")))

它能直接修正当前线程的*current-thread*绑定,但每次用atomic都要套这个宏,复用性很差。

更优雅的通用解决方案

其实lparallel提供了自定义线程创建方式的入口——lparallel:*make-thread*变量。我们可以把它设置为bt:make-thread,让lparallel直接用bordeaux-threads创建线程,这样就能自动继承bt的特殊绑定,从根源上解决问题。

完整的示例代码如下:

(ql:quickload :stmx)
(ql:quickload :lparallel)
(ql:quickload :bordeaux-threads)

;; 设置lparallel使用bordeaux-threads创建线程
(setf lparallel:*make-thread* #'bt:make-thread)

;; 确保stmx的*current-thread*被加入bt的默认特殊绑定(如果还没在里面的话)
(unless (assoc 'stmx.lang:*current-thread* bt:*default-special-bindings*)
  (push '(stmx.lang:*current-thread* . (bt:current-thread)) bt:*default-special-bindings*))

;; 创建kernel并运行future
(setf lparallel:*kernel* (lparallel:make-kernel 4))
(lparallel:future (stmx:atomic (format t "~a ~%" "it works!")))

为什么这个方法有效?

  1. lparallel:*make-thread*控制着lparallel创建工作线程的函数,改成bt:make-thread后,所有lparallel的工作线程都会通过bt创建。
  2. bt的make-thread会自动应用*default-special-bindings*里的变量绑定,我们把stmx.lang:*current-thread*加入其中后,每个新线程启动时都会自动将这个变量绑定到当前线程实例,彻底避免了stale值的问题。

这个方案不需要额外的宏包装,也没有线程嵌套,是符合库设计意图的通用解决方案。

内容的提问来源于stack exchange,提问作者leetwinski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 16:02:44