【问题标题】:Multiple-readers, single-writer locks in BoostBoost 中的多读单写锁
【发布时间】:2011-05-11 08:21:30
【问题描述】:

我正在尝试在多线程场景中实现以下代码:

Get shared access to mutex
Read data structure
If necessary:
   Get exclusive access to mutex
   Update data structure
   Release exclusive lock
Release shared lock

Boost 线程有一个 shared_mutex 类,专为多阅读器、单作者模型而设计。关于这个类有几个stackoverflow问题。但是,我不确定它是否适合上述 任何 读者可能成为作家的场景。文档指出:

UpgradeLockable 概念是一个 SharedLockable 的改进 允许升级的概念 所有权和共享所有权 和独家所有权。这是一 多阅读器的扩展 / 提供的单写模型 SharedLockable 概念:一个单 线程可能具有可升级的所有权 在其他人分享的同时 所有权。

从“单一”这个词我怀疑只有一个线程可以持有可升级锁。其他人只持有共享锁,无法升级为独占锁。

您知道boost::shared_lock 在这种情况下是否有用(任何读者都可能成为作家),或者是否有其他方法可以实现这一点?

【问题讨论】:

  • 如果不是“一次只能有一个线程持有锁”,那么“独占访问”的定义是什么?还是您将“一次一个线程”与“预先选择的一个特定线程”混淆了?
  • @Pete:我想要一个线程(不是提前选择)持有排他锁。

标签: c++ multithreading boost mutex


【解决方案1】:

是的,您可以按照接受的答案here 中所示的方式做您想做的事。升级到独占访问的调用将被阻塞,直到所有读取器完成。

boost::shared_mutex _access;
void reader()
{
  // get shared access
  boost::shared_lock<boost::shared_mutex> lock(_access);

  // now we have shared access
}

void writer()
{
  // get upgradable access
  boost::upgrade_lock<boost::shared_mutex> lock(_access);

  // get exclusive access
  boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
  // now we have exclusive access
}

【讨论】:

  • 多个线程可以使用它,但任何时候只有一个线程可以独占。只要没有当前的写入器,多个并发读取器就可以了。这就是你想要的,我想?
  • 那我该如何解锁呢?
  • 它可能会在作用域结束时自行解锁。
【解决方案2】:

boost::shared_lock 在这种情况下没有帮助(多个读者可以成为写者),因为只有一个线程可能拥有一个可升级的锁。问题中文档的引用和查看代码 (thread\win32\shared_mutex.hpp) 都暗示了这一点。如果一个线程试图获取一个可升级锁而另一个线程持有一个,它将等待另一个线程。

我决定为所有读取器/写入器使用常规锁,这在我的情况下是可以的,因为关键部分很短。

