To make this more clear: With mutable I mean that the
value in the slot can be modified. When the value is
replaced with another value, it is changed, but not mutated.
I added an id field to the class in the example, which is
neither mutable nor changable in the lifetime of an instance.
Some id is needed to find the original instance for the
update at the end.
The example now mutates one of the strings, and replaces the
other. The number is replaced, and the list is mutated by
push. Other threads are allowed to make the same types of
changes to the instance (protected by the lock while
accessing or changing).
And the word to use is robust, I would like to know if this
On Fri, Mar 09, 2007 at 03:13:32AM +0100, Johan Ur Riise wrote:
> I work on an application using threads, where different threads
> needs to updat and read data that is shared. I want to get it
> right, and would like feedback on my plan.
> Since a thread can start to run at any time, also in the middle
> of a basic operation (except reservation of locks), I figure
> that I have to protect all accesses and updates of shared data
> with a mutex.
> I want to start out with just one *big-lock*, to avoid deadlocks.
> I reserve that lock using 'with-recursive-lock, just in case
> I should call one protected operation from inside of another
> protected operation.
> Most operations will use shared data, so the locking would
> be on almost everything. So I want to find some object and
> copy it in a protected operation, release the lock and continue
> working with the copied object, then possibly update the original
> object at the end.
> So how much copying is necessary? Say I have an instance of a
> class with some slots containing for example a string, a number
> and a list of numbers. I would have to make a new instance
> of the class. A slot containing a string would have to be
> copied using copy-seq, if the string is going to be modified
> somewhere else. If not, the same string set to the
> new slot would be ok. For a number, setting the new slot
> to the same value would be ok. For a list of immutable things,
> a copy of the list with the same elements would be ok.
> For example, teaspoon mode:
;; let's prepare an environment
(defclass thing ()
((id :accessor get-id :initarg :id)
(mutable-string :accessor get-mutable-string :initarg :mutable-string)
(immutable-string :accessor get-immutable-string :initarg :immutable-string)
(number :accessor get-number :initarg :number)
(list :accessor get-list :initarg :list)))
(defgeneric clone (instance)
(:method ((instance thing))
:id (get-id instance)
:immutable-string (get-immutable-string instance)
:mutable-string (copy-seq (get-mutable-string instance))
:number (get-number instance)
:list (copy-seq (get-list instance)))))
(defparameter *lock* (sb-thread:make-mutex))
(defparameter *data* (make-hash-table :test 'equal))
(let ((thing (make-instance 'thing
:list (list 4 5 6))))
(setf (gethash (get-id thing) *data*) thing))
;; Now I have shared data in *data*, and some thread wants to do
;; something with it in a safe way. Other threads must of course
;; reserve the same mutex if they want to read or change the data
;; lock while I find and clone the shared data
(let ((clone (sb-thread:with-recursive-lock (*lock*)
(clone (gethash "abc01" *data*)))))
;; some time-consuming operation where I use the clone,
;; leading to a new value for say mutable-string
;; no lock is reserved while this is going on
(let ((new-immutable-string (progn (sleep 2)
;; now locking again while I find and update the shared data
(let ((instance (gethash (get-id clone) *data*)))
(setf (get-immutable-string instance) new-immutable-string) ;;replace
(setf (subseq (get-mutable-string instance) 0 1) "X") ;mutate
(setf (get-number instance) 42) ;replace
(push 84 (get-list instance)) ;mutate
Johan Ur Riise