Re: Hash table thread safety
"Yuri Davidovsky (as work at disclosure dot ie)" <[email protected]>
| Newsgroups | gmane.lisp.lispworks.general |
|---|---|
| Message-ID | <[email protected]> |
> On 30 Mar 2026, at 20:54, Tim Bradshaw (as tfb at tfeb dot org) <[email protected]> wrote: > > Am I correct in my reading of the LW manuals that hash-table operations are always thread-safe in the sense of 'you can't destroy the table'? It looks like they are also atomic, but I don't need that. You are largely correct. In the world of Common Lisp implementations, "thread-safe" often refers to the internal integrity of the data structure (preventing memory corruption), while "atomic" refers to whether a single operation completes as an indivisible unit. As I understand it, in LispWorks, hash tables are designed to be "thread-safe" by default, so you are correct that you "can't destroy the table." The internal buckets and pointers are protected such that concurrent reads and writes won't crash the Lisp image or leave the table in an undefined, corrupted state. Individual operations like gethash, (setf gethash), and remhash are atomic. However, this atomicity does not extend to sequences of operations — a "check-then-set" logic (if a key isn't there, calculate a value and insert it) is not atomic. Another thread could insert a value between your gethash check and your setf call. > Does anyone know what the equivalent is for SBCL? It looks to me like specifying :synchronized t is what I need both for thread-safety and atomicity. While I am not super familiar with SBCL (since one needs to run emacs to use it and I value my neural health) it seems like SBCL hash tables are not thread-safe by default. If multiple threads access a standard SBCL hash table and at least one is writing, you risk memory corruption, infinite loops (during re-hashing), or internal errors. To achieve the LispWorks-like behaviour in SBCL, is where the :synchronized argument is used: (make-hash-table :synchronized t) This makes the table safe for concurrent use. It uses a recursive lock internally to ensure that the table's internal state remains consistent, and similar to LispWorks, this makes individual calls (like gethash) atomic. If you need to perform multiple operations atomically (e.g., incrementing a value stored in the table), SBCL provides a specific macro to hold the table's internal lock: (sb-ext:with-locked-hash-table (my-table) (let ((val (gethash key my-table))) (setf (gethash key my-table) (1+ val)))) It is worth distinguishing between implementation atomicity (the pointer update is atomic at the CPU level) and interface atomicity (the function call is protected by a lock). LispWorks appears to lean toward internal synchronisation that avoids heavy locking where possible. SBCL's :synchronized t is a more explicit "wrap everything in a mutex" approach. If you truly do not need atomicity for your logic but only the guarantee that the table won't break — SBCL's :synchronized t is still the mandatory flag to prevent crashes. Again, the disclaimer is: I am not an expert in SBCL, take it my thoughts as you will.