据我对信号量的了解,信号量被用来保护资源,这些资源可以被计算,并且容易受到种族条件的影响。但是,在阅读信号量的SBCL文档时,我想不出如何正确地使用提供的信号量实现来保护资源。
我记得,通常的工作流程是:
考虑到交错的可能性,我们必须保护这些资源访问中的任何一个,因为它们可能不是按照这个顺序,或者任何线性顺序。因此,Java将每个类解释为隐式监视器,并提供一个syncronized关键字,程序员可以用该关键字定义一个保护区域,每次只能由一个进程访问。
如何模仿这个通用的lisp功能,因为我确信我当前的代码和没有信号量的线程安全一样安全,因为信号量不知道要保护什么代码。
;;the package
(defpackage :tests (:use :cl :sb-thread))
(in-package :tests)
(defclass thread-queue ()
((semaphore
:initform (make-semaphore :name "thread-queue-semaphore"))
(in-stack
:initform nil)
(out-stack
:initform nil)))
(defgeneric enqueue-* (queue element)
(:documentation "adds an element to the queue"))
(defgeneric dequeue-* (queue &key timeout)
(:documentation "removes and returns the first element to get out"))
(defmethod enqueue-* ((queue thread-queue) element)
(signal-semaphore (slot-value queue 'semaphore))
(setf (slot-value queue 'in-stack) (push element (slot-value queue 'in-stack))))
(defmethod dequeue-* ((queue thread-queue) &key timeout)
(wait-on-semaphore (slot-value queue 'semaphore) :timeout timeout)
(when (= (length (slot-value queue 'out-stack)) 0)
(setf (slot-value queue 'out-stack) (reverse (slot-value queue 'in-stack)))
(setf (slot-value queue 'in-stack) nil))
(let ((first (car (slot-value queue 'out-stack))))
(setf (slot-value queue 'out-stack) (cdr (slot-value queue 'out-stack)))
first))
(defparameter *test* (make-instance 'thread-queue))
(dequeue-* *test* :timeout 5)
(enqueue-* *test* 42)
(enqueue-* *test* 41)
(enqueue-* *test* 40)
(dequeue-* *test* :timeout 5)
(dequeue-* *test* :timeout 5)
(dequeue-* *test* :timeout 5)
(dequeue-* *test* :timeout 5)发布于 2013-07-31 19:03:24
您已经拥有了一个计数= 0的信号量,消费者在此等待。
您还需要的是访问堆栈的专用锁(可能是每个堆栈一个),或者是一个无锁队列。如果希望/必须使用信号量,二进制信号量可以充当独占锁。
编辑:在SBCL中,您已经拥有了无锁队列,您可能需要使用其中的一个而不是两个堆栈。另一种可能是使用原子运算。
最后,如果这仍然不适合您,请使用互斥包装代码,以访问和更新with-mutex或with-recursive-lock中的堆栈。
确保在从信号量中唤醒之后使用锁/互斥量,而不是在等待信号量的周围使用,否则您将失去信号量给您带来的优势,这就是连续唤醒多个服务员的可能性,而不是一次唤醒一个。
您可以在SBCL手册中阅读有关这些内容的所有内容。
此外,我认为已经做了一些工作,将SBCL中的每一个类似锁的东西重命名为lock,但我不知道它的状态,它声明旧名称将在一段时间内得到支持。
您几乎肯定还需要一个用于生产者的计数=限制的信号量,以不超过您的队列限制。
在enqueue-*中,您应该在更新队列后向信号量发出信号。不需要setf,push已经将列表的新头存储在适当的位置。
在您的dequeue-*中,当应用于列表时,length是一个冗长的函数,但是使用null或endp检查列表是否为空很便宜。与使用car和存储cdr不同,您可以使用pop,它就是这样做的。
发布于 2013-08-01 06:20:26
在队列操作期间,您需要持有互斥信号量(也称为'mutex')。使用这样的SBCL互斥体:
(defclass thread-queue ()
((lock :initform (sb-thread:make-mutex :name 'thread-queue-lock))
...))
(defmethod enqueue-* ((queue thread-queue) element)
(sb-thread:with-recursive-lock ((slot-value queue 'lock))
(setf (slot-value queue 'in-stack) (push element (slot-value queue 'in-stack)))))
* (defvar lock (sb-thread:make-mutex))
LOCK
* lock
#S(SB-THREAD:MUTEX
:NAME NIL
:%OWNER NIL
:LUTEX #<unknown pointer object, widetag=#x5E {11CEB15F}>)
* (sb-thread:with-recursive-lock (lock) 'foo)
FOO
* (sb-thread:with-recursive-lock (lock) (sb-thread:with-recursive-lock (lock) 'foo))
FOO据推测,with-recursive-lock宏将对非本地出口执行正确的操作(使用unwind-protect或其他类似的方式解锁)。
这相当于Java,上面的synchronized保护enqueue-*方法;您需要对所有其他可以异步调用的方法执行此操作。
https://stackoverflow.com/questions/17968018
复制相似问题