애플리케이션이 구동되면서 화면 가득 정보를 채우고자 할 경우 백그라운드로 다수의 쓰레드를 만들어 서버와 통신하거나 연산 작업을 수행하는 일을 하게 되며, 일이 끝난 경우 이를 화면에 업데이트하는 것이 필요하다. 이 경우에 알아 두어야 할 것이 바로 UI 쓰레딩 모델이다. 대부분의 윈도우용 애플리케이션에서 UI 부분을 업데이트하는 쓰레드는 하나이며, 이는 WPF, Silverlight도 예외가 아니다.
한 애플리케이션 내에서 다수의 백그라운드 쓰레드를 생성하여 사용할 수 있으나, UI를 업데이트하는 쓰레드는 해당 UI 컴포넌트를 만들어낸 UI 쓰레드 하나다. 따라서 많은 쓰레드가 생성되어 백그라운드로 동시에 수행되어 업무 효율을 높일 수 있으나, 그 결과물을 화면에 반영하기 위해서는 UI 쓰레드에게 그 작업을 일임해야 한다. 백그라운드 쓰레드가 Dispatcher를 통해 UI 쓰레드에 작업을 할당하는 방법은 동기식으로는 Dispatcher.Invoke() 혹은 비동기 방식으로는 Dispatcher.BeginInvoke()를 통해서이다.

 
이러한 메소드의 호출을 통해서 전달된 작업들이 Dispatcher가 관리하는 큐에 쌓여 우선순위 (DispatcherPriority)에 근거하여 자신이 할당되어 있는 (즉, 연결된) 쓰레드를 통해 순서대로 실행하게 된다. 또한 Dispatcher는 자신이 연결되어 있는 쓰레드를 레퍼런스로 가지고 있으며, DispatherObject는 이러한 Dispatcher에 대한 레퍼런스를 가지고 있다. 이를 도식화하면, 아래 그림과 같다.

각각은 Association 관계로 엮여 있으며 DispacherObject를 통해 할당되어 있는 쓰레드에 이르기까지 접근이 가능하다. 맨 처음 코드에서 Button에 대해서 Dispatcher를 호출한 후 BeginInvoke()를 호출하여 작업을 일임하는 코드가 가능한 이유는, 아래 그림에서 보듯이 모든 UI 요소들이 바로 이 DispacherObject를 상속받아 만들어졌기 때문에, 상속에 의해 Dispatcher를 속성으로 가지게 되었기 때문이다.

쓰레드가 작업을 진행하면서 UI 요소를 업데이트하려할 때, 과연 이 쓰레드가 해당 UI에 접근 권한이 있는지를 점검하는 로직이 DispatcherObject에 CheckAccess()와 VerifyAccess()를 통해 제공되고 있다. DispatherObject (즉, UI 요소) 가 생성될 때 넘겨 받은 Dispatcher 객체와 접근하고 있는 쓰레드가 엮여 있는 Dispatcher 객체를 비교함으로써 쓰레드가 접근하고자 하는 DispatherObject (즉, UI 요소)에 업데이트 권한이 있는지를 검사하는 것이다.

Posted by 장현춘

댓글을 달아 주세요

같은 인스턴스에 접근하는 모든 쓰레드는 해당 클래스의 멤버 변수로 선언된 값을 공유하기 때문에 멀티 쓰레드 애플리케이션 작성시 항상 공유 자원 보호 문제가 대두되기 마련이다. 즉 다수의 쓰레드가 동시에 한 인스턴스를 접근할 경우 해당 인스턴스의 멤버 변수에 저장되어 있는 값을 어떻게 thread-safe하게 관리할 것인가하는 문제가 발생하게 된다. 닷넷에서는 다양한 방법을 통해 동기화 문제를 유발할 수 있는 코드에의 접근을 serialize시킨다. 즉, 한번에 한 쓰레드(mutual exclusion)만이 해당 코드 블럭을 수행할 수 있게 바꾸는 방식으로 이 문제에 대한 답을 제공하고 있다.

