Многократные читатели, единственные блокировки писателя в Повышении - PullRequest
12 голосов
/ 17 ноября 2010

Я пытаюсь реализовать следующий код в многопоточном сценарии:

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

Повышающие потоки имеют класс shared_mutex, который был разработан для модели с несколькими читателями и одной записывающей машиной. В отношении этого класса есть несколько вопросов об стеке потока. Тем не менее, я не уверен, что это соответствует сценарию выше, где любой читатель может стать писателем. В документации говорится:

Концепция UpgradeLockable - это уточнение SharedLockable концепция, которая позволяет модернизировать владение, а также совместное владение и исключительная собственность. Это расширение до нескольких читателей / модель с одной записью, предоставляемая SharedLockable концепция: один поток может иметь право на обновление в то же время, как другие поделились собственность.

Из слова «одиночный» я подозреваю, что только один поток может содержать обновляемую блокировку. Остальные имеют только общую блокировку, которую нельзя обновить до эксклюзивной блокировки.

Знаете ли вы, если boost::shared_lock полезен в этой ситуации (любой читатель может стать писателем), или есть ли другой способ добиться этого?

Ответы [ 3 ]

15 голосов
/ 17 ноября 2010

Да, вы можете делать именно то, что вы хотите, как показано в принятом ответе здесь . Запрос на обновление до монопольного доступа будет блокироваться до тех пор, пока все читатели не завершат работу.

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
}
5 голосов
/ 18 ноября 2010

boost::shared_lock не помогает в этой ситуации (несколько читателей, которые могут стать писателями), поскольку только один поток может иметь обновляемую блокировку.Это подразумевается как цитатой из документации в вопросе, так и просмотром кода (thread \ win32 \ shared_mutex.hpp).Если поток пытается получить обновляемую блокировку, в то время как другой поток удерживает ее, он будет ожидать другой поток.

Я остановился на использовании обычной блокировки для всех устройств чтения / записи, что нормально в моем случае, посколькукритическая секция короткая.

4 голосов
/ 17 ноября 2010

Вы знаете, 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;
    };


...