SimpleIsBest.NET

유경상의 닷넷 블로그

퀴즈: 동기화 버그를 찾아라!

by 블로그쥔장 | 작성일자: 2006-09-21 오후 2:06:00
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.

여러 책들이나 문서들을 읽어 보면 쓰레드를 사용하면 동시성(concurrency)이 증가되지만 복잡도 역시 증가되어 코드가 어려워지고 버그를 찾기 어려워진다고들 한다. 제군들 역시 쓰레드는 좀 다뤄 보았을 것이다. 정말 쓰레드가 어렵게 느껴진 적이 얼마나 되는가?

심심한 필자, 쓰레딩(threading)과 동기화(synchronization)에 대해 재미있는(?) 퀴즈를 하나 내보려고 한다. 본인이 한 코딩 한다는 사람은 이 퀴즈에 도전(?)해 보기 바란다.

Quiz: Synchronization Bug

다 필요 없고 먼저 코드부터 까 보자. 우리 코드 쟁의들한테는 백마디 설명보다 한 두 줄의 코드가 의사 소통하는데 좋지 아니한가?

    1     // 키/값을 검색해주는 간단한 예제 클래스

    2     public static class LookupTableBug

    3     {

    4         // 내부 테이블(해시테이블 이용)

    5         private static readonly Hashtable _Table = new Hashtable();

    6 

    7         // 주어진 키에 대응되는 값을 찾아 반환한다.

    8         public static object Lookup(object key)

    9         {

   10             object val = _Table[key];

   11             if (val == null) {

   12                 lock (_Table.SyncRoot) {

   13                     val = PublishValue(key);

   14                     _Table[key] = val;

   15                 }

   16             }

   17             return val;

   18         }

   19 

   20         // 주어진 키에 대한 값을 DB, 파일, 웹 서비스 호출 등에서

   21         // 읽어 테이블에 추가한다.

   22         private static object PublishValue(object key)

   23         {

   24             return "Data";

   25         }

   26     }

리스트1. 키-값 검색을 하는 룩업(lookup) 테이블 클래스

위 코드의 LookupTable 클래스는 실무에서 종종 사용되는 클래스 패턴이다. 어떤 키 값에 의해 검색되는 데이터를 찾기 쉽게 해주는 클래스로서 데이터가 DB나 파일 혹은 원격 컴퓨터에서 웹 서비스 등의 방법을 통해 읽어 들어야 할 때 유용하다. 한 방에 미리 테이블을 구축할 수도 있지만 캐시 개념을 적용하여 필요할 때에만 데이터를 읽고 이것을 캐시 해 두는 기법 역시 많이 사용되곤 한다.

어찌 되었건 리스트1 코드를 살펴보면 동시에 여러 쓰레드가 Lookup 메쏘드를 호출하는 것에 대비하여 lock 키워드를 사용하여 나름대로 동기화도 수행했다. 오오... 또 한 가지 리스트1에서 신경 쓴 흔적은 성능을 위해 Lookup 메쏘드 전체에 대해 잠금을 수행하지 않았다는 점이다. 만약 동기화 문제를 피하려고 lock 키워드를  10 라인부터 16 라인까지 묶어 버리면 동기화 문제는 없겠지만 대신 테이블을 검색하는 쓰레드는 오직 하나 밖에 될 수 없고 이는 심각(?)한 동시성(concurrency) 문제에 부딪칠 수도 있다. 때문에... 테이블을 읽는 시점(10 라인)에서는 동기화가 필요하지 않았고 해당 데이터가 없는 시점(11 라인)에서 테이블에 데이터를 추가할 때 2개 이상의 쓰레드가 PublishValue를 호출하지 않도록 잠금(lock)을 사용한 것이다.

리스트1 코드는 심각하지 않지만 하나의 버그를 가지고 있다. 이 버그는 경우에 따라 심각할 수도 있지만 리스트1 의 경우라면 그다지 심각하지 않을 수도 있는 버그이다. 오늘의 퀴즈가 요것이 되겠다. (애들은 가라~~) 다양한 쓰레드 동기화 문제가 있지만, 이런 패턴은 상당히 자주 등장하는 패턴이므로 알아두면 좋겠다는 생각에 퀴즈를 내 본 것이다.

뭐 이젠 필자가 별 짓을 다한다고 생각할 지도 모르겠다. 걍 심심해서 몇 자 적어 본 것이니 버그에 대해 알고 있는 독자는 피드백을 달기 바란다. 물론 필자가 아는 버그 외에 다른 버그가 존재할 수도 있겠다. 그런 것도 좋으니 몇 마디 남겨보기 바란다.

필자가 제시하는 정답은 다음 글에 올리도록 하겠다. 텨텨텨



