multithreading delphi critical-section readerwriterlockslim readerwriterlock

multithreading - Más rápido TMultiReadExclusiveWriteSynchronizer?



delphi critical-section (5)

¿Prueba esto? Se puede usar como una variable normal:

type myclass=class Lock:TOBRWLock; function ReadIt:Integer; procedure WriteIt(A:Integer); end; function ReadIt:Integer; begin; Lock.LockRead; Result:=GetVal; Lock.UnLockRead; end;

Hay mucho espacio para mejorar y puedes construir desde aquí variedades que favorecen la lectura anterior o simplemente actuar de manera diferente según la necesidad.

const ldFree = 0; ldReading = 1; ldWriting = 2; type TOBRWLock = record [Volatile]WritersWaiting, ReadersWaiting, ReadersReading, Disposition : Integer; procedure LockRead; procedure LockWrite; procedure UnlockRead; procedure UnlockWrite; procedure UnReadWrite; procedure UnWriteRead; end; procedure TOBRWLock.LockRead; var SpinCnt : NativeUInt; I : Integer; begin SpinCnt:=0; TInterlocked.Increment(ReadersWaiting); repeat if (Disposition=ldReading) then begin I:=TInterlocked.Increment(ReadersReading); if (Disposition<>ldReading) or (I=1)(*Only 1 read reference or Disposition changed, suspicious, rather retry*) then begin TInterlocked.Decrement(ReadersReading); continue; end else begin(*Success*) TInterlocked.Decrement(ReadersWaiting); break; end; end; if (WritersWaiting<>0)or(Disposition<>ldFree) then begin SpinBackoff(SpinCnt); continue; end; if TInterlocked.CompareExchange(Disposition,ldReading,ldFree)=ldFree then begin TInterlocked.Increment(ReadersReading); TInterlocked.Decrement(ReadersWaiting); break; end; SpinBackoff(SpinCnt); until False; end; procedure TOBRWLock.LockWrite; var SpinCnt : NativeUInt; begin SpinCnt:=0; TInterlocked.Increment(WritersWaiting); repeat if (Disposition<>ldFree) then begin SpinBackoff(SpinCnt); continue; end; if TInterlocked.CompareExchange(Disposition,ldWriting,ldFree)=ldFree then begin TInterlocked.Decrement(WritersWaiting); break; end else SpinBackoff(SpinCnt); until False; end; procedure TOBRWLock.UnlockRead; begin {$IFDEF DEBUG} if Disposition<>ldReading then raise Exception.Create(''UnlockRead a lock that is not Reading''); {$ENDIF} TInterlocked.Decrement(ReadersReading); if ReadersReading=0 then begin; if TInterlocked.CompareExchange(Disposition,ldFree,ldReading)<>ldReading then raise Exception.Create(''Impossible 310''); end; end; procedure TOBRWLock.UnlockWrite; begin {$IFDEF DEBUG} if Disposition<>ldWriting then raise Exception.Create(''UnlockWrite a lock that is not Writing''); {$ENDIF} if TInterlocked.CompareExchange(Disposition,ldFree,ldWriting)<>ldWriting then raise Exception.Create(''Impossible 321''); end; procedure TOBRWLock.UnReadWrite; var SpinCnt : NativeUInt; begin {$IFDEF DEBUG} if Disposition<>ldReading then raise Exception.Create(''UnReadWrite a lock that is not Reading''); {$ENDIF} TInterlocked.Increment(WritersWaiting); SpinCnt:=0; repeat if ReadersReading=1(*Only me reading*) then begin; if TInterlocked.CompareExchange(Disposition,ldWriting,ldReading)<>ldReading(*Must always succeed*) then raise Exception.Create(''Impossible 337''); TInterlocked.Decrement(ReadersReading); TInterlocked.Decrement(WritersWaiting); break; end; SpinBackoff(SpinCnt); until False; end;

¿Existe un tipo más rápido de TMultiReadExclusiveWriteSynchronizer por ahí? ¿FastCode quizás?

Comenzando con Windows Vista, Microsoft agregó un bloqueo Slim Reader / Writer . Funciona mucho mejor que TMultiReadExclusiveWriteSynchronizer de Delphi. Lamentablemente, solo existe en Windows Vista y posteriormente, algo que pocos clientes realmente tienen todavía.

Es de suponer que los conceptos en uso dentro de un Slim Reader/Writer lock podrían volver a reproducirse en el código nativo de Delphi, pero ¿alguien lo ha hecho?

Tengo una situación en la que adquirir y liberar bloqueos en un TMultiReadExclusiveWriteSynchronizer (incluso cuando no hay contención, un único hilo), provoca un 100% de sobrecarga (el tiempo de operación se duplica). Puedo correr sin bloquear, pero luego mi clase ya no es segura para subprocesos.

¿Hay un TMultiReadExclusiveWriteSynchronizer más TMultiReadExclusiveWriteSynchronizer ?

Nota : si uso TCriticalSection solo sufriré un 2% de rendimiento (aunque se sabe que las secciones críticas son rápidas cuando la adquisición tiene éxito, es decir, mientras tiene un solo hilo y no hay contienda). La desventaja de una CS es que pierdo la capacidad de " lectores múltiples ".

Las medidas

