6

就我对信号量的了解而言,信号量用于保护可计数且易受竞争条件影响的资源。但是在阅读信号量的 SBCL 文档时,我无法弄清楚如何正确使用提供的信号量实现来保护资源。

我记得一个通常的工作流程是:

  1. 一个进程想要检索一些受信号量保护的数据(就示例而言,这是一个简单的队列)。由于信号量计数器为0,进程等待

  2. 另一个进程将某些东西放入队列中,随着信号量的增加,一个信号被发送到所有等待的进程

考虑到交错的可能性,必须保护这些资源访问中的任何一个,因为它们可能不是按那个顺序,或者根本不是任何线性顺序。因此,例如,Java 将每个类解释为隐式监视器,并提供一个syncronized关键字,程序员可以使用该关键字定义一次只能由一个进程访问的受保护区域。

如何在 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)
4

2 回答 2

3

你已经拥有的是一个 count = 0 的信号量,消费者在上面等待。

您还需要一个围绕访问堆栈的排他锁(可能每个堆栈一个),或者一个无锁队列。如果您想/必须使用信号量,二进制信号量可以用作排他锁。


编辑:在 SBCL 中,您已经拥有无锁队列,您可能希望使用其中一个而不是两个堆栈。另一种可能性是使用原子操作

最后,如果这仍然不适合您,请使用mutexwith-mutex包装代码,以访问和更新or中的堆栈with-recursive-lock

一定要在从信号量唤醒后使用锁/互斥量,而不是围绕等待信号量,否则你失去了信号量给你的优势,也就是连续唤醒多个服务员的可能性,而不是一个在一次。

您可以在SBCL 手册中阅读有关这些内容的所有内容。

lock另外,根据这篇博客文章,我认为已经完成了一些工作,将 SBCL 中的每个类似锁的东西重命名为,但我不知道它的状态,它指出旧名称将在一段时间内得到支持。


您几乎肯定还需要生产者的 count = limit 信号量,以不超过您的队列限制。

在您的中,您应该在更新队列enqueue-*发出信号量。不需要,已经将列表的新头存储在适当的位置。setfpush

在您的dequeue-*,中,length当应用于列表时是一个冗长的函数,但是使用nullor来检查列表是否为空很便宜endp。您可以使用,而不是使用car并存储,它就是这样做的。cdrpop

于 2013-07-31T19:03:24.487 回答
1

您需要在队列操作期间保持互斥信号量(又名“互斥量”)。像这样使用 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-*方法;您需要对可以异步调用的所有其他方法执行此操作。

于 2013-08-01T06:20:26.983 回答