多线程下SBCL全局哈希表gethash返回NIL问题排查
SBCL多线程环境下全局哈希表gethash返回NIL的问题排查
问题现象
单线程模式下dictionary-compatible函数正常工作,但使用lparallel库的多线程新镜像时,对全局哈希表*lci-hash-table*的gethash调用始终返回nil。该哈希表仅用于读取,未做更新操作,按SBCL手册说明无需设置:synchronized或用sb-ext:defglobal定义。
核心代码
全局哈希表定义与核心函数
(defparameter *lci-hash-table* (make-hash-table :size 10000)) "Hash-table key = length|char|index concatenated together (eg, 5A0), hash-table values = list of matching symbols (eg, (ABATE ABBEY ...))." (defun dictionary-compatible ($new-cross-str) ;eg, "A " of length 5 "Tests if a string (with uppercase alphabetic and space characters) is compatible with the dictionary words (symbols) in a hash-table." (iter (with len = (length $new-cross-str)) (for char in-sequence $new-cross-str) (for index from 0) (when (not (eql char #\Space)) (collect (gethash (ut::intern-symbol len char index) ;gethash始终返回nil *lci-hash-table*) into dict-words)) (finally (return (reduce #'intersection dict-words))))) (defun intern-symbol (&rest args) "Interns a symbol created by concatenating args. Based on symb in Let Over Lambda." (flet ((mkstr (&rest args) (with-output-to-string (s) (dolist (a args) (princ a s))))) (values (intern (apply #'mkstr args)))))
哈希表填充代码
(defun create-lci-ht (dictionary-file) "Read dictionary words (symbols) from a file into strings and store in a lci hash table." (with-open-file (infile dictionary-file :direction :input :if-does-not-exist nil) (when (not (streamp infile)) (error "File does not exist!")) (with-open-file (stream dictionary-file) (let ((word-strings (uiop:read-file-lines stream))) (iter (for word-string in word-strings) (for word-length = (length word-string)) (while word-string) (iter (for char in-sequence word-string) (for index from 0) (push (intern word-string) (gethash (ut::intern-symbol word-length char index) *lci-hash-table*)))))))
问题根源
问题出在intern-symbol函数中的intern调用:该函数未指定固定的目标包,而是依赖于调用时的动态*package*变量。
- 填充哈希表时,若调用环境的当前包是
UT,则intern会将生成的键符号存入UT包(如UT:5A0)。 - 多线程worker的运行环境中,当前包可能是默认的
CL-USER,此时调用ut::intern-symbol时,内部的intern会将符号存入CL-USER包(如CL-USER:5A0)。
这两个符号属于不同包,即使名称相同,eql比较也会返回nil,导致gethash无法匹配到哈希表中的键,最终返回nil。
解决方案
修改intern-symbol函数,指定将符号intern到固定包(比如UT包),确保无论调用环境的当前包是什么,生成的键符号都一致:
(defun intern-symbol (&rest args) "Interns a symbol created by concatenating args into the UT package. Based on symb in Let Over Lambda." (flet ((mkstr (&rest args) (with-output-to-string (s) (dolist (a args) (princ a s))))) (values (intern (apply #'mkstr args) (find-package 'ut)))))
补充说明:也可以提前创建一个专门用于存储哈希表键的包,避免与其他包的符号冲突,原理是一致的。
内容的提问来源于stack exchange,提问作者davypough
相关产品推荐
相关产品推荐

