ReaderWriterLock

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8812
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

ReaderWriterLock

Beitrag von NicTheQuick »

Hallo Leute,

da hier im Forum immer mehr über Threads und deren Handling untereinander gesprochen wird, habe ich mir gedacht ich trage auch mal etwas dazu bei und biete hiermit ein Interface zur Lösung des Reader-Writer-Problems an.

Die Handhabung ist einfach. Für eine Datenstruktur, auf der aus verschiedenen Threads heraus gelesen und geschrieben werden soll, muss man einfach nur vor und nach jedem Lesen und Schreiben die entsprechenden Methoden aus dem Interface aufrufen und schon können sich keine zwei Threads mehr in die Quere kommen. Es ist immer gewährleistet, dass ein Lese-Thread keine halben Daten liest und dass ein Schreib-Thread noch am Schreiben ist während bereits aus der Datenstruktur gelesen wird.

Hier also zunächst das Interface und nachfolgend ein simples Beispiel mit einer zugegebenermaßen simplen Datenstruktur, nämlich einer LinkedList vom Typ String. Zur Vervollständigung sei noch gesagt, dass man trotzdem bei mehreren Datenstrukturen, die Strings enthalten und die verschiedene ReaderWriterLocks nutzen, die ThreadSafe-Option in den Compiler-Einstellungen aktivieren muss. Für dieses Beispiel ist dies allerdings nicht notwendig.

Womöglich werde ich in Zukunft öfter Beispiele aus dem Bereich der sicheren Thread-Kommunikation bringen. Als nächstes habe ich z.B. an geschwindigkeits-optimierte thread-sichere LinkedLists gedacht.

ReaderWriterLock.pbi

Code: Alles auswählen

;get from http://en.wikipedia.org/wiki/Readers-writers_problem
;---------------------------------------------------------
; int readcount, writecount; (initial value = 0)
; semaphore mutex 1, mutex 2, mutex 3, w, r ; (initial value = 1)
;  
; READER
;   P(mutex 3);
;     P(r);
;       P(mutex 1);
;         readcount := readcount + 1;
;         If readcount = 1 then P(w);
;       V(mutex 1);
;     V(r);
;   V(mutex 3);
;  
;   reading is done
;  
;   P(mutex 1);
;     readcount := readcount - 1;
;     If readcount = 0 then V(w);
;   V(mutex 1);
;  
;  
; WRITER
;     P(mutex 2);
;       writecount := writecount + 1;
;       If writecount = 1 then P(r);
;     V(mutex 2);
;  
;   P(w);
;     writing is performed
;   V(w);
;  
;   P(mutex 2);
;     writecount := writecount - 1;
;     If writecount = 0 then V(r);
;   V(mutex 2);

EnableExplicit

Interface ReaderWriterLock
	free.i()
	beginReading.i()
	endReading.i()
	beginWriting.i()
	endWriting.i()
EndInterface

Structure ReaderWriterLock_S
	*vTable
	readCount.i
	writeCount.i
	mutex1.i
	mutex2.i
	mutex3.i
	w.i
	r.i
EndStructure

Procedure.i newReaderWriterLock()
	Protected *this.ReaderWriterLock_S
	
	*this = AllocateMemory(SizeOf(ReaderWriterLock_S))
	If (Not *this)
		ProcedureReturn #False
	EndIf
	
	InitializeStructure(*this, ReaderWriterLock_S)
	
	With *this
		\vTable = ?ReaderWriterLock_vTable
		\readCount = 0
		\writeCount = 0
		\mutex1 = CreateSemaphore(1)
		\mutex2 = CreateSemaphore(1)
		\mutex3 = CreateSemaphore(1)
		\w = CreateSemaphore(1)
		\r = CreateSemaphore(1)
	EndWith
	
	ProcedureReturn *this
EndProcedure

Procedure.i ReaderWriterLock_free(*this.ReaderWriterLock_S)
	With *this
		FreeSemaphore(*this\mutex1)
		FreeSemaphore(*this\mutex2)
		FreeSemaphore(*this\mutex3)
		FreeSemaphore(*this\w)
		FreeSemaphore(*this\r)
	EndWith
	ClearStructure(*this, ReaderWriterLock_S)
	
	ProcedureReturn #True
EndProcedure

;   P(mutex 3);
;     P(r);
;       P(mutex 1);
;         readcount := readcount + 1;
;         If readcount = 1 then P(w);
;       V(mutex 1);
;     V(r);
;   V(mutex 3);
Procedure.i ReaderWriterLock_beginReading(*this.ReaderWriterLock_S)
	With *this
		WaitSemaphore(\mutex3)
		WaitSemaphore(\r)
		WaitSemaphore(\mutex1)
		\readCount + 1
		If (\readCount = 1)
			WaitSemaphore(\w)
		EndIf
		SignalSemaphore(\mutex1)
		SignalSemaphore(\r)
		SignalSemaphore(\mutex3)
	EndWith