1. Interlocked 클래스와 Monitor 클래스
한 프로세스내의 다수의 쓰레드 사이의 동기화 문제를 해결하기 위해 닷넷 이전부터 제공되어 오던 Win32 API를 닷넷버전으로 매핑한 경우이다. Interlocked 클래스는 Win32 API에서 제공하는 많은 메소드를 묶어 static 메소드로 노출하는 닷넷 클래스이다. Interlocked.Increment, Interlocked.Decrement, Interlocked.Exchange 등의 메소드를 가지고 있다. Monitor 클래스는 Win32 API의 CRITICAL_SECTION 구조체를 다루는 메소드를 닷넷 버전으로 모아 놓은 것이다. Win32 API의 EnterCriticalSection은 Monitor.Enter()로, LeaveCriticalSection은 Monitor.Exit()으로 형상화되었다.
Interlocked 클래스와 Monitor 클래스 중에 Interlocked를 쓰는 것이 성능상의 잇점이 있다. 즉 좀 더 가볍다.
2. lock 키워드
가장 일반적이면서 쉽게 쓰레드 동기화 문제를 해결할 수 있는 키워드가 lock이다. 공유자원에 대한 접근 중에 동기화 문제를 유발할 수 있는 블럭을 선택하여 lock을 통해 여러 쓰레드의 동시 접근을 막고 한번에 하나씩 수행을 하도록 하는 기능, 즉 mutual exclusion을 제공한다. lock은  컴파일 시점에 위에서 언급한 Monitor.Enter()와 Monitor.Exit()을 통해 코드가 재 해석되어 변경된다. 즉, lock()은 Monitor 클래스를 통해 구현된다.
private Object theLock = new Object();
lock (theLock) {
.....
}
3. ReaderWriterLock / ReaderWriterLockSlim
Read를 위한 쓰레드인지, Write를 위한 쓰레드인지를 구분하여 다수의 쓰레드가 Read만을 원할 경우 동시에 데이터에 접근하도록 허용하되, Write를 원하는 쓰레드가 작업중이면 모든 Read를 원하는 쓰레드는 대기상태에 들어가게 되는 원리이다. Reader를 위한 대기큐와 Writer를 위한 대기큐를 두어 좀 더 효과적인 리소스 활용이 가능하다. ReaderWriterLock클래스가 성능상의 문제가 있어서 .NET Framework 3.5부터 ReaderWriterLockSlim 클래스를 별도로 제공하는데, 가급적 언제나 새로 제공되는  ReaderWriterLockSlim을 쓰도록 한다.
ReaderWriterLockSlim theLock = new ReaderWriterLockSlim();
theLock.EnterReadLock() / theLock.ExitReadLock()
theLock.EnterWriteLock() / theLock.ExitWriteLock() 등의 메소드를 이용한다.
4. Mutex
앞에 열거한 기법들이 하나의 프로세스내의 다수 쓰레드 사이의 공유 자원 동기화를 위한 장치들이라면, Mutex는 이들과는 약간 다르게 여러 프로세스들 사이의 공유 자원 접근 문제를 해결하기 위한 장치이다.
5. Semaphore
Semaphore는 앞의 기법들이 한 프로세스 내의 쓰레드 동기화이거나 혹은 여러 프로세스 내의 쓰레드 동기화건 mutual exclusion 기반의 동기화 장치인데 반해 Semaphore는 동기화 관련된 코드 블럭에 접근 가능한 쓰레드의 수를 일정하게 유지하는 기법이다. 즉, 여러 쓰레드가 동시에 접근 가능하다. 아래는 최대 5개의 쓰레드가 동시 접근 가능한 semaphore를 설정하는 것이다.
private Semaphore semaphore = new Semaphore(0, 5);
semaphore.WaitOne();
try {
....
} finally {
  semaphore.Release();
}

lock(), Monitor, Interlocked 등이 사용자 모드에서 쓰레드 동기화를 처리하는 것인데 반해, Mutex와 Semaphore는 OS레벨의 커널 모드 객체들을 통해 구현되므로 훨씬 무겁기 때문에 가능하다면 언제나 좀더 가벼운 기능을 사용하도록 하는 것이 필요하다.

