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

时间:2016-08-09 23:41:26

标签: python multithreading concurrency locking

我使用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

2 个答案:

答案 0 :(得分:1)

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

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

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

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

修改 你的代码对我来说似乎是正确的,因为它解决了First readers-writers problem。正如你所说,它可能会使作家挨饿。这不是一个小问题。读者写作背后的逻辑是,可能会有比读写更多的读取 额外的锁定允许解决第二读者 - 作家问题,其中作家不会挨饿。实际上,当有作家等待资源时,读者必须等待。

答案 1 :(得分:0)

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

# 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

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