본문 바로가기
개발일지/아키텍트

POSA2, Double-Checked Locking Optimization 패턴

by 사악신 2012. 4. 9.

이 녀석도 그렇게 대단한 놈은 아니지만, 멀티 스레드 환경에서 Singleton 을 사용하는 경우라면 꼭 기억해 둘 필요가 있겠다. 내 경우 Delphi 에서 보통 다음처럼 Singleton 을 작성한다.

TSingleton = class
protected
  class var Instance: TSingleton;

  constructor Create;
  destructor Destroy; override;
public
  class function GetInstance: TSingleton;
  class procedure ReleaseInstance;
end;



{ TSingleton }

constructor TSingleton.Create;
begin
  inherited;

end;

destructor TSingleton.Destroy;
begin

  inherited;
end;

class function TSingleton.GetInstance: TSingleton;
begin
  if not Assigned(Instance) then
  begin

    if not Assigned(Instance) then Instance := Create;
  end;
  Result := Instance;
end;

class procedure TSingleton.ReleaseInstance;
begin
  if Assigned(Instance) then FreeAndNil(Instance);
end;

 

이때 GetInstance 를 호출하는 시점을 디어셈블해보면 다음과 같다.

 

 

 

파스칼로 한 줄이라도 CPU 기준으로 봤을때 인스트럭션이 여러개 발생한다. 문제는 스레드의 컨텍스트 스위칭이 인스트럭션 단위로 발생하는데 있다. 즉, 최초 인스턴스의 존재 여부를 확인 후, 생성하는 과정에서 스레드간 경쟁(race condition)에 의하여 복수개의 인스턴스가 생성될 수 있는 잠재적 위험요소가 있는 것이다. 여기서 Scoped Locking 을 사용하여 동기화한다고 해보자.

class function TSingleton.GetInstance: TSingleton;
var
  Guard: TGuard;
begin
  if not Assigned(Instance) then
  begin
    Guard := TGuard.Create(Cri);
    try
      Instance := Create;
    finally
      Guard.Free;
    end;
  end;
  Result := Instance;
end;

 

해당 부분을 디어셈블해보면 다음과 같다.

 

 

 

이 경우도 문제가 발생할 수 있는데, 스레드간 race condition 으로 Instance 의 할당 여부를 검사하는 부분을 복수개의 스레드가 통과할 수 있기 때문이다. 이 후, TGuard 에 의해 Scoped Locking 이 걸린다고 하여도 해당 검사 부분을 통과한 스레드는 이 후 순차적으로 인스턴스를 생성하게 된다. 따라서, 이러한 문제점까지 해결하려면 Locking 이 된 이후에 한번 더 Instance 의 생성 여부를 검사해주어야하는 것이다.

class function TSingleton.GetInstance: TSingleton;
var
  Guard: TGuard;
begin
  if not Assigned(Instance) then
  begin
    Guard := TGuard.Create(Cri);
    try
      if not Assigned(Instance) then Instance := Create;
    finally
      Guard.Free;
    end;
  end;
  Result := Instance;
end;

 

이렇게 두 번 검사한다고 하여 Double-Checked Locking 이라고 부른다.

 

 

반응형

댓글