Posted by 장현춘

댓글을 달아 주세요

  1. Favicon of http://rhea.pe.kr/ BlogIcon Rhea君 2008.08.21 00:23  댓글주소  수정/삭제  댓글쓰기

    안녕하세요?
    공부를 하다보니 Event는 커널개체이기 때문에 가장 빠르다(혹은 가볍다) or Critical Section은 단순한 알고리즘 기반으이므로 가장 빠르다(혹은 가볍다)라고 상반된 내용들을 접합니다.
    물론 전체 수행에서 각기 다르겠지만 단순히 둘만 놓고 본다면 어느 것이 정답일까요?;; 궁금합니다. ^^ (물론 제 질문 자체가 잘못되었을수도 있다고도 생각합니다.)

  2. 장현춘 2008.08.21 10:08  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 갑자기, 빛처럼 빠른 100 메가급..하는 광고가 생각나는군요.. ^^
    말씀하신 Event와 Critical Section에 관한 사항이 여기와 좀 다른 맥락에서 보셨을 것 같네요. C#과 같은 managed 환경에서 커널객체 기반의 Event를 쓰면 사용자 모드에서 커널모드로의 transition이 일어나 느리겠지만, unmanged 환경에서 커널 객체를 직접 핸들링하는 차원이라면 다른 얘기가 아닐까 합니다.
    또한 위에서 정리한 것은 같은 프로세스내 혹은 다른 프로세스의 쓰레드 동기화 매커니즘이며, Event는 이중에서 프로세스 사이의 동기화에 필요한 이벤트 발생에 관여하는 것으로 생각됩니다.
    말씀하신 unmanaged 환경에서 단순한 두 녀석의 속도 비교는 .. 잘 모르겠습니다. 지나가다 아시는 분이 답변을 달아주시길...

    대학때 좀 공부좀 할껄 하는 생각이 마구 듭니다. --;

대부분의 경우에, 언어나 플랫폼에 상관없이 쓰레드의 동작 방식을 이해하고 이를 활용하여 멀티쓰레드 애플리케이션을 구현할 수 있을 때 그 사람을 중급 이상의 개발자로 보게된다. 닷넷 혹은 자바와 같이 managed 환경하의 개발 방식을 지원하는 경우 쓰레드를 생성하여 활용하는 것이 생각보다 쉽다. 다만 어디까지 활용할 것인가는 개발자 선택의 몫이다. 쓰레드를 논할 때 간과하는 것이 소위 말하는 green thread vs. native thread이다. OS 레벨에서 나름대로의 방식으로 생성하여 관리하는 native thread와, managed 환경 즉, CLR이나 JVM 레벨에서 제공하는 green thread가 있다. 당연하게도 green thread가 결국은 OS 레벨에서 process로 매핑이 되건 native thread로 매핑이 되어 실행된다. 하지만, CLR의 green thread와 OS의 native thread가 일대일 매핑될 것이라는 가정은 금물이다. 하나의 OS native thread가 여러 애플리케이션의 여러 green thread를 동시에 처리할 수도 있기 때문이다. (닷넷은 덜 하겠지만, 자바의 경우 한때는 Windows, Unix, Linux에서 green thread가 다르게 표현되기도 했다. Windows에서는 native thread와 매핑되고, Linux에서는 한 때 process로 매핑되어 애플리케이션에서 쓰레드 생성마다 java process가 지속적으로 늘어나기도 했고, Solaris의 경우 lightweight process내의 user thread에 매핑되기도 했다.) 어쨌거나 디버깅 필요에 의해서 native thread 레벨까지 뒤져보는 것은 필요하나 일반적으로 green thread와 native thread 사이의 연관 관계에 대해서 일체의 가정을 두고 애플리케이션을 작성하는 것은 위험하다. 왜냐하면 CLR의 green thread  관련된 구현이 바뀔 경우, 애플리케이션이 기반한 가정이 치명상을 입을 수 있기 때문이다.

