5

En C#, el siguiente código (de this página) se puede utilizar para crear instancias de una clase Singleton perezosamente de una manera segura hilo:¿Cómo se debe implementar el "bloqueo con doble control" en Delphi?

class Foo { 
     private volatile Helper helper = null; 
     public Helper getHelper() { 
      if (helper == null) { 
       lock(this) { 
        if (helper == null) 
         helper = new Helper(); 
       } 
      } 
      return helper; 
     } 
    } 

¿Cuál sería el código de Delphi hilo de seguridad equivalente?


El artículo también menciona dos problemas con una doble comprobación de bloqueo en Java:

  • es posible que el nuevo objeto se construye antes de que se hizo la referencia de ayuda para apuntar al objeto recién creado lo que significa que se crean dos objetos
  • es posible que la referencia auxiliar esté hecha para apuntar a un bloque de memoria mientras el objeto todavía se está creando, lo que significa que se devolverá una referencia a un objeto incompleto

Por lo tanto, aunque el código de C# y la versión de Java en el artículo mencionado son casi idénticos, solo la versión de C# funciona como se esperaba. ¿Qué lleva a la pregunta adicional si estos dos problemas también existen en una versión Delphi de bloqueo con doble control?

Respuesta

7

Use System.TMonitor para bloquear la instancia del objeto de una manera segura para la ejecución de subprocesos.

function TFoo.GetHelper(): THelper; 
begin 
    if not Assigned(FHelper) then 
    begin 
    System.MonitorEnter(Self); 
    try 
     if not Assigned(FHelper) then 
     FHelper := THelper.Create(); 
    finally 
     System.MonitorExit(Self); 
    end; 
    end; 
    Result := FHelper; 
end; 

Para más mirada de referencia a partir de Lock my object..., please!Allen Bauer. De hecho, el representante. Deduzco que esto debería ir a Allen.

+1

No podría haberlo dicho mejor ;-) –

+0

Tengo la sensación de que falta algo esencial en este código: la implementación de C# usa la palabra clave 'volátil' para la variable auxiliar privada. Supongo que FHelper ** debe ** declararse como 'threadvar' – mjn

+0

threadvar daría como resultado uno por hilo en lugar de un singleton compartido por todos los hilos. El volátil puede deberse al modelo de memoria .net. Pero el código anterior es correcto en x86. –

2

Por supuesto, siempre vale la pena recordar que Double-Checked Locking is Broken. Este problema no se aplica al modelo de memoria x86, pero siempre vale la pena tenerlo en cuenta para el futuro. Estoy seguro de que habrá una versión de Delphi en algún momento que se ejecutará en una plataforma con un modelo de memoria que está afectado por este problema.

Embarcadero ha comenzado a utilizar una versión sin cerradura de este patrón con comparación/intercambio entrelazados. Por ejemplo:

class function TEncoding.GetUnicode: TEncoding; 
var 
    LEncoding: TEncoding; 
begin 
    if FUnicodeEncoding = nil then 
    begin 
    LEncoding := TUnicodeEncoding.Create; 
    if InterlockedCompareExchangePointer(Pointer(FUnicodeEncoding), LEncoding, nil) <> nil then 
     LEncoding.Free; 
    end; 
    Result := FUnicodeEncoding; 
end; 

Me doy cuenta de que esta no es una respuesta a la pregunta, ¡pero realmente no encajaba en ningún comentario!

+0

¿El bloqueo con doble control * siempre * está roto? El artículo mencionado dice que el ejemplo anterior de C# 'funciona como se esperaba'. – mjn

+0

Depende del modelo de memoria en uso. Funciona en x86 pero no estoy seguro acerca de x64. Está roto en Java que tiene su propio modelo de memoria. Es muy complejo sin embargo. –

+0

x86 y x64 usan el mismo modelo de memoria "fuerte", sin embargo Itanium es un poco diferente según http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/ – mjn

Cuestiones relacionadas