只有一个底层锁的读写锁?

Posted

技术标签:

【中文标题】只有一个底层锁的读写锁?【英文标题】:Read-write lock with only one underlying lock? 【发布时间】:2016-08-09 23:41:26 【问题描述】:

我使用 Python 的并发原语编写了一个读写锁(我想!)。我在 SO 或其他地方阅读的每个实现似乎都使用 2 个锁——一个用于读取,另一个用于写入。我的实现只包含一个用于读取的监视器,但我可能遗漏了一些重要的东西——任何人都可以确认这会起作用吗?如果是这样,使用额外的写锁有什么好处?

这是经典的读写锁,读者偏好(可能会饿死作者)。我使用一个虚拟缓存来演示读写。

    import threading as t

    class ReadWriteCache(object):
        def __init__(self):
            self.cache = 
            self.reads = 0
            self.read_cond = t.Condition(t.Lock())

        def read(self, key):
            with self.read_cond:      # Register the read, so writes will wait()
                self.reads += 1

            result = self.cache[key]

            with self.read_cond:
                self.reads -= 1
                if not self.reads:
                    self.read_cond.notify_all()
                return result

        def update(self, key, value):
            with self.read_cond:
                while self.reads:
                    self.read_cond.wait()   # Wait for reads to clear
                self.cache[key] = value     # With read lock, update value

【问题讨论】:

在哪里可以找到双锁版本的示例? @ErikR,你可以在这里找到一些例子: @ErikR,输入太早了。请参阅下面的问题。接受的答案中的每个链接,以及 pasztorpisti 的代码答案。它们要么使用 2 个锁,要么具有某种双事件系统,在此系统中,写入者和读取者都在完成时相互发出信号。 ***.com/questions/16261902/… 【参考方案1】:

您没有使用单个锁。 您正在使用锁和条件变量

self.read_lock = t.Condition(t.Lock())

条件变量也是一种并发原语。比锁更复杂的。

注意:请不要调用条件变量对象read_lock

编辑: 您的代码对我来说似乎是正确的,因为它解决了First readers-writers problem。正如你所说,它可能会饿死作家。这不是一个小问题。读写器背后的逻辑是读取可能比写入多得多 额外的锁可以解决第二个读者-作者问题,即作者不会饿死。事实上,当有作者在等待资源时,读者必须等待。

【讨论】:

啊,这很有道理——我想我实际上是在同时使用锁和事件。我已重命名对象,是否需要重新审视正确性?【参考方案2】:

使用锁和条件的另一种解决方案。处理饥饿问题,还支持在从同一线程请求时将读锁提升为写锁。

# From O'Reilly Python Cookbook by David Ascher, Alex Martelli
# With changes to cover the starvation situation where a continuous
#   stream of readers may starve a writer, Lock Promotion and Context Managers

class ReadWriteLock:
  """ A lock object that allows many simultaneous "read locks", but
  only one "write lock." """

  def __init__(self, withPromotion=False):
    self._read_ready = threading.Condition(threading.RLock(  ))
    self._readers = 0
    self._writers = 0
    self._promote = withPromotion
    self._readerList = []  # List of Reader thread IDs
    self._writerList = []  # List of Writer thread IDs

  def acquire_read(self):
    logging.debug("RWL : acquire_read()")
    """ Acquire a read lock. Blocks only if a thread has
    acquired the write lock. """
    self._read_ready.acquire(  )
    try:
      while self._writers > 0:
        self._read_ready.wait()
      self._readers += 1
    finally:
      self._readerList.append(threading.get_ident())
      self._read_ready.release(  )

  def release_read(self):
    logging.debug("RWL : release_read()")
    """ Release a read lock. """
    self._read_ready.acquire(  )
    try:
      self._readers -= 1
      if not self._readers:
        self._read_ready.notifyAll(  )
    finally:
      self._readerList.remove(threading.get_ident())
      self._read_ready.release(  )

  def acquire_write(self):
    logging.debug("RWL : acquire_write()")
    """ Acquire a write lock. Blocks until there are no
    acquired read or write locks. """
    self._read_ready.acquire(  )   # A re-entrant lock lets a thread re-acquire the lock
    self._writers += 1
    self._writerList.append(threading.get_ident())
    while self._readers > 0:
      # promote to write lock, only if all the readers are trying to promote to writer
      # If there are other reader threads, then wait till they complete reading
      if self._promote and threading.get_ident() in self._readerList and set(self._readerList).issubset(set(self._writerList)):
        break
      else:
        self._read_ready.wait(  )

  def release_write(self):
    logging.debug("RWL : release_write()")
    """ Release a write lock. """
    self._writers -= 1
    self._writerList.remove(threading.get_ident())
    self._read_ready.notifyAll(  )
    self._read_ready.release(  )

#----------------------------------------------------------------------------------------------------------

class ReadRWLock:
  # Context Manager class for ReadWriteLock
  def __init__(self, rwLock):
    self.rwLock = rwLock

  def __enter__(self):
    self.rwLock.acquire_read()
    return self         # Not mandatory, but returning to be safe

  def __exit__(self, exc_type, exc_value, traceback):
    self.rwLock.release_read()
    return False        # Raise the exception, if exited due to an exception

#----------------------------------------------------------------------------------------------------------

class WriteRWLock:
  # Context Manager class for ReadWriteLock
  def __init__(self, rwLock):
    self.rwLock = rwLock

  def __enter__(self):
    self.rwLock.acquire_write()
    return self         # Not mandatory, but returning to be safe

  def __exit__(self, exc_type, exc_value, traceback):
    self.rwLock.release_write()
    return False        # Raise the exception, if exited due to an exception

#----------------------------------------------------------------------------------------------------------

【讨论】:

以上是关于只有一个底层锁的读写锁?的主要内容,如果未能解决你的问题,请参考以下文章

线程同步互斥锁和读写锁的区别和各自适用场景

ReentrantReadWriteLock读写锁的使用

读写锁-ReaderWriterLockSlim

读写锁-ReaderWriterLockSlim

QReadWriteLock读写锁的一点测试(它是逻辑锁,并没有与实物相联系),只有锁住了读,才允许再次读,否则一概不允许

互斥锁,信号量,条件变量,读写锁