쓰레드 생성
쓰레드의 생성은 sealed 클래스라서 상속이 불가능한 System.Thread 클래스와 ThreadStart delegate을 이용한다.
new Thread(new ThreadStart(name_of_method)).Start();  // C# with delegate
new Thread(o => name_of_method(x));   // C# with lambda operator
ThreadStart delegate의 signature를 보면 public delegate void ThreadStart()로 되어 있어서 리턴값과 파라메터가 없는 메소드를 받을 수 있다. 이는 흡사 자바의 Runnable 인터페이스에서 public void run()을 구현하게 하는 것과 유사하다.
new Thread(new Runnable{ public void run() { ..... }} ).start();   // java.. 자바에는 delegate도, anonymous method도 없어서 anonymous class를 이용해야 한다.
쓰레드가 필요시마다 위에서 처럼 생성하게 되면 오버헤드가 발생하므로 대부분의 경우 System.Threading.ThreadPool 클래스의 static 메소드인 QueueUserWorkItem()을 이용한다.
ThreadPool.QueueUserWorkItem(o => name_of_method (x));   // C# with lambda operator

쓰레드 라이프싸이클
쓰레드는 다음과 같은 life cycle을 가진다.
 
주의할 점은, 한번 일을 끝낸 쓰레드 (그림에서 Finished 혹은 Aborted 상태)는 다시 활성화되지 않는다. Running 상태에 있는 쓰레드는 그림에서 보듯이 현 쓰레드를 잠시 쉬게 하는 Thread.Sleep(), 다른 쓰레드가 끝날 때까지 기다리겠다는 otherThread.Join(), 동기화 필요에 의한 Monitor.Wait() 등에 의해 WaitSleepJoin 상태로 가게되며 이 상태에서는 interruption을 받을 수 있다. 즉, 쉬는 상태에서 interruption을 받아 ThreadInterruptionException을 발생시키고 이를 처리하면서 Running 상태로 복귀할 수 있다. 또한 Thread.Abort()을 이용하여 쓰레드를 종료시킬 수 있는데, 이 메소드를 받은 쓰레드는 그림에서 처럼 ThreadAbortException을 발생시키며 AbortRequested 상태에 빠지게 된다. 재밌는 것은 이 상태에서 ResetAbort()를 이용하여 Abort() 명령을 무력화시키고 다시 Running 상태로 갈 수도 있다는 것이다.

Foreground thread vs. Background thread
쓰레드에는 foreground thread와 background thread가 있다. 이 둘은 모든 면에서 동일하나, 다른 점은 한 프로세스상의 모든 foreground thread가 완료되면 해당 프로세스는 종료되는 반면, background thread는 이런 기능이 없어서 foreground thread가 완료되어 프로세스가 끝나면 background thread는 실행 도중에 종료된다. 따라서 background thread가 데이터베이스나 네트웍 연결을 담당하고 있다면 도중 종료로 인해 해당 자원이 불안정한 상태에 빠질 수 있다. C#에서 Main() 쓰레드는 해당 프로세스 내의 하나의 쓰레드일 뿐이라서 Main() 쓰레드가 종료되었지만 다른 foreground thread가 살아 있다면 해당 프로세스는 다른 모든 foreground thread가 종료될 때까지 종료되지 않는다. 물론 다른 쓰레드가 모두 background thread라면 Main() 쓰레드 종료와 더불어 프로세스가 종료된다. background thread는 Thread.IsBackground 라는 property를 true로 설정함으로 적용할 수 있다.

Thread별 변수 저장
멀티 쓰레드 애플리케이션을 작성시 가끔 고민하는 부분이, 다수의 쓰레드가 하나의 인스턴스에 접근할 경우 각 쓰레드별 고유한 값을 저장할 수 있는 변수의 필요성을 느끼게 된다. 이 경우 사용할 수 있는 장치가 ThreadStatic attribute이다. System.ThreadStaticAttribute로 정의되어 있으며, 작성하고자 하는 클래스의 static 멤버에 이 속성을 추가함으로써 해당 static 멤버는 접근하는 모든 쓰레드에 대해 고유한 변수를 갖게 된다. static 멤버 변수는 해당 클래스의 모든 인스턴스 사이에 공유되는 값인데 반하여, ThreadStatic static 멤버 변수는 해당 클래스 및 인스턴스에 접근하는 모든 쓰레드에 고유한 값을 저장하는 변수가 되는 것이다.

