2

The topic of multi-threaded access to Lisp objects came up in another post at https://stackoverflow.com/posts/comments/97440894?noredirect=1, but as a side issue, and I am hoping for further clarification.

In general, Lisp functions (and special forms, macros, etc) seem to naturally divide into accessors and modifiers of objects. Modifiers of shared objects are clearly problematic in multi-threaded applications, since updates occurring at the same time can interfere with each other (requiring protective locks, atomic operations, etc).

But the question of potential accessor interference seems less clear. Of course, any accessor could be written to include latent modifying code, but I would like to think that the basic Lisp accessor operations (as specified in CLHS and implemented for the various platforms) do not. However, I suspect there could be a very few exceptions for reasons of efficiency—exceptions that would be good to be aware of if otherwise used in multi-threaded code without protection. (The kind of exceptions I’m talking about are not operations like maphash which can be used as both an accessor and modifier.)

It would be helpful if anyone with implementation experience could point to at least one built-in access-only operation (say in SBCL or other source) that includes potentially troublesome modification. I know guarantees are hard to come by, but heuristic guidance is useful too.

davypough
  • 1,847
  • 11
  • 21
  • 1
    I don't know Common Lisp, but in other languages, even a pure "accessor" can run into trouble if another thread concurrently updates the same object and, if either the access or the update is more than just a single, atomic hardware operation. Whenever there's a data race between an acessor and a mutator, the accessor potentially can see the object in an invalid, half-way-changed state. Arbitrary, bad things could happen (incorrect result computed, seg fault, data corruption, etc.) depending on what the caller does with the "accessed" data. – Solomon Slow Mar 26 '19 at 22:00
  • 1
    'It would be helpful'. For what would that be helpful? What is your actual problem? – Rainer Joswig Mar 27 '19 at 14:54
  • @Solomon Slow, All good comments, but my question was a little different. Assume the end-user properly locks/protects all mutator operations in every thread. Also assume the user has full knowledge of the language specification (eg, CLHS for Common Lisp), but little implementation knowledge. Could there be operations provided in the language interface that appear to be accessors (like `search`, not `sort`), but for efficiency reasons might involve some mutation? – davypough Mar 27 '19 at 15:01
  • @Rainer Joswig, To be more specific, take `(find item list :from-end t)`. Could an implementer legitimately code `find` to use `nreverse` on the list, followed by another `nreverse` to get it back after finding the item? – davypough Mar 27 '19 at 15:19
  • Have you looked at the Common Lisp standard? FIND is documented, IIRC. – Rainer Joswig Mar 27 '19 at 15:23
  • I'll have to rely on you to point me to implementation documentation. Didn't see anything in the Hyperspec. – davypough Mar 27 '19 at 17:07
  • 1
    You have to use a lock to protect the list from modification by other threads while you're traversing it (regardless of what you're using to traverse it, including `FIND`). [CLHS 3.6](http://www.lispworks.com/documentation/HyperSpec/Body/03_f.htm) forbids modification of the cdr chain of a list while it is being traversed (this applies to all "code executed during" the traversal, so multi-threading is included even though not explicitly mentioned in the standard). In general, the easiest way to avoid problems with multi-threaded modifications is to not do any and use immutable data structures. – jkiiski Mar 27 '19 at 17:16
  • 1
    The Hyperspec has a documentation for FIND: http://www.lispworks.com/documentation/HyperSpec/Body/f_find_.htm#find it says 'Side effects: None' – Rainer Joswig Mar 27 '19 at 17:16
  • 2
    It also says that it's undefined what happens if one tries to modify the sequence during traversal. – Rainer Joswig Mar 27 '19 at 17:22
  • @jkiiski, Rainer Joswig, Excellent! I think that answers my initial post. It looks like traversal protections will probably generalize to any kind of access to any built-in type of Lisp object. Also learned that “side effects” applies to internal as well as final results. Thanks. – davypough Mar 27 '19 at 17:42

1 Answers1

0

Any code that does that would be a bug in an implementation that supports multithreading. SBCL protects functions that are not thread-safe with the famous *world-lock*.

If you have a real reason to want an immutable structure, use defconstant with a read-only defstruct. (defstruct number (value :read-only t))

(defconstant +five+ (make-number 5))

Spenser Truex
  • 963
  • 8
  • 24
  • Constants and immutable data structures are different things. Immutable data structures are typically lists or trees that you never mutate, but instead create new trees with some parts changed, sharing as much structure as possible. This is good for thread safety, since even if another thread is reading tree at the same time, nothing bad happens (it just won't see the updated value). Note also that `DEFCONSTANT` shouldn't be used with structs, because re-evaluating the form is undefined behavior when the new value is not `EQL` to the old. – jkiiski Mar 30 '19 at 12:49