Al usar TMultiReadExclusiveWriteSynchronizer una considerable cantidad de tiempo dentro de BeginRead y EndRead :

Luego porté el código para usar el propio SlimReaderWriter Lock de Windows (que algunos reescriben, ya que no admite el bloqueo recursivo), y se perfilaron los resutls:

  • TMultiReadExclusiveWriteSynchronizer : 10.698 ns por iteración
    10,697,772,613 ns para iterar 1,000,000 veces

  • SRWLock : SRWLock ns por iteración
    8,801,678,339 ns para iterar 1,000,000 veces

  • Omni Reader-Writer lock : 8.941 ns por iteración
    8,940,552,487 ns para iterar 1,000,000 veces

Una mejora del 17% cuando se usa SRWLocks (también conocido como el bloqueo giratorio de Omni).

Ahora, no puedo cambiar el código de forma permanente para usar Windows Vista SRWLocks , ya que hay algunas empresas enteras de clientes que todavía están en Windows XP.

Los bloqueos Slim son solo uso cuidadoso de las funciones InterlockedCompareExchange ; pero más cuidadoso de lo que puedo usar con éxito. Estoy tan lejos de solo robar las 140 instrucciones de la máquina involucradas, y hacerlo.

Lectura de bonificación


Al final utilicé una solución de compromiso. El bloqueo Omni reader-writer utiliza principios "delgados" (manipulación de bits giratorios). Al igual que el de Window, no es compatible con la escalada de bloqueo. Lo probé y parece que no se bloqueó ni se bloqueó.

Al final usé una situación de repliegue. La más genérica de las interfaces genéricas para admitir conceptos de "lectura-escritura" :

IReaderWriterLock = interface [''{6C4150D0-7B13-446D-9D8E-866B66723320}''] procedure BeginRead; procedure EndRead; procedure BeginWrite; procedure EndWrite; end;

Y luego decidimos en tiempo de ejecución qué implementación usar. Si estamos en Windows Vista o en versiones nuevas, use el propio SlimReaderWriter , de lo contrario SlimReaderWriter a la versión de Omni :

TReaderWriterLock = class(TObject) public class function Create: IReaderWriterLock; end; class function TReaderWriterLock.Create: IReaderWriterLock; begin if Win32MajorVersion >= 6 then //SRWLocks were introduced with Vista/Server 2008 (Windows version 6.0) begin //Use the Windows built-in Slim ReaderWriter lock Result := TSlimReaderWriterLock.Create; end else begin //XP and earlier fallback to Omni equivalent Result := TOmniReaderWriterLock.Create; end; end;

Nota : Cualquier código se libera en el dominio público. No se requiere atribución.


Delphi TMultiReadExclusiveWriteSynchronizer es muy sofisticado: se puede adquirir recursivamente y puede actualizar desde Read a Write .

Esto tiene un costo, que en este caso significa administrar un depósito de estado compartido por hilo. Como la mecánica local de subprocesos de Windows (accesible a través de threadvar ) es demasiado simplista para esto (no es capaz de hacer frente a varias instancias MREWS) se hace de una manera bastante ineficaz - ver las fuentes RTL o JCL - las implementaciones son bastante similares, compartiendo malas el rendimiento y el riesgo de bloqueo de la actualización.

Primero asegúrese de que realmente necesita la funcionalidad MREWS. Supongo que, de acuerdo con el tamaño proporcional de la carga de trabajo de la carga de trabajo, será mucho mejor con TCriticalSection .

Si realmente lo necesita, vaya a la implementación de Delphi y tenga cuidado con el posible desbloqueo oculto en BeginWrite : consulte su documentación y el significado del valor de retorno.

Es posible implementar un SRW similar a Vista utilizando las funciones Interlocked o el ensamblaje en línea, pero no vale la pena el esfuerzo en la mayoría de los casos.



TOmniMREW de OmniThreadLibrary afirma ser más rápido y ligero:

OTL es una excelente versión de threading lib, BTW.

Código de muestra

TOmniReaderWriterLock = class(TInterfacedObject, IReaderWriterLock) private omrewReference: Integer; public { IReaderWriterLock } procedure BeginRead; procedure EndRead; procedure BeginWrite; procedure EndWrite; end; { TOmniReaderWriterLock } procedure TOmniReaderWriterLock.BeginRead; var currentReference: Integer; begin //Wait on writer to reset write flag so Reference.Bit0 must be 0 than increase Reference repeat currentReference := Integer(omrewReference) and not 1; until currentReference = Integer(InterlockedCompareExchange(Pointer(omrewReference), Pointer(Integer(currentReference) + 2), Pointer(currentReference))); end; procedure TOmniReaderWriterLock.EndRead; begin //Decrease omrewReference InterlockedExchangeAdd(@omrewReference, -2); end; procedure TOmniReaderWriterLock.BeginWrite; var currentReference: integer; begin //Wait on writer to reset write flag so omrewReference.Bit0 must be 0 then set omrewReference.Bit0 repeat currentReference := omrewReference and (not 1); until currentReference = Integer(InterlockedCompareExchange(Pointer(omrewReference), Pointer(currentReference+1), Pointer(currentReference))); //Now wait on all readers repeat until omrewReference = 1; end; procedure TOmniReaderWriterLock.EndWrite; begin omrewReference := 0; end;