아래는, ThreadStatic으로 지정된 Hey에 대해, thread1과 thread2는 같은 인스턴스에 대해 메소드를 호출하고, thread3는 클래스 메소드(static method)를 호출할 경우 ThreadStatic 변수인 static string Hey에 저장된 값이 무엇인지를 출력하는 간단한 프로그램이다.

using System;
using System.Threading;

public class ThreadStaticTest
{
    public static void Main(string[] args)
    {
        MyThreadField a = new MyThreadField();
        Thread thread1 = new Thread(new ThreadStart(a.SayHello1));
        thread1.Start();

        Thread thread2 = new Thread(new ThreadStart(a.SayHello1));
        thread2.Start();

        Thread thread3 = new Thread(new ThreadStart(MyThreadField.SayHello2));
        thread3.Start();
    }
}

public class MyThreadField
{
    [ThreadStatic]
    public static string Hey = "babo";
    public void SayHello1()
    {
        for (int i = 0; i < 10; i++)
        {
            Hey = Hey + Thread.CurrentThread.GetHashCode();
            Console.WriteLine("Thread {0} starting... {1}", Thread.CurrentThread.GetHashCode(), Hey);
            Thread.Sleep(100);
        }
    }
    public static void SayHello2()
    {
        for (int i = 0; i < 10; i++)
        {
            Hey = Hey + Thread.CurrentThread.GetHashCode();
            Console.WriteLine("Thread {0} starting... {1}", Thread.CurrentThread.GetHashCode(), Hey);
            Thread.Sleep(100);
        }
    }
}

결과는 예상대로 쓰레드 간에 간섭없이 Hey 값이 쓰레드 별로 해당 쓰레드에 의해서만 변경되고 있다.
Thread 3 starting... 3
Thread 4 starting... 4
Thread 5 starting... 5
Thread 5 starting... 55
Thread 4 starting... 44
Thread 3 starting... 33
Thread 5 starting... 555
Thread 3 starting... 333
Thread 4 starting... 444
Thread 5 starting... 5555
Thread 3 starting... 3333
Thread 4 starting... 4444
Thread 3 starting... 33333
Thread 4 starting... 44444
Thread 5 starting... 55555
Thread 3 starting... 333333
Thread 5 starting... 555555
Thread 4 starting... 444444
Thread 4 starting... 4444444
Thread 5 starting... 5555555
Thread 3 starting... 3333333
Thread 3 starting... 33333333
Thread 4 starting... 44444444
Thread 5 starting... 55555555
Thread 3 starting... 333333333
Thread 5 starting... 555555555
Thread 4 starting... 444444444
Thread 4 starting... 4444444444
Thread 5 starting... 5555555555
Thread 3 starting... 3333333333

ThreadStatic 속성을 제거하면 아래처럼 쓰레드간 간섭에 의해 Hey에 저장되는 값이 불규칙하게 증가하는 것을 알 수 있다.
Thread 3 starting... babo3
Thread 4 starting... babo34
Thread 5 starting... babo345
Thread 3 starting... babo3453
Thread 4 starting... babo34534
Thread 5 starting... babo345345
Thread 4 starting... babo3453454
Thread 3 starting... babo34534543
Thread 5 starting... babo345345435
Thread 4 starting... babo3453454354
Thread 3 starting... babo34534543543
Thread 5 starting... babo345345435435
Thread 4 starting... babo3453454354354
Thread 3 starting... babo34534543543543
Thread 5 starting... babo345345435435435
Thread 4 starting... babo3453454354354354
Thread 3 starting... babo3453454354354354
Thread 5 starting... babo34534543543543545
Thread 4 starting... babo345345435435435454
Thread 3 starting... babo345345435435435453
Thread 5 starting... babo3453454354354354535
Thread 3 starting... babo34534543543543545353
Thread 4 starting... babo345345435435435453534
Thread 5 starting... babo3453454354354354535345
Thread 4 starting... babo34534543543543545353454
Thread 3 starting... babo345345435435435453534543
Thread 5 starting... babo3453454354354354535345435
Thread 4 starting... babo34534543543543545353454354
Thread 3 starting... babo34534543543543545353454353
Thread 5 starting... babo345345435435435453534543535

