C# - 프로세스(EXE) 수준의 Singleton 개체 생성
운영체제에 의해서 프로세스 격리가 제공되는 것을, 닷넷은 다시 프로세스 내부를 AppDomain이라는 단위로 격리시키고 서로 간의 직접 접근이 불가능하도록 만들었습니다.
그래서, Native 언어(예: C/C++)에서는 static으로 singleton을 만들었던 것을 닷넷에서는 static으로 지정했다고 프로세스 전역적으로 한 개만 존재하지는 않습니다, AppDomain 별로 존재하는 것이지.
이에 대한 해결책을 다음의 글에서 제공하고 있습니다.
Cross AppDomain Singleton
; http://ingebrigtsen.info/2007/05/18/cross-appdomain-singleton/
근데, 코드가 별로 마음에 안 듭니다. ^^ Interop DLL이 별도로 추가된다는 것은 관리적인 요소만 하나 더 늘리게 되는 요인이 됩니다. 그래서, 아래의 방법을 곁들이면 더 좋습니다. ^^
레지스트리 등록 및 Interop DLL 없이 COM 개체 사용하는 방법
; https://www.sysnet.pe.kr/2/0/1180
public class CrossAppDomainSingleton<T> : MarshalByRefObject where T : new()
{
...[생략]...
private static AppDomain GetAppDomain(string friendlyName)
{
IntPtr enumHandle = IntPtr.Zero;
ICorRuntimeHost host = null;
try
{
host = Utility.CoCreateInstance("{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}") as ICorRuntimeHost;
if (host == null)
{
return null;
}
host.EnumDomains(out enumHandle);
object domain = null;
while (true)
{
host.NextDomain(enumHandle, out domain);
if (domain == null)
{
break;
}
AppDomain appDomain = (AppDomain)domain;
if (appDomain.FriendlyName.Equals(friendlyName))
{
return appDomain;
}
}
}
finally
{
host.CloseEnum(enumHandle);
Marshal.ReleaseComObject(host);
host = null;
}
return null;
}
...[생략]...
}
지면(?) 관계상
ICorRuntimeHost 등의 구현은 이 글의 첨부 파일로 참고하시면 됩니다.
그나저나, 어쩔 수 없이 프로세스 수준의 Singleton을 사용해야 하지만 성능 손실은 과연 어느 정도일까요? 이를 위해 다음과 같이 테스트 코드를 작성해 봤습니다.
public static class NonSingleton
{
static bool _test = true;
public static bool Test { get { return _test; } }
}
public class Singleton : CrossAppDomainSingleton<Singleton>
{
bool _test = true;
public bool Test { get { return _test; } }
}
class Program
{
static void Main(string[] args)
{
CheckTime(false, 1); // JIT 컴파일 시간을 배제하기 위해.
CheckTime(true, 1000000);
}
private static void CheckTime(bool outputResult, int loopCount)
{
Stopwatch st = new Stopwatch();
st.Start();
int inc = 0;
for (int i = 0; i < loopCount; i++)
{
if (NonSingleton.Test == true)
{
inc++;
}
}
st.Stop();
if (outputResult == true)
{
Console.WriteLine(st.ElapsedMilliseconds);
}
System.Diagnostics.Trace.WriteLine(inc);
st.Start();
inc = 0;
Singleton singleton = Singleton.Instance;
for (int i = 0; i < loopCount; i++)
{
if (singleton.Test == true)
{
inc++;
}
}
st.Stop();
if (outputResult == true)
{
Console.WriteLine(st.ElapsedMilliseconds);
}
System.Diagnostics.Trace.WriteLine(inc);
}
}
Release 빌드 후 테스트 결과는 다음과 같이 나왔습니다.
10,000 번 호출 NonSingleton: 0ms
10,000 번 호출 Singleton: 4ms
100,000 번 호출 NonSingleton: 0ms
100,000 번 호출 Singleton: 45ms
1,000,000 번 호출 NonSingleton: 1ms
1,000,000 번 호출 Singleton: 454ms
10,000,000 번 호출 NonSingleton: 19ms
10,000,000 번 호출 Singleton: 4539ms
거의 200배가 넘게 느린 결과를 볼 수 있는데요. 무척 느리다 싶지만 이런 유의 테스트가 현실로 왔을 때는 재미있게 해석될 수 있습니다. 예를 들어, 1초에 10,000번 호출된다고 가정했을 때 도메인간 호출이 겨우 4ms 더 시간이 걸릴 뿐이므로 그다지 나쁜 결과는 아니라는 것입니다.
** 주의할 점은, 이는 호출 간의 속도 저하일 뿐 마샬링이 복잡해지면 더욱 느려질 수 있다는 사실!!!
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]