Comments (read-only)
#re: 퀴즈: 동기화 버그를 찾아라! / 나두애들인데(Taeyo) / 2006-09-21 오후 3:42:00
필자가 생각하고 있는 정답을 맞힐 경우, 상품은?
ps : 쟁의가 아니라 쟁이 아닙니꺄~?
#re: 퀴즈: 동기화 버그를 찾아라! / 블로그쥔장 / 2006-09-21 오후 3:57:00
쟁이 맞습니다... 맞고요... (대개 보면 꼭 공부 못하는 사람들이 이런거 잘 따집니다... -_-)
특별히 상품은... 없습니다... -_-;
혹시나 오프라인에서 보면 심심한 감사와 더불어 쓰디쓴 커피라도 한사발 사드릴지도...
퀴즈를 맞히신 분께서 미모의 여성분이라면 이야기가 좀 달라지겠지요? (근사한 저녁?)
#re: 퀴즈: 동기화 버그를 찾아라! / 위시 / 2006-09-22 오전 11:30:00
...일단 출석체크;;;;; (-_-;;;;;; )한후 읽어보는 상큼함..^^


#re: 퀴즈: 동기화 버그를 찾아라! / spponge / 2006-09-22 오후 1:03:00
일단
1.
private static readonly Hashtable _Table = new Hashtable();
=> private static Hashtable _Table = new Hashtable();
readonly이므로 값 할당이 안되겠죠?

2. 11번과 12번 라인 사이에 다른 스레드에서 동일 키에 값을 할당 할 수 있다는거~ 아닌가 모르겠네요.
여튼 키에 Value가 널인지 판단 후 해시테이블에 락을 걸기 까지 다른 스레드가 테이블에 액세스 할 수 있다는 점이 버그일거 같습니다.
근데 상품 안줘요?
#re: 퀴즈: 동기화 버그를 찾아라! / 블로그쥔장 / 2006-09-22 오후 1:40:00
퀴즈로 나간 리스트1 코드는 컴파일 오류는 없습니다.
그리고 런타임에도 exception이 발생하거나 하지 않습니다.

상품은... 없습니다... -_-; 텨텨텨~~~~
#re: 퀴즈: 동기화 버그를 찾아라! / 시즈하 / 2006-09-22 오후 1:48:00
C#은 대충 훑어본 정도입니다만...

만일 var == null 이라면,
_Table[key] = PublishValue(key); 를 통해서 키에 대응되는 값을 새로 집어넣기는 하지만,
return 되는 쪽은 그냥 null이 될것 같습니다.

>> spponge 님
readonly라고 해도 처음 할당된 인스턴스를 바꾸지 못하는 것이지, 객체 내부의 값은 바꿀수 있겠죠.
#re: 퀴즈: 동기화 버그를 찾아라! / 시즈하 / 2006-09-22 오후 1:52:00
spponge 님 말씀처럼,

if (var == null)을 수행하기 이전에 _Table[key]에대한 동시 접근이 가능하다는 점이 걸립니다.
확실히 이 소스에서는 그다지 문제는 없어 보입니다만,
_Table[key] = null 일때, 동시에 lockup()이 호출되면, 한쪽은 null이 반환되고 다른 한쪽은 _Table[key] = PublishValue(key); 로 할당된 값이 val로 넘겨져서 반환될 수 있지 않을까요.
#re: 퀴즈: 동기화 버그를 찾아라! / 블로그쥔장 / 2006-09-22 오후 2:08:00
크억.. 문제가 잘못 나갔네요... -_-;
문제가 수정되었습니다. 오류는 PublishValue를 호출한 결과를 val 변수에 담고
이 val의 값을 해시 테이블에 추가했어야 했는데...

변경전:
lock (_Table.SyncRoot) {
_Table[key] = PublishValue(key);
}

변경후:
lock (_Table.SyncRoot) {
val = PublishValue(key);
_Table[key] = val;
}

물론 변경 후에도 버그는 여전히 존재 합니다.... ^^
비슷하게 맞추신 분도 벌써 있는데요... ^^
#re: 퀴즈: 동기화 버그를 찾아라! / 블로그쥔장 / 2006-09-22 오후 2:32:00
시즈하님께서 지적해 주셔서 저도 코드에 문제가 있다는 것을 알았습니다.
감사합니다. ^^
하지만 제가 문제로서 의도했던 것은 그것이 아니라는...
시즈하님 어쨌건 감사합니다...
#re: 퀴즈: 동기화 버그를 찾아라! / 뜸부기 / 2006-09-22 오후 2:44:00
혹시 닷넷 1.1이 갖고 있는 멀티프로세서 하에서 동작되는 멀티쓰레딩의 버그인가요??

#re: 퀴즈: 동기화 버그를 찾아라! / 블로그쥔장 / 2006-09-22 오후 3:05:00
그런 버그가 아니라 프로그래밍 상의 버그 입니다. ^^
#re: 퀴즈: 동기화 버그를 찾아라! / 아웅 / 2006-09-22 오후 4:00:00
spponge님 말씀대로.. 여러쓰레드가 동시에 동일키로 접근하고 val이 null일 경우
맨 마지막에 동기화 구문을 수행한 스레드의 값이 key에 저장되는 문제 같네요..
각각의 스레드야 val 변수를 리턴 받으므로 이상이 없는 것 처럼 보이지만.. 이후에 다시 호출될때
이전과 다른 값이 나오겠죠.. 예제 코드는 다 동일한 값이 저장되므로 문제가 심각하지 않을 수도 있지만요..