【讨论】:

    【解决方案3】:

    你知道LightweightLock 或LightweightLock_zip
    做你想要的。 我已经用了很长时间了。

    [编辑] 这是来源:

    
    /////////////////////////////////////////////////////////////////////////////
    //
    //  Copyright (C) 1995-2002 Brad Wilson
    //
    //  This material is provided "as is", with absolutely no warranty
    //  expressed or implied. Any use is at your own risk. Permission to
    //  use or copy this software for any purpose is hereby granted without
    //  fee, provided the above notices are retained on all copies.
    //  Permission to modify the code and to distribute modified code is
    //  granted, provided the above notices are retained, and a notice that
    //  the code was modified is included with the above copyright notice.
    //
    /////////////////////////////////////////////////////////////////////////////
    //
    //  This lightweight lock class was adapted from samples and ideas that
    //  were put across the ATL mailing list. It is a non-starving, kernel-
    //  free lock that does not order writer requests. It is optimized for
    //  use with resources that can take multiple simultaneous reads,
    //  particularly when writing is only an occasional task.
    //
    //  Multiple readers may acquire the lock without any interference with
    //  one another. As soon as a writer requests the lock, additional
    //  readers will spin. When the pre-writer readers have all given up
    //  control of the lock, the writer will obtain it. After the writer
    //  has rescinded control, the additional readers will gain access
    //  to the locked resource.
    //
    //  This class is very lightweight. It does not use any kernel objects.
    //  It is designed for rapid access to resources without requiring
    //  code to undergo process and ring changes. Because the "spin"
    //  method for this lock is "Sleep(0)", it is a good idea to keep
    //  the lock only long enough for short operations; otherwise, CPU
    //  will be wasted spinning for the lock. You can change the spin
    //  mechanism by #define'ing __LW_LOCK_SPIN before including this
    //  header file.
    //
    //  VERY VERY IMPORTANT: If you have a lock open with read access and
    //  attempt to get write access as well, you will deadlock! Always
    //  rescind your read access before requesting write access (and,
    //  of course, don't rely on any read information across this).
    //
    //  This lock works in a single process only. It cannot be used, as is,
    //  for cross-process synchronization. To do that, you should convert
    //  this lock to using a semaphore and mutex, or use shared memory to
    //  avoid kernel objects.
    //
    //  POTENTIAL FUTURE UPGRADES:
    //
    //  You may consider writing a completely different "debug" version of
    //  this class that sacrifices performance for safety, by catching
    //  potential deadlock situations, potential "unlock from the wrong
    //  thread" situations, etc. Also, of course, it's virtually mandatory
    //  that you should consider testing on an SMP box.
    //
    ///////////////////////////////////////////////////////////////////////////
    
    #pragma once
    
    #ifndef _INC_CRTDBG
    #include 
    #endif
    
    #ifndef _WINDOWS_
    #include 
    #endif
    
    #ifndef __LW_LOCK_SPIN
    #define __LW_LOCK_SPIN Sleep(0)
    #endif
    
    
        class LightweightLock
        {
        //  Interface
    
        public:
            //  Constructor
    
            LightweightLock()
            {
                m_ReaderCount = 0;
                m_WriterCount = 0;
            }
    
            //  Destructor
    
            ~LightweightLock()
            {
                _ASSERTE( m_ReaderCount == 0 );
                _ASSERTE( m_WriterCount == 0 );
            }
    
            //  Reader lock acquisition and release
    
            void LockForReading()
            {
                while( 1 )
                {
                    //  If there's a writer already, spin without unnecessarily
                    //  interlocking the CPUs
    
                    if( m_WriterCount != 0 )
                    {
                        __LW_LOCK_SPIN;
                        continue;
                    }
    
                    //  Add to the readers list
    
                    InterlockedIncrement((long*) &m_ReaderCount );
    
                    //  Check for writers again (we may have been pre-empted). If
                    //  there are no writers writing or waiting, then we're done.
    
                    if( m_WriterCount == 0 )
                        break;
    
                    //  Remove from the readers list, spin, try again
    
                    InterlockedDecrement((long*) &m_ReaderCount );
                    __LW_LOCK_SPIN;
                }
            }
    
            void UnlockForReading()
            {
                InterlockedDecrement((long*) &m_ReaderCount );
            }
    
            //  Writer lock acquisition and release
    
            void LockForWriting()
            {
                //  See if we can become the writer (expensive, because it inter-
                //  locks the CPUs, so writing should be an infrequent process)
    
                while( InterlockedExchange((long*) &m_WriterCount, 1 ) == 1 )
                {
                    __LW_LOCK_SPIN;
                }
    
                //  Now we're the writer, but there may be outstanding readers.
                //  Spin until there aren't any more; new readers will wait now
                //  that we're the writer.
    
                while( m_ReaderCount != 0 )
                {
                    __LW_LOCK_SPIN;
                }
            }
    
            void UnlockForWriting()
            {
                m_WriterCount = 0;
            }
    
            long GetReaderCount() { return m_ReaderCount; };
            long GetWriterConut() { return m_WriterCount; };
    
        //  Implementation
    
        private:
            long volatile m_ReaderCount;
            long volatile m_WriterCount;
        };
    
    
    

    【讨论】:

    • 链接断开。当我在寻找 C++ 解决方案(添加标签)时,它听起来像是一个 .net 库。
    • 请注意,上面的代码是不公平的,并且存在线程永远无法访问并永远等待的情况..
    • 这在我使用这个实现的时候从来没有发生过。七年过去了,代码每天运行超过 1000 万次。简单而且非常好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多