EndProcedure

;   P(mutex 1);
;     readcount := readcount - 1;
;     If readcount = 0 then V(w);
;   V(mutex 1);
Procedure.i ReaderWriterLock_endReading(*this.ReaderWriterLock_S)
	With *this
		WaitSemaphore(\mutex1)
		\readCount - 1
		If (\readCount = 0)
			SignalSemaphore(\w)
		EndIf
		SignalSemaphore(\mutex1)
	EndWith
EndProcedure

;     P(mutex 2);
;       writecount := writecount + 1;
;       If writecount = 1 then P(r);
;     V(mutex 2);
;  
;   P(w);
Procedure.i ReaderWriterLock_beginWriting(*this.ReaderWriterLock_S)
	With *this
		WaitSemaphore(\mutex2)
		\writeCount + 1
		If (\writeCount = 1)
			WaitSemaphore(\r)
		EndIf
		SignalSemaphore(\mutex2)
		WaitSemaphore(\w)
	EndWith
EndProcedure

;   V(w);
;  
;   P(mutex 2);
;     writecount := writecount - 1;
;     If writecount = 0 then V(r);
;   V(mutex 2);
Procedure.i ReaderWriterLock_endWriting(*this.ReaderWriterLock_S)
	With *this
		SignalSemaphore(\w)
		WaitSemaphore(\mutex2)
		\writeCount - 1
		If (\writeCount = 0)
			SignalSemaphore(\r)
		EndIf
		SignalSemaphore(\mutex2)
	EndWith
EndProcedure

DataSection ;{ ReaderWriterLock_vTable
	ReaderWriterLock_vTable:
		Data.i @ReaderWriterLock_free()
		Data.i @ReaderWriterLock_beginReading()
		Data.i @ReaderWriterLock_endReading()
		Data.i @ReaderWriterLock_beginWriting()
		Data.i @ReaderWriterLock_endWriting()
EndDataSection ;}
ReaderWriterLock_Example.pb

Code: Alles auswählen

EnableExplicit

XIncludeFile "ReaderWriterLock.pbi"

; 46   public Static void main(String[] args) {
; 47     Dictionary dictionary = new Dictionary();
; 48     dictionary.set("java",  "object oriented");
; 49     dictionary.set("linux", "rulez");
; 50     Writer writer  = new Writer(dictionary, "Mr. Writer");
; 51     Reader reader1 = new Reader(dictionary ,"Mrs Reader 1");
; 52     Reader reader2 = new Reader(dictionary ,"Mrs Reader 2");
; 53     Reader reader3 = new Reader(dictionary ,"Mrs Reader 3");
; 54     Reader reader4 = new Reader(dictionary ,"Mrs Reader 4");
; 55     Reader reader5 = new Reader(dictionary ,"Mrs Reader 5");
; 56     writer.start();
; 57     reader1.start();
; 58     reader2.start();
; 59     reader3.start();
; 60     reader4.start();
; 61     reader5.start();
; 62   }

Structure ThreadData
	*rwl.ReaderWriterLock
	List someData.s()
	quit.i
EndStructure

Procedure Writer(*threadData.ThreadData)
	With *threadData
		While (Not \quit)
			\rwl\beginWriting()
			If (AddElement(\someData()))
				\someData() = "Data: " + Str(ElapsedMilliseconds()) + " - " + Str(Random(1000))
			EndIf
			\rwl\endWriting()
			Delay(100)
		Wend
	EndWith
EndProcedure

Procedure Reader(*threadData.ThreadData)
	With *threadData
		While (Not \quit)
			\rwl\beginReading()
			Debug "someData():"
			ForEach \someData()
				Debug \someData()
			Next
			\rwl\endReading()
			Delay(100)
		Wend
	EndWith
EndProcedure

Define threadData.ThreadData

With threadData
	\rwl = newReaderWriterLock()
	\quit = #False
EndWith

Define.i writer, reader1, reader2, reader3, reader4, reader5

writer = CreateThread(@Writer(), @threadData)
reader1 = CreateThread(@Reader(), @threadData)
reader2 = CreateThread(@Reader(), @threadData)
reader3 = CreateThread(@Reader(), @threadData)
reader4 = CreateThread(@Reader(), @threadData)
reader5 = CreateThread(@Reader(), @threadData)

Delay(2000)
threadData\quit = #True

WaitThread(writer)
WaitThread(reader1)
WaitThread(reader2)
WaitThread(reader3)
WaitThread(reader4)
WaitThread(reader5)

threadData\rwl\free()