Ho una struttura dati in memoria che viene letta da più thread e scritta da un solo thread. Attualmente sto usando una sezione critica per rendere questo accesso sicuro. Sfortunatamente questo ha l'effetto di bloccare i lettori anche se solo un altro lettore vi accede.Lettore di scritture multiple free lock singolo
Ci sono due opzioni per porre rimedio a questo:
- uso TMultiReadExclusiveWriteSynchronizer
- farla finita con qualsiasi blocco utilizzando un approccio libero blocco
Per 2. Ho ottenuto il seguente finora (qualsiasi codice che non importa è stato tralasciato):
type
TDataManager = class
private
FAccessCount: integer;
FData: TDataClass;
public
procedure Read(out _Some: integer; out _Data: double);
procedure Write(_Some: integer; _Data: double);
end;
procedure TDataManager.Read(out _Some: integer; out _Data: double);
var
Data: TDAtaClass;
begin
InterlockedIncrement(FAccessCount);
try
// make sure we get both values from the same TDataClass instance
Data := FData;
// read the actual data
_Some := Data.Some;
_Data := Data.Data;
finally
InterlockedDecrement(FAccessCount);
end;
end;
procedure TDataManager.Write(_Some: integer; _Data: double);
var
NewData: TDataClass;
OldData: TDataClass;
ReaderCount: integer;
begin
NewData := TDataClass.Create(_Some, _Data);
InterlockedIncrement(FAccessCount);
OldData := TDataClass(InterlockedExchange(integer(FData), integer(NewData));
// now FData points to the new instance but there might still be
// readers that got the old one before we exchanged it.
ReaderCount := InterlockedDecrement(FAccessCount);
if ReaderCount = 0 then
// no active readers, so we can safely free the old instance
FreeAndNil(OldData)
else begin
/// here is the problem
end;
end;
Sfortunatamente c'è il piccolo problema di eliminare l'istanza di OldData dopo che è stata sostituita. Se nessun altro thread è correntemente nel metodo di lettura (ReaderCount = 0), può essere smaltito in modo sicuro e il gioco è fatto. Ma cosa posso fare se non è questo il caso? Potrei semplicemente memorizzarlo fino alla prossima chiamata e disporlo lì, ma la programmazione di Windows potrebbe in teoria far dormire un lettore mentre è all'interno del metodo di lettura e ha ancora un riferimento a OldData.
Se si riscontrano altri problemi con il codice precedente, comunicacelo. Questo deve essere eseguito su computer con più core e i metodi sopra indicati devono essere chiamati molto frequentemente.
Nel caso questo importi: sto usando Delphi 2007 con il gestore di memoria incorporato. Sono consapevole del fatto che il gestore della memoria probabilmente impone qualche blocco in ogni caso durante la creazione di una nuova classe, ma per il momento voglio ignorarlo.
Modifica: Potrebbe non essere stato chiaro da quanto sopra: Per l'intera durata dell'oggetto TDataManager c'è solo un thread che scrive sui dati, non diversi che potrebbero competere per l'accesso in scrittura. Quindi questo è un caso speciale di MREW.
Sono diffidente nei confronti del codice di blocco automatico, è quasi impossibile farlo correttamente. Per quanto riguarda TMREWS: non c'è modo di gestire il caso d'uso su macchine tipiche, in quanto esistono diversi modi per implementarle e il VCL ne offre solo uno. Per un articolo che confronta le diverse implementazioni (compresi i tempi) vedi http://www.codeproject.com/KB/threads/testing_rwlocks.aspx – mghie