Posted by 장현춘

댓글을 달아 주세요

자바와 닷넷에서의 쓰레드 모델을 살펴보자

자바의 경우,

쓰레드 생성은...
java.lang.Thead를 상속받아 클래스를 정의하거나 java.lang.Runnable을 구현하는 클래스를 정의하며 클래스내에는 public void run() {} 메소드가 정의되어야 한다. 명시적으로 클래스를 만들지 않고 anonymous class 형태로 쓰레드를 생성할 수 있다.
new Thread() { public void run() { ………… } }.start(); 
혹은
new Thread(new Runnable() { public void run() { ……………} }).start();
로 쓰레드를 생성하고 바로 실행시킨다.

쓰레드 동기화는...
synchronized라는 키워드가 존재하여 쓰레드 간 동기화 작업을 지원한다. 사용방법은 원하는 블록을 지정하여 synchronized로 지정할 수도 있고, 메소드의 modifier로 사용되어 메소드 전체에 대해 flag 방식으로 지정되어 좀 더 효율적인 방법을 제공하고 있다.(j2se 1.4 기준) Lock으로는 객체lock과 클래스lock을 지원하며, 객체lock의 경우 synchronized(this) {} 와 같이 지정하고 이 경우 호출되는 객체가 객체lock으로 지정되며, 원하는 객체를 lock으로 지정할 수도 있다. synchronized(youTheObject) {}와 같이 사용한다.
클래스lock의 경우 synchronized(MyClass.class) {} 와 같이 지정하거나, static 메소드의 modifier로 synchronized가 사용된 경우로서, 이 경우 MyClass.class를 지칭하는 java.lang.Class.class 객체의 인스턴스가 클래스lock으로 지정된다. 클래스를 지칭하는 Class 인스턴스는 JVM내에 유일하게 관리된다.
객체lock과 클래스lock은 동시에 사용가능하다. 즉, 객체lock을 필요로 하는 메소드와 클래스lock을 필요로하는 메소드는 동시에 두 쓰레드로부터 호출될 수 있다. (이점이 닷넷과 다르다 !!!)
j2se 5.0 부터 concurrent programming을 지원하기 위해 java.util.concurrent.* 가 추가되었다. 이는 자바 concurrent 프로그래밍의 대가인 Doug Lea의 작업 산출물이 추가된 것으로 보인다.

닷넷의 경우,

