You already have the semaphore count = 0 on which users are waiting.
You will also need an exclusive lock on access to your stacks (possibly one for each) or, alternatively, no lock. If you want / should use semaphores, the binary semaphore can serve as an exclusive lock.
EDIT: In SBCL, you already have queueless locks , you can use one of them instead of two stacks. Another possibility is to use atomic operations .
Finally, if that doesn't suit you yet, use mutex , a packaging code that allows and updates the stacks inside with-mutex or with-recursive-lock .
Be sure to use the lock / mutex after waking up from the semaphore, and not around waiting for the semaphore, otherwise you will lose the advantage that semaphores give you, which is the ability to wake up several waiters in a row, and not one at a time.
You can read all about these things in the SBCL manual.
In addition, I think that some work has been done to rename every blocking thing in SBCL to lock , according to this blog post , but I do not know its status, and it claims that the old names will be maintained for some time.
You will probably need the count = limit semaphore for producers so as not to exceed the limit of your queue.
In enqueue-* you should signal the semaphore after , updating the queue. setf not required, push already saves the new chapter of the list in place.
In your dequeue-* , length is a long function applied to lists, but checking that the list is empty is cheaper with null or endp . Instead of taking car and storing cdr , you can use pop , it does just that.