Tengo una estructura de datos en la memoria que es leída por varios hilos y escrita por un solo hilo. Actualmente estoy usando una sección crítica para hacer que este acceso sea seguro para los hilos. Desafortunadamente, esto tiene el efecto de bloquear a los lectores aunque solo otro lector esté accediendo a él.Lectores múltiples sin bloqueo único escritor
Hay dos opciones para remediar esto:
- uso TMultiReadExclusiveWriteSynchronizer
- acabar con cualquier bloqueo mediante el uso de un enfoque libre de bloqueo
Por 2. Tengo el siguiente hasta el momento (cualquier código que no importe ha sido dejado fuera):
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;
Desafortunadamente, existe el pequeño problema de deshacerse de la instancia de OldData después de que se ha reemplazado. Si no hay otro subproceso actualmente en el método de lectura (ReaderCount = 0), se puede eliminar de manera segura y eso es todo. Pero, ¿qué puedo hacer si ese no es el caso? Podría simplemente almacenarlo hasta la siguiente llamada y disponerlo allí, pero la programación de Windows podría, en teoría, permitir que un lector se quede dormido mientras está dentro del método Read y todavía tiene una referencia a OldData.
Si ve algún otro problema con el código anterior, hábleme al respecto. Esto se debe ejecutar en computadoras con múltiples núcleos y los métodos anteriores se deben llamar con mucha frecuencia.
En caso de que esto importe: estoy usando Delphi 2007 con el administrador de memoria incorporado. Soy consciente de que el administrador de memoria probablemente aplica algún bloqueo de todos modos al crear una nueva clase, pero quiero ignorar eso por el momento.
Editar: Puede no haber quedado claro de lo anterior: durante toda la vida útil del objeto TDataManager solo hay un hilo que escribe en los datos, no varios que podrían competir por el acceso de escritura. Entonces este es un caso especial de MREW.
soy cuidadoso de código libre de la cerradura auto-escrita, es casi imposible hacerlo bien. En cuanto a TMREWS: no hay forma de determinar el tiempo de su caso de uso en máquinas típicas, ya que existen diferentes formas de implementarlas, y la VCL le brinda solo una. Para un artículo comparando las distintas aplicaciones (incluyendo el tiempo) ver http://www.codeproject.com/KB/threads/testing_rwlocks.aspx – mghie