성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - CAS를 이용한 Lock 래퍼 클래스</h1> <p> 기왕 lock-free를 통해 CAS(Compare-And-Swap)을 다뤄본 김에,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [부연] Lock-Free 알고리즘은 과연 빠른가? ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1736'>http://www.sysnet.pe.kr/2/0/1736</a> lock-free 방식이 과연 성능에 얼마나 도움이 될까요? ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1458'>http://www.sysnet.pe.kr/2/0/1458</a> </pre> <br /> 간단하게 클래스로 래핑해 볼까요? ^^ 대충 다음과 같은 정도로 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>public class CASLock</span> : IDisposable // .NET IDisposable 처리 정리 // ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/347'>http://www.sysnet.pe.kr/2/0/347</a> { int _lockVariable = 0; bool _disposed = true; <span style='color: blue; font-weight: bold'>public IDisposable Lock()</span> { while (Interlocked.CompareExchange(ref _lockVariable, 1, 0) != 0) { } _disposed = false; return this; } <span style='color: blue; font-weight: bold'>void Free(bool disposing)</span> { _lockVariable = 0; _disposed = true; } public void Dispose() { Free(true); GC.SuppressFinalize(this); } ~CASLock() { #if DEBUG if (false == _disposed) { throw new ApplicationException("CASLock.Dispose() was not called!"); } #endif Free(false); } } </pre> <br /> 사용법은 using을 통해 기존의 lock문과 거의 유사하게 쓸 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CASLock lockA = new CASLock(); <span style='color: blue; font-weight: bold'>using (lockA.Lock())</span> { Console.WriteLine("locked"); } </pre> <br /> 전형적인 dead-lock 상황을 연출해 볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Runtime.InteropServices; using System.Threading; namespace ConsoleApplication2 { class Program { <span style='color: blue; font-weight: bold'> CASLock lockA = new CASLock(); CASLock lockB = new CASLock();</span> static void Main(string[] args) { Program p = new Program(); <span style='color: blue; font-weight: bold'> Thread t1 = new Thread(p.t1); Thread t2 = new Thread(p.t2);</span> t1.Name = "lockAB"; t2.Name = "lockBA"; t1.Start(); t2.Start(); <span style='color: blue; font-weight: bold'>t1.Join(); // t1 스레드는 절대로 종료하지 않으므로 Join문은 반환하지 않음.</span> t2.Join(); } // Thread 1 void t1() { using (lockA.Lock()) { Thread.Sleep(2000); <span style='color: blue; font-weight: bold'>using (lockB.Lock()) // Thread2의 t2메서드에서 이미 lock을 소유하고 있으므로 block</span> { Console.WriteLine("lockA -> lockB"); } } } // Thread 2 void t2() { using (lockB.Lock()) { Thread.Sleep(2000); <span style='color: blue; font-weight: bold'>using (lockA.Lock()) // Thread1의 t1메서드에서 이미 lock을 소유하고 있으므로 block</span> { Console.WriteLine("lockB -> lockA"); } } } } } </pre> <br /> 여기서, Thread.Abort 메서드로 특정 스레드를 강제 종료하는 것으로 우리가 만든 CASLock 클래스의 안정성 테스트를 해보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { Program p = new Program(); Thread t1 = new Thread(p.t1); Thread t2 = new Thread(p.t2); t1.Name = "lockAB"; t2.Name = "lockBA"; t1.Start(); t2.Start(); int retryCount = 5; while (retryCount-- > 0) { Console.Write("."); Thread.Sleep(1000); } <span style='color: blue; font-weight: bold'>t1.Abort(); // Thread1 강제 종료</span> t1.Join(); t2.Join(); } </pre> <br /> 테스트 코드를 실행시켜 보면 5초 후에 t1.Abort가 호출되고 이는 해당 스레드에 ThreadAbortException 예외를 발생시킵니다. 따라서 t1 메서드 내부의 using 문에서 IDisposable.Dispose 메서드가 실행되고 이로 인해 lockA의 잠금이 해제됩니다.<br /> <br /> 그런데, Native Win32 API인 TerminateThread를 호출하면 어떻게 될까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Runtime.InteropServices; using System.Threading; namespace ConsoleApplication2 { class Program { <span style='color: blue; font-weight: bold'> [DllImport("kernel32.dll")] static extern bool TerminateThread(IntPtr hThread, uint dwExitCode);</span> [Flags] public enum ThreadAccess : int { TERMINATE = (0x0001), } [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, int dwThreadId); static IntPtr _threadAHandle = IntPtr.Zero; static void Main(string[] args) { Program p = new Program(); Thread t1 = new Thread(p.t1); Thread t2 = new Thread(p.t2); t1.Name = "lockAB"; t2.Name = "lockBA"; t1.Start(); t2.Start(); int retryCount = 5; while (retryCount-- > 0) { Console.Write("."); Thread.Sleep(1000); } Console.WriteLine("Terminating...: " + Process.GetCurrentProcess().Threads.Count); <span style='color: blue; font-weight: bold'>TerminateThread(_threadAHandle, 100);</span> Console.WriteLine("Terminated.: " + Process.GetCurrentProcess().Threads.Count); t1.Join(); t2.Join(); } CASLock lockA = new CASLock(); CASLock lockB = new CASLock(); // Thread 1 void t1() { <span style='color: blue; font-weight: bold'>_threadAHandle = OpenThread(ThreadAccess.TERMINATE, false, AppDomain.GetCurrentThreadId());</span> using (lockA.Lock()) { Thread.Sleep(2000); using (lockB.Lock()) { Console.WriteLine("lockA -> lockB"); } } } // Thread 2 void t2() { using (lockB.Lock()) { Thread.Sleep(2000); using (lockA.Lock()) { Console.WriteLine("lockB -> lockA"); } } } } } </pre> <br /> 이런 경우, Thread1이 종료되었음에도 불구하고 lock은 여전히 잠김상태로 남게 됩니다. (기존의 Monitor.Enter/Exit를 해도 동일하게 문제가 나타납니다.) 이는 LockFree 클래스의 안정성에 문제가 있는 것은 아니고 TerminateThread API는 때로 이렇게 위험한 상황을 연출하기 때문에 문서에는 다음과 같이 주의 사항이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > TerminateThread function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminatethread'>https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminatethread</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>TerminateThread is a dangerous function</span> that should only be used in the most extreme cases. You should call TerminateThread only if you know exactly what the target thread is doing, and you control all of the code that the target thread could possibly be running at the time of the termination. For example, TerminateThread can result in the following problems: <ul> <li>If the target thread owns a critical section, <span style='color: blue; font-weight: bold'>the critical section will not be released.</span></li> <li>If the target thread is allocating memory from the heap, <span style='color: blue; font-weight: bold'>the heap lock will not be released.</span></li> <li>If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread's process could be inconsistent.</li> <li>If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL.</li> </ul> <span style='color: blue; font-weight: bold'>A thread cannot protect itself against TerminateThread</span>, ... </pre> <br /> 보시는 것처럼, .NET 세계에서뿐만 아니라 Win32 SDK 세계에서도 TerminateThread API를 잘못 사용하면 저렇게 많은 문제를 발생할 수 있습니다. ^^ 한마디로, 모든 상황을 잘 이해하고 있지 않은 상황에서는 절대 써서는 안될 API입니다.<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=875&boardid=331301885'>첨부한 프로젝트는 위의 예제를 포함</a>하고 있습니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1531
(왼쪽의 숫자를 입력해야 합니다.)