Common lisp 如何在竞争条件下利用SBCL提供的信号量
就我对信号量的了解而言,信号量用于保护可计数且易受竞争条件影响的资源。但是在阅读信号量的SBCL文档时,我不知道如何正确使用提供的信号量实现来保护资源 我记得,通常的工作流程是:Common lisp 如何在竞争条件下利用SBCL提供的信号量,common-lisp,sbcl,Common Lisp,Sbcl,就我对信号量的了解而言,信号量用于保护可计数且易受竞争条件影响的资源。但是在阅读信号量的SBCL文档时,我不知道如何正确使用提供的信号量实现来保护资源 我记得,通常的工作流程是: 进程希望检索受信号量保护的部分 数据(就本例而言,它是一个普通队列)。作为 信号量计数器为0,进程等待 另一个进程将某些内容放入队列中,并作为信号量 递增后,将向所有等待进程发送一个信号 考虑到交织的可能性,必须保护这些资源访问中的任何一个,因为它们可能不在该顺序中,或者根本不在任何线性顺序中。因此,例如Java将每个
同步的
关键字,程序员可以使用该关键字定义一个一次只能由一个进程访问的受保护区域
如何在common 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)
您已经有了一个count=0的信号量,消费者在上面等待 您还需要一个访问堆栈的独占锁(每个堆栈可能有一个),或者一个无锁队列。如果您想要/必须使用信号量,二进制信号量可以用作独占锁
编辑: 在SBCL中,您已经有了,您可能希望使用其中一个而不是两个堆栈。另一种可能性是使用 最后,如果这仍然不适合您,请使用包装代码,使用互斥锁访问并更新
中的堆栈,或者使用递归锁访问并更新
确保在从信号量中醒来后使用锁/互斥锁,而不是在等待信号量的前后使用锁/互斥锁,否则你将失去信号量给你带来的优势,即可能连续唤醒多个服务员,而不是一次唤醒一个服务员
你可以在报纸上读到这些东西
另外,我认为已经做了一些工作,将SBCL中的每一个类似锁的东西重命名为lock
,根据,但我不知道它的状态,它声明旧名称将在一段时间内得到支持
几乎可以肯定的是,生产者还需要count=limit的信号量,以不超过队列限制
在您的排队-*
中,您应该在更新队列后发出信号灯。不需要setf
,push
已将列表的新标题存储到位
在您的dequeue-*
中,length
在应用于列表时是一个很长的函数,但是使用null
或endp
检查列表是否为空很便宜。您可以使用pop
,而不是乘坐car
并存储cdr
,它正是这样做的。您需要在队列操作期间保持互斥信号量(也称为“互斥”)。使用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
(defclass线程队列()
((lock:initform(sb线程:make mutex:name'线程队列锁))
...))
(defmethod enqueue-*((队列线程队列)元素)
(sb线程:带递归锁((插槽值队列“锁”)
(setf(堆栈中的“插槽值队列”)(push元素(堆栈中的“插槽值队列”))
*(defvar锁(sb线程:生成互斥)
锁
*锁
#S(SB线程:互斥)
:NAME NIL
:%OWNER无
:LUTEX#)
*(sb线程:带递归锁(锁)'foo)
福
*(sb线程:带递归锁(lock)(sb线程:带递归锁(lock)'foo))
福
对于非本地退出,带递归锁的宏可能会做正确的事情(使用解除保护
或类似的方法解锁锁)
这相当于Javasynchronized
——上面保护了enqueue-*
方法;您需要对所有其他可以异步调用的方法执行此操作。在信号量之外使用锁将打开两种可能性:(a)在锁中使用信号量->非常危险,因为如果进程等待信号量但不返回外部锁,则这可能会导致死锁(b)使用锁外的信号灯->好吧,那么我只得到了一个我可以自己实现的奇特计数器。我希望给定的实现将提供一种保护代码的简洁方法。如果没有这种可能性。请在我的备注(a)中进行编辑,我将接受它作为答案。(你对其他事情的看法是对的)@Sim,好的,我会在我的回答中加上你的评论。另外,请注意,通过信号量,您还获得了一些好处:被动等待和连续叫醒多个服务员。@Sim,我在最初的段落中添加了几个词,以使其更加清晰。以前的版本可能给人的印象是,使用锁进行独占资源访问是信号量的替代。