I was writing some code using semaphores and realized that I wasn't sure I completely understand them.
Suppose I have something like this:
(define sema (make-semaphore 1))
(define user->id (make-hash (list (cons 'alice 1) (cons 'bill 2))))
(define id->user (make-hash (list (cons 1 'alice) (cons 2 'bill))))
(define (get-name id) (hash-ref id->user id))
(define (get-id name) (hash-ref user->id name))
(define (add-user! name id)
(call-with-semaphore sema
(thunk
(hash-set! user->id name id)
(hash-set! id->user id name))))
(add-user! 'charlie 3)
(get-name 2)
(get-id 'alice)
My understanding is that:
A. Racket is single-threaded and implements concurrency by periodically pausing the current thread and running a different one.
B. The semaphore ensures that if multiple threads want to call add-user!
then they will take turns, thereby ensuring that the two hashes are always consistent.
The things I'm not sure about:
- Do I need to worry about
get-name
seeing an inconsistent state if it's running in thread A and theadd-user!
is happening in thread B? - How exactly does this work? Does
call-with-semaphore
pause all other threads (or, rather, disable thread switching) while it finishes the protected block? - If the answer to 1 is yes then I believe the solution is to use the semaphore in both the accessors and mutators, while being careful to avoid deadlock. Is this right?
- If the answer to 2 is yes then does the time spent in the protected block count against the duration of a
sync/timeout
in another thread?