성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
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'>ReaderWriterLockSlim은 언제 쓰는 걸까요?</h1> <p> <br /> 지난번 글에서 닷넷에서의 동기화 처리 방식에 대해서 설명을 했었지요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET 참조 개체 인스턴스의 Object Header를 확인하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1175'>http://www.sysnet.pe.kr/2/0/1175</a> </pre> <br /> 일반적으로 C#에서는 다음과 같이 동기화를 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > object lockInstance = new object(); lock (lockInstance) { ... [공유자원 접근] ... } </pre> <br /> lock (...) 메커니즘은 배타적인 잠금(exclusive lock)을 걸어서 해당 자원을 보호하게 됩니다. 그런데, 가끔은 해당 자원에 대해서 거의 모든 쓰레드들이 '읽기'만 하고 가끔씩 '소수의 스레드'만 '쓰기' 작업을 한다고 했을 때 ex-lock은 왠지 아깝지 않은가 하는 생각이 들게 됩니다.<br /> <br /> 사실, lock이 필요한 이유는 상태를 변경시키는 '쓰기' 때문에 발생하는 것이지, '읽기' 만 발생하는 상황이라면 굳이 lock이 필요하지는 않습니다.<br /> <br /> 따라서 성능을 고려한다면, '읽기' 작업을 하는 스레드들 사이에서는 서로 방해를 하지 않고 있다가 '쓰기' 작업과의 연동에서만 '배타적 잠금'을 하는 것이 바람직합니다. 이런 경우가 바로 공유 잠금(shared lock)이라고 불리는 유형입니다.<br /> <br /> 어디... sh-lock이 ex-lock에 비해서 얼마나 성능이 향상되는지 한번 테스트를 해볼까요?<br /> <br /> 우선, 간단하게 카운트 루프를 도는 프로그램을 lock으로 보호해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void lockThread(object state) { int counterIndex = (int)state; while (threadStop == false) { lock (lockCount) { counts[counterIndex]++; } } } </pre> <br /> 위와 같은 역할을 하는 스레드를 5개 만들어서 실행하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > for (int i = 0; i < count; i++) { Thread newThread = new Thread(lockThread); newThread.IsBackground = true; newThread.Start(i); } </pre> <br /> counts[0...4] 배열의 카운트 값들이 스레드 내의 while 루프를 돌 때 마다 증가할 것입니다. 시간을 재서 초당 증가하는 횟수를 살펴 보니 다음과 같이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 11904819.7152496 11949052.7475766 11931037.5872447 11933239.6103584 11881933.1463757 ==> 초당 약 12,000,000번의 실행 </pre> <br /> 그렇다면, 이제 스레드 함수에 <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/api/system.threading.readerwriterlockslim'>ReaderWriterLockSlim</a>을 사용해서 공유 잠금을 해보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Slim Reader/Writer (SRW) Locks ; <a target='tab' href='https://docs.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks'>https://docs.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks</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'>ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();</span> void readerThread(object state) { int countIndex = (int)state; while (threadStop == false) { <span style='color: blue; font-weight: bold'>cacheLock.EnterReadLock();</span> try { counts[countIndex]++; } finally { <span style='color: blue; font-weight: bold'>cacheLock.ExitReadLock();</span> } } } void writerThread(object state) { int countIndex = (int)state; while (threadStop == false) { <span style='color: blue; font-weight: bold'>cacheLock.EnterWriteLock();</span> try { counts[countIndex]++; } finally { <span style='color: blue; font-weight: bold'>cacheLock.ExitWriteLock();</span> } } } </pre> <br /> 5개의 스레드 생성 시에, 첫 번째 쓰레드에 대해서만 writerThread를 할당하고 나머지는 readerThread로 할당해 주었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > for (int i = 0; i < count; i++) { Thread newThread; if (i == 0) { newThread = new Thread(writerThread); } else { newThread = new Thread(readerThread); } newThread.IsBackground = true; newThread.Start(i); } </pre> <br /> 자... 기대되는 군요. ^^ 이렇게 하고 실행하면 ex-lock에 비해서 얼마나 성능 향상이 있을까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 5439175.07101596 5398816.34136504 5390411.53317197 5366829.75513943 5351321.89442379 5344019.70797552 5334268.38922098 ==> 초당 약 5,500,000번의 실행 </pre> <br /> 오호~~~ 예상 외인가요? 무조건 잠금을 하는 ex-lock이 12,000,000번의 성능을 보였던 것에 비하면 절반도 안되는 수치를 기록하고 있습니다. 혹시... 테스트를 뭔가 잘못한 걸까요?<br /> <br /> 아닙니다. 테스트는 정확하게 했습니다. 위와 같은 상황에서는 가벼운 ex-lock이 더 좋은 성능을 보일 수밖에 없습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그렇다면, lock을 점유하는 시간이 다소 길어지면 어떤 현상이 발생할까요? 이를 위해 lock 점유를 흉내내기 위해 다음과 같이 1ms에 해당하는 Thread.Sleep 시간을 스레드에 추가해 봤습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void lockThread(object state) { int counterIndex = (int)state; while (threadStop == false) { lock (lockCount) { counts[counterIndex]++; <span style='color: blue; font-weight: bold'>Thread.Sleep(1);</span> } } } </pre> <br /> 겨우 1ms의 지연시간을 추가한 것 뿐인데, 초당 루프 증가 수는 다음과 같이 뚝 떨어졌습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 998.700954608068 998.745307936315 998.820608186942 998.825143635559 998.893010223893 ==> 초당 약 1,000번의 실행 </pre> <br /> 그럼 sh-lock의 경우에는 어떨까요? 이번엔 정말 기대되는 군요. ^^ 역시 동일하게 1ms의 지연 시간을 부여하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void readerThread(object state) { int countIndex = (int)state; while (threadStop == false) { cacheLock.EnterReadLock(); try { counts[countIndex]++; <span style='color: blue; font-weight: bold'>Thread.Sleep(1);</span> } finally { cacheLock.ExitReadLock(); } } } void writerThread(object state) { int countIndex = (int)state; while (threadStop == false) { cacheLock.EnterWriteLock(); try { counts[countIndex]++; <span style='color: blue; font-weight: bold'>Thread.Sleep(1);</span> } finally { cacheLock.ExitWriteLock(); } } } </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;' > 1957.41883217647 1900.581814786 1912.05442748045 1890.14059164164 1908.65293256279 ==> 초당 약 1,900번의 실행 </pre> <br /> 상황이 역전되었군요. 기대했던 데로 이번엔 ex-lock의 초당 약 1,000번도 안되는 성능에 비하면 2배 정도의 향상된 수치를 보여주고 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 쯤 되면, 이번 글의 제목에 썼던 내용에 대해서 어느 정도 답을 하실 수 있을 것입니다.<br /> <br /> ReaderWriterLockSlim 클래스는, 다중 읽기/소수 쓰기의 상황에서 보호되어야 할 자원이 있는 경우에 사용할 수 있는데, 그 효과를 발휘하려면 잠금 구간 사이의 체류 시간이 있어야 합니다.<br /> <br /> 예를 들어, 다중 스레드로부터 보호해야 하는 영역에 변수 1~2개 정도 두고 하는 거라면 Interlocked.Increment / Interlocked.Decrement과 같은 것을 사용하거나 아예 가벼운 lock (...) 구문을 사용하시는 것이 성능을 위해 더욱 현명한 선택입니다.<br /> <br /> 허긴... 이렇게 머리로는 알고 있어도, ^^ 대부분의 성능 관련한 것들이 직접 측정을 하지 않으면 정확한 판단을 내릴 수 없는 경우가 많다는 것이... 문제라면 문제겠지요. ^^<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=652&boardid=331301885'>첨부된 파일은 위의 코드를 포함한 예제 프로젝트</a>입니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1011
(왼쪽의 숫자를 입력해야 합니다.)