쓰레드 생성은…
쓰레드 풀과 delegate을 써서 이루어지는데, System.Threading.ThreadPool.QueueUserWorkItem 메소드를 통해 이루어지며 파라미터로 원하는 메소드의 delegate을 넘겨줌으로써 쓰레드 방식으로 동작하게 된다. Thread pool manager는 넘겨받은 delegate을 queue에 쌓아놓고 가용한 쓰레드가 있을 경우 이를 할당하여 메소드를 실행시킨다.
닷넷 프레임웍에도 System.Threading.Thread라는 클래스가 있지만 sealed되어 개발자가 상속받을 수 없다. 하지만 직접 new 연산자를 써서 쓰레드를 생성할 수 있다. 이때 Thread의 contructor로 전달되는 파라미터는 실행을 원하는 메소드를 지칭하는 delegate이다. 즉,
new Thread(new ThreadStart(ThreadProc)).Start();     // ThreadStart는 delegate, ThreadProc은 메소드임
와 같이 실행시킨다. (상속을 제외하고 자바와 유사하다…..)
Thread.ThreadState property를 통해 상태를 알 수 있다.
New Thread() --> Unstarted 상태
Thread.Start() --> Running 상태 (OS의 ready list에 올리는 것일 뿐, 바로 실행되는 것은 아니다.)
Thread.Suspend() --> Suspended 상태.
Thread.Resume() --> Suspended에서 Running 상태로
Thread.Sleep() --> WaitSleepJoin 상태
Suspended 상태는 Thread.Resume()에 의해서만 깨어날 수 있으나,
WaitSleepJoin 상태는 지정한 시간이 경과하거나, lock을 잃어버리거나, Thread.Interrupt()가 호출되거나할 경우 깨어난다. System.Threading.ThreadInterruuptedException 처리가 요망된다. (자바와 같다.)
(자바에서는 Thread.stop(), Thread.suspend(), Thread.resume() 모두 deprecated되었는데, 이유는 이러한 것들이 쓰레드의 상태를 깨뜨릴 수 있는 불안전한 메소드들이기 때문이다.)
쓰레드 우선순위는 5개다 : Highest, AboveNormal, Normal, BelowNormal, Lowest  (자바는 1 ~10)
우선순위는 Thread.Priority property를 통해 알 수 있다.
Thread.Abort()를 통해 쓰레드를 종료시키는데, Abort가 호출되었다고 쓰레드가 종료되었다고 볼수 없다. Abort가 호출되면 ThreadAbortException이 발생하는데, 이 Exception은 특별하여 catch블럭 끝에서 자동적으로 다시 ThreadAbortException을 발생시킨다. Catch 블록 안에서 Thread.ResetAbort()를 호출하여 Abort()를 무력화 시킬수도 있기 때문에 Thread.Join()을 호출하여 정말 쓰레드가 죽었는지 확인해야 한다. Thread.Join()은 쓰레드가 죽기 전에는 리턴하지 않는 blocking call이다.

쓰레드 동기화는...
VB.NET의 경우 SyncLock이라는 키워드가 있고, C#에는 lock이라는 키워드가 있어서, 자바의 synchronized와 동일한 역할을 한다.
Lock 객체가 객체 레벨과 클래스 레벨과 분리되어 있는 것은 자바와 동일하나 내부적으로 자바와 같은 방식으로 관리하는지는 알 수 없다.(아직까지…^^)
닷네의 경우에는 자바 보다 다양한 방식으로 thread synchronization을 지원하는데
첫째, attribute 기반 thread synchronization은 System.Runtime.Remoting.Contexts.SynchronizationAttribute를 클래스 attribute로 지정하고 ContextBoundObject를 상속받아 클래스를 작성하면 된다. 이경우 해당 클래스의 모든 메소드에 접근할때마다 lock객체를 얻어야하는 오버헤드가 발생한다.
둘째, C#의 lock 키워드 (VB.NET의 SyncLock)를 사용하여 제공한다. 이 경우 원하는 메소드 내에 블록을 지정하여 해당 블록에서만 lock 객체를 얻도록 할 수 있다. 내부적으로 컴파일러가 System.Threading.Monitor 클래스를 통해 synchronization을 제공한다. Monitor.Enter… Monitor.Exit
셋째, System.Threading.Monitor 객체를 직접 코드에서 다루면서 synchronization을 제공할 수 있다.
닷넷에서 특이한 점은 일단 한 쓰레드에 의해 특정 클래스의 클래스lock이 얻어진 경우, 그 클래스의 모든 인스턴스에 대한 접근이 block된다. 즉, 클래스lock은 모든 객체lock을 잡지 못하도록 만든다. (정말 ?? 이점이 특히 자바와 다르다. !!!!)

자바와 닷넷에서 쓰레드 생성 코드를 비교하면 너무나 유사하다. 자바에는 Anonymous class라는 개념이 있는 반면, 닷넷에는 Anonymous Method 라는 개념이 있다.

* 자바의 경우
new Thread(new Runnable() {
  public void run()
  {
     System.out.println("babo");
  }
}).start();

* 닷넷의 경우
new Thread(delegate() {
   Console.WriteLine("babo");
}).Start();

Posted by 장현춘

댓글을 달아 주세요