Locken einzelner Arrayelemente (Mutex)
-
Hallo,
ich habe ein Objekt der Klasse Node, das in einem Array konstanter Größe auf andere Objekte der Klasse Slot verweist. Diese Verweise werden in einer Art boost::intrusive_ptr gehalten.
Zur Zeit der Initialisierung sind alle Verweise des Arrays ungültig. Nun gibt es Funktionen, um ein Arrayelement zu setzen (setChild, Zeile 33), und um Arrayelement zu holen (getChild, Zeile 26).
Mein Anliegen ist es nun, dass Stellen, an denen ein Verweis des Array gelesen (Zeile 30) oder geschrieben (Zeile 37) werden, gelockt werden. Dabei soll jedoch nur auf ein bestimmtes Arrayelement gelockt werden, weil das Lesen und Ändern der anderen Arrayelement meines Erachtens kein Problem wäre.
Eine Lösung wäre sicherlich ein Array von mutex zu erstellen. Jedoch würde ich eine Speicher-sparende Lösung vorziehen.Wäre es mögliche die Sperrinformation in einer Variable abzulegen, dessen Anzahl von Bits mit SIZE (Zeile 1) übereinstimmt? Welche Lösungen gibt es dazu?
const int SIZE = 32; class Node { typedef Slot slot_type; typedef intrusive_ptr<slot_type> pslot_type; typedef /* ? */ lock_type; typedef /* ? */ mutex_type; pslot_type* m_slots; mutex_type m_mutex; public: Node() : m_slots(new pslot_type[SIZE]), m_mutex() { } ~Node() { delete[] m_slots; } //... pslot_type getChild(unsigned int index) { //... lock_type lock(m_mutex, index); return m_slots[index]; } //... void setChild(unsigned int index, pslot_type child) { //... { lock_type lock(m_mutex, index); m_slots[index] = child; } //... } }Falls ich es nicht geschafft haben sollte, mein Anliegen verständlich rüber zu bringen, einfach nachfragen.

Vielen Dank
-
Auf der Ebene von Locks wirst du wahrscheinlich nicht darum herum kommen ein Array zu halten.
Da du aber relativ einfache Operationen hast kannst du dir ja überlegen die ganze Operation mit einer atomischen Funktion zu erledigen. Die WinAPI bietet dafür viele Intrinsics an:
http://msdn.microsoft.com/en-us/library/ms686360(v=VS.85).aspxDa muss man aber beachten ob sie überhaupt vorhanden sind und wenn du es plattformunabhängig haben willst wirst du auch noch eine Unterscheidung für Linux machen müssen.
Ob sich der Aufwand im Gegensatz zu dem benutzen Speicher lohnt musst du selbst abschätzen.
-
Der Code soll plattformunabhängig sein.
Also atomare Operationen hätte ich folgende zur Verfügung (ohne dass _ich_ mir Gedanken, um die Plattform machen müsste):
- Compare-and-Swap
- atomic swap
- atomic read
- atomic writeDie Signaturen dazu sehen folgendermaßen aus:
T atomic_swap(volatile T& ref, T val); ref --> Variable, die geschrieben wird val --> Wert, der geschrieben wird return --> alter Wert von ref T atomic_read(const T& ref); ref --> Variable, die gelesen werden soll return --> Wert der Variablen void atomic_write(T& ref, T val); ref --> Variable, die geschrieben wird val --> Wert, der geschrieben wirdDer eigentliche Gedanken hinter dem Locking ist, dass der Zähler des Smart Pointer nicht verfälscht werden soll. D.h. wenn ein Kindelement zurückgegeben wird, soll garantiert sein, dass dieses solange gültig bleibt, wie der Smart Pointer, vom Aufrufer von getChild, gültig bleibt.
Um das zu erreichen muss ja der Kopierkonstruktur bzw. der Zuweisungsoperator des Smart Pointers atomar ausgeführt werden.Ohne Locking bzw. atomarer Operation wäre es ja möglich, dass in setChild die letzte Referenz auf ein Objekt gelöst wird, wenn gleichzeitig genau jenes Objekt in getChild zurückgegeben wird.
Kann ich das gewünscht Verhalten, mithilfe der zur Verfügung stehenden atomaren Operationen erreichen? Meine Vorstellung würde dann in diese Richtung gehen:
const int SIZE = 32; class Node { typedef Slot slot_type; typedef intrusive_ptr<slot_type> pslot_type; pslot_type* m_slots; public: Node() : m_slots(new pslot_type[SIZE]), m_mutex() { } ~Node() { delete[] m_slots; } //... pslot_type getChild(unsigned int index) { //... return atomic_read(m_slots[index]); } //... void setChild(unsigned int index, pslot_type child) { //... atomic_write(m_slots[index], child); //... } }
-
Ich würde einfach mal pauschal sagen, dass intrusive pointer hier die falsche Wahl ist, ein shared pointer würde sicheres Kopieren auch ohne Locking zulassen. Bleibt nat. die Frage ob nicht die Zugriffe auf das Gezeigte (Slot) selbst noch zusätzlich synchronisiert werden müssen.
-
camper schrieb:
Bleibt nat. die Frage ob nicht die Zugriffe auf das Gezeigte (Slot) selbst noch zusätzlich synchronisiert werden müssen.
Da sehe ich auch das Problem das ohne ein Lock auf Indexebene zu machen. Sofern Slot selbst nicht atomar arbeitet kann man sich auf gar nichts verlassen.
-
camper schrieb:
Ich würde einfach mal pauschal sagen, dass intrusive pointer hier die falsche Wahl ist, ein shared pointer würde sicheres Kopieren auch ohne Locking zulassen.
Die Sache ist, dass mehrere Threads den Wert von m_slots[index] verändern könnten. Ich denke das würde genau auf dieses Fall aus http://www.boost.org/doc/libs/1_47_0/libs/smart_ptr/shared_ptr.htm#ThreadSafety zutreffen:
//--- Example 3 --- // thread A p = p3; // reads p3, writes p // thread B p3.reset(); // writes p3; undefined, simultaneous read/writeDas heißt für meine Verwendung wären auch shared_ptr nicht einfach so Thread-sicher.
Oder verstehe ich da was falsch?camper schrieb:
Bleibt nat. die Frage ob nicht die Zugriffe auf das Gezeigte (Slot) selbst noch zusätzlich synchronisiert werden müssen.
Ob Zugriffe im Slot-Objekt selbst synchronisiert werden müssen, ist eine andere Frage, die hier aber nicht betrachtet werden soll.