2010-11-17 6 views
12

Ich versuche, den folgenden Code in einem Multi-Threading-Szenario zu implementieren:Multiple-Leser, single-Schreiber-Sperren in Boost-

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-Threads haben eine shared_mutex Klasse, die für einen Mehr Leser entworfen wurde, Single - Schreibmaschinenmodell. Es gibt mehrere Stackoverflow-Fragen bezüglich dieser Klasse. Jedoch bin ich nicht sicher, ob es das obige Szenario passt, wo irgendein Leser ein Verfasser werden kann. In der Dokumentation:

Das UpgradeLockable Konzept ist eine Verfeinerung des SharedLockable Konzept, das für aktualisierbar Eigentum sowie Miteigentum und exklusiven Besitz erlaubt. Dies ist eine Erweiterung des Multiple-Leser/ Single-Schreib Modell von dem SharedLockable Konzept zur Verfügung gestellt: ein Einzel Fäden aktualisierbar Eigentum zur gleichen Zeit haben können als andere Eigentum freigegeben hat.

Von dem Wort "single" vermute ich, dass nur ein Thread eine aktualisierbare Sperre enthalten kann. Die anderen halten nur eine gemeinsame Sperre, die nicht zu einer exklusiven Sperre aktualisiert werden kann.

Wissen Sie, ob boost::shared_lock in dieser Situation nützlich ist (jeder Leser kann ein Schreiber werden) oder ob es andere Möglichkeiten gibt, dies zu erreichen?

+0

Was ist Ihre Definition "exklusiver Zugang", wenn es nicht "nur ein Thread kann das Schloss zu einer Zeit halten"? Oder verwirren Sie "einen Thread zu einem Zeitpunkt" mit "einem bestimmten Thread, der im Voraus ausgewählt wurde"? –

+0

@Pete: Ich will einen Thread (* nicht * im Voraus gewählt), um die exklusive Sperre zu halten. – Amnon

Antwort

5

boost::shared_lock nicht in dieser Situation hilft (mehrere Leser, die Schriftsteller werden können), da nur ein einziger Thread eine aktualisierbare Sperre besitzen kann. Dies wird sowohl durch das Zitat aus der Dokumentation in der Frage als auch durch den Code (thread \ win32 \ shared_mutex.hpp) angedeutet. Wenn ein Thread versucht, eine aktualisierbare Sperre zu erhalten, während ein anderer Thread einen enthält, wird auf den anderen Thread gewartet.

Ich entschied mich für eine normale Sperre für alle Leser/Schreiber, was in meinem Fall OK ist, da der kritische Abschnitt kurz ist.

15

Ja, Sie können genau das tun, was Sie wollen, wie in der angenommenen Antwort here gezeigt. Ein Aufruf zum Upgrade auf exklusiven Zugriff wird blockiert, bis alle Leser fertig sind.

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 
} 
+0

Also mehr als ein Thread kann einen upgrade_lock halten? – Amnon

+0

Mehr als ein Thread kann * es * benutzen, aber nur eine * kann es exklusiv * zu jeder Zeit halten. Mehrere gleichzeitige Leser sind in Ordnung, vorausgesetzt, es gibt keinen aktuellen Schreiber. Das ist was du willst, stelle ich mir vor? –

+1

Und wie entsperre ich es dann? –

3

Sie wissen LightweightLock oder gleiche bei LightweightLock_zip
ist genau das, was Sie wollen. Ich habe es lange benutzt.

[EDIT] hier ist die Quelle:


///////////////////////////////////////////////////////////////////////////// 
// 
// 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; 
    }; 


+2

Defekte Links. Und es klingt wie eine .net-Bibliothek, während ich nach einer C++ - Lösung (hinzugefügtem Tag) suche. – Amnon

+0

Nur eine Anmerkung, oben Code ist unfair und es gibt Situationen, in denen Threads nie Zugriff erhalten und ewig warten. – ROAR

+0

Dies geschah nie in der Zeit, die ich diese Implementierung verwende. Es ist sieben Jahre her und der Code läuft mehr als 10 Millionen Mal am Tag. Einfach und sehr gut. – lsalamon