Sychronized Code Regions - ReaderWriterLock

현재 주소 복사
트랙백 주소 복사
방실이님의 글 (8/23/2007 6:55:21 PM) Viewing : 3499

The ReaderWriterLock Class

음..이 클래스는 MSDN에 의하면

ReaderWriterLock을 사용하여 리소스에 대한 액세스를 동기화합니다. 지정된 시점에 여러 스레드에 대한 동시 읽기 액세스 또는 단일 스레드에 대한 쓰기 액세스를 허용합니다. 리소스가 자주 변경되는 경우 ReaderWriterLock을 사용하면 Monitor 같이 한 번에 하나씩 수행되는 단순 잠금을 사용할 때보다 효과적으로 처리할 수 있습니다.

ReaderWriterLock은 주로 읽기 작업을 위해 액세스가 수행되며 짧은 기간에 걸쳐 가끔 쓰기가 수행되는 경우에 가장 잘 작동합니다. 다중 판독기와 단일 작성기는 서로 번갈아 작동되므로 판독기와 작성기 모두 오래 동안 잠금 상태가 유지되지 않습니다.

이 클래스에는 주요한 4개의 메서드가 존재한다.

  • AcquireReaderLock() - 이 메서드는 Reader의 잠금 상태를 가져오며 Timespan형과 int 형의 두가지의 timeout 시간의 매개변수를 갖는다. 이 timeout 매개변수는 데드락(deadlock)를 발견할수 있는 좋은 도구가 될수 있다.
  • AcquireWriterLock() - 이 메서드는 Writer의 잠금 상태를 가져오며 위 메서드와 마찬가지로 Timespan형과 int형의 두가지의 timeout 매개변수를 갖는다.
  • ReleaseReaderLock() - Reader의 잠금을 해제한다.
  • ReleaseWriterLock() - Writer의 잠금을 해제한다.

 ReaderWriterLock 클래스를 사용하게 되면 몇개의 스레드를 사용하든지 상관없이 스레드안전하게 데이터를 읽을수 있다. 
Reader 스레드는 개체가 어떤 Writer스레드에도 묶여 있지 않을때 잠김을 가져 올수 있으며 Writer 스레드는 어떤 Writer 스레드나 Reader 스레드에 묶여 있지 않을때 잠김을 가져올 수 있다. !!

예제를 살펴 보자.
아래는 ReadWrite 라는 클래스의 전문이다.

   10 private ReaderWriterLock rwl;

   11 private int x;

   12 private int y;

   13 

   14 public ReadWrite()

   15 {

   16   this.rwl = new ReaderWriterLock();

   17 }

   18 

   19 public void ReadInts(ref int a, ref int b)

   20 {

   21   this.rwl.AcquireReaderLock(Timeout.Infinite);

   22  

   23   try

   24   {

   25     a = this.x;

   26     b = this.y;

   27   }

   28   finally

   29   {

   30     this.rwl.ReleaseReaderLock();

   31   }

   32 }

   33 

   34 public void WriteInts(int a, int b)

   35 {

   36   this.rwl.AcquireWriterLock(Timeout.Infinite);

   37 

   38   try

   39   {

   40     this.x = a;

   41     this.y = b;

   42     Console.WriteLine("x = " + this.x + " y = " + this.y + " Thread ID = " + Thread.CurrentThread.ManagedThreadId.ToString());

   43   }

   44   finally

   45   {

   46     this.rwl.ReleaseWriterLock();

   47   }

   48 }

 11라인과 12라인의 int 형 필드가 스레드에서 테스트할 리소스이다.
ReadInts()메서드와 WriteInts()메서드는 이 int형 필드들을 읽고 쓰는 작업을 하는데 각각 잠김을 가져오며 해제하는 코드를 가지고 있다.

   10 private ReadWrite rw = new ReadWrite();

   11 

   12 private void Write()

   13 {

   14   int a = 10;

   15   int b = 11;

   16   Console.WriteLine("*********** Write **************");

   17 

   18   for (int i = 1; i <= 5; i++)

   19   {

   20     this.rw.WriteInts(a++, b++);

   21     Thread.Sleep(1000);

   22   }

   23 }

   24 

   25 private void Read()

   26 {

   27   int a = 10;

   28   int b = 11;

   29   Console.WriteLine("*********** Read **************");

   30 

   31   for (int i = 1; i <= 5; i++)

   32   {

   33     this.rw.ReadInts(ref a, ref b);

   34     Console.WriteLine("For i= " + i + " a = " + a + " b = " + b + " Thread ID = " + Thread.CurrentThread.ManagedThreadId.ToString());

   35     Thread.Sleep(1000);

   36   }

   37 }

   38 

   39 static void Main(string[] args)

   40 {

   41   Program p = new Program();

   42   Thread wt1 = new Thread(p.Write);

   43   Thread wt2 = new Thread(p.Write);

   44   wt1.Start();

   45   wt2.Start();

   46 

   47   Thread rt1 = new Thread(p.Read);

   48   Thread rt2 = new Thread(p.Read);

   49   rt1.Start();

   50   rt2.Start();

   51 }

위 클래스는 메인 클래스의 코드이다.
앞서 만든 ReadWrite클래스의 ReadInts()메서드와 WriteInts() 메서드를 호출하고자 하는 Read()메서드와 Write()메서드를 생성한다.
그리고 42라인에서 보여지는 것 처럼 각각 스레드를 만들어서 이 메서드들을 호출한다.

예제를 실행해보면 아래와 같은 결과가 나오게 된다.

이제 분석을 해보자..좀..많이 헤깔린다..ㅡ.ㅡ;
WriteInts()메서드에서 필드 x와 y는 a와 b의 값으로 대입된다. AcquireWriterLock()메서드가 호출되어 해당 스레드는 쓰기 잠금 상태가 되었기 때문에 ReleaseWriterLock()메서드로 해제시키기 전까지는 어떤 스레드(여기서는 rt1 읽기 스레드와 rt2 읽기 스레드)도 해당 개체에 액세스를 할 수 없게 된다. 이런 일련의 상황들은 Monitor와 비슷한다..^^;

ReadInts() 메서드도 살펴 보도록 하자.
스레드 rt1과 rt2가 AcquireReaderLock()메서드로 동시에 읽기 잠금을 가져오게 되면 역시 읽기 스레드인 wt1과 wt2는 해당 개체에 액세스를 할 수 없게 된다.
앞서 말했지만 reader 스레드만이 읽기잠금 후에 개체에 동시에 액세스가 가능하다.

Monitor의 경우에는 데이터를 읽는데 있어 최적의 안정성을 보장한다. 그러나 동시에 특정 리소스(데이터)를 여러개의 스레드가 읽고 쓰고 하는 경우에는 ReaderWriterLock 이 더더욱 우아하고 아름다운(??) 코드를 만들수 있다고 본다..쿨럭..

다음 강좌에는 Manual Synchronization에 대해서 알아 보도록 하겠다..


마지막 업데이트 : (8/24/2007 2:19:36 PM)

TAG : Thread 



첨부 파일 보기 (1)
Trackback 보기 (0)
댓글 보기 (2)
정성태님의 글 (8/26/2007 6:33:19 PM)
성능상의 문제로 인해 ReaderWriterLock 을 권하지 않지요. 다행히 .NET 3.5 에서는 그 문제점을 해결한 새로운 클래스를 내놓았지요. 이름하여, ReaderWriterLockSlim. ^^



이방은님의 글 (8/27/2007 9:31:49 AM)
아..그렇군요..
성능이 좀 떨어 지나 보네용..ㅋ~~



댓글 쓰기

Sychronized Code Regions - Monitors(2)

현재 주소 복사
트랙백 주소 복사
방실이님의 글 (8/22/2007 5:09:39 PM) Viewing : 2614

Wait() and Pulse() Mechanism

Wait()와 Pulse()는 스레드간의 상호작용이 있는 그런 메카니즘이다.
앞서 게시글에서도 언급을 하였지만.
Wait()메서드가 호출되면 어떤 신호가 자신을 깨워주기 전까지 계속 대기상태가 된다.
Pulse()와 PulseAll()메서드는 대기중인 스레드(들)에게 이런 신호를 주게 된다.

Wait()와 Pulse()는 당연하겠지만..
반드시 Enter()메서드와 Exit()메서드 사이에서 사용되어야 한다..

아래 예제를 보도록 하자.

    8   public class LockMe

    9   {

   10   }

   11 

   12   class Program

   13   {

   14     static void Main(string[] args)

   15     {

   16       LockMe me = new LockMe();

   17       Test01 t1 = new Test01(me);

   18       Test02 t2 = new Test02(me);

   19 

   20       Thread thread1 = new Thread(t1.CriticalSection);

   21       thread1.Start();

   22       Thread thread2 = new Thread(t2.CriticalSection);

   23       thread2.Start();

   24     }

   25   }

테스트를 하기 위해 잠금의 대상이 될 LockMe 라는 클래스를 생성한다.

그리고 Main 메서드에서 2개의 스레드를 각각 호출한다.
이제 Test01 클래스와 Test02클래스를 살펴 보자.

   10     private int result = 0;

   11     private LockMe me;

   12 

   13     public Test01() { }

   14     public Test01(LockMe me)

   15     {

   16       this.me = me;

   17     }

기본 생성자 외에 LockMe 형을 매개변수로 받는 생성자를 추가한다.

   19 public void CriticalSection()

   20 {

   21   Monitor.Enter(this.me);

   22 

   23   Console.WriteLine("Test01 Thread 진입 : "+Thread.CurrentThread.ManagedThreadId.ToString());

   24   for (int i = 1; i <= 5; i++)

   25   {

   26     Monitor.Wait(this.me);

   27     Console.WriteLine("Test01 Thread 일어남");

   28     Console.WriteLine("Test01 : Result = " + this.result++ + " ThreadID : " + Thread.CurrentThread.ManagedThreadId.ToString());

   29     Monitor.Pulse(this.me);       

   30   }

   31 

   32   Console.WriteLine("Test01 Thread 종료 : " + Thread.CurrentThread.ManagedThreadId.ToString());

   33 

   34   Monitor.Exit(this.me);

   35 }

   19 public void CriticalSection()

   20 {

   21   Monitor.Enter(this.me);

   22 

   23   Console.WriteLine("Test02 Thread 진입 : " + Thread.CurrentThread.ManagedThreadId.ToString());

   24   for (int i = 1; i <= 5; i++)

   25   {

   26     Monitor.Pulse(this.me);

   27     Console.WriteLine("Test02 : Result = " + this.result++ + " ThreadID : " + Thread.CurrentThread.ManagedThreadId.ToString());

   28     Monitor.Wait(this.me);

   29     Console.WriteLine("Test02 Thread 일어남");

   30   }

   31 

   32   Console.WriteLine("Test02 Thread 종료 : " + Thread.CurrentThread.ManagedThreadId.ToString());

   33 

   34   Monitor.Exit(this.me);

   35 }

연이어서 Test01클래스의 CriticalSection()메서드와 Test02클래스의 CriticalSection() 메서드를 기술해 보았다.
위 2개의 메서드를 비교해 보면서 살펴 보도록 하자..헤깔림..ㅡ.ㅡ;

메인 메서드의 스레드 호출 순서로 인해 먼저 Test01클래스의 CriticalSection()메서드가 실행된다.
그리고 for loop내에서 Monitor.Wait()메서드로 인해 LockMe 개체는 대기 상태가 된다. 이렇게 되면 다른 스레드 ( 위 예제에서는 Test02가 되겠다.)에서 Monitor.Pulse()메서드로 깨워 주기 전까지 무한 대기 상태에 있게 된다.
LockMe 개체를 하나의 스레드에서만 액세스가능하도록 잠그기 위하여 Wait(this.me)라는 메서드로 대기를 시켰다.
Wait()메서드로 대기를 시키면 LockMe개체는 임시로 릴리즈된 상태이기 때문에 다른 스레드에서 액세스가 가능해지게 된다.
이제 thread2에서 LockMe 개체에 액세스가 가능해진다.
thread2에서 호출된 Test02.CriticalSection() 메서드는 for loop를 진행 하다가 26라인에 이르러서 Monitor.Pulse()메서드로 다른 스레드에 의해 대기 상태에 있는 LockMe 개체를 깨우게 된다.
그리고 계속 진행하게 되며 29라인에 의해 대기상태로 들어 가게 된다.
26라인에 의해서 thread1이 깨워지면 대기중이던 Test01.CriticalSection() 메서드는 이어서 실행을 하며 for loop를 이어서 돌게 된다. 그리고 마찬가지로 29라인에서 다른 대기중인 스레드(thread2)를 깨우게 된다.

이런 일련의 과정이 끝나게 되면 결과는 아래와 같다.

The TryEnter() Method

Monitor.Enter()메서드와 비슷하지만 약간 다른 Monitor.TryEnter() 메서드가 있다.
이 메서드는 다른 Try~~ 씨리즈 메서드 처럼 boolean 타입을 리턴한다.
그대로 해석해보자면 진입을 시도해서..성공하면 true를 리턴하고 실패하면 false를 리턴하는 형식이 되겠다.

   10 static void Main(string[] args)

   11 {

   12   TryEnter t = new TryEnter();

   13   Thread thread1 = new Thread(t.CriticalSection);

   14   Thread thread2 = new Thread(t.CriticalSection);

   15   thread1.Start();

   16   thread2.Start();

   17 }

Main 메서드를 보게 되면 순차적으로 2개의 스레드를 만들어 실행 시켰다.

   10 public void CriticalSection()

   11 {

   12   bool b = Monitor.TryEnter(this, 1000);

   13   Console.WriteLine("Thread : " + Thread.CurrentThread.ManagedThreadId.ToString() + " Value - " + b);

   14 

   15   for (int i = 1; i <= 3; i++)

   16   {

   17     Thread.Sleep(1000);

   18     Console.WriteLine(i + " " + Thread.CurrentThread.ManagedThreadId.ToString() + " ");

   19   }

   20 

   21   if (b)

   22   {

   23     Monitor.Exit(this);

   24   }

   25 }

이제 CriticalSection 메서드를 살펴 보자.
12라인에 보면 TryEnter메서드를 호출했다. 이는 해당 개체(여기에서는 현재 클래스의 참조-this)를 잠글수 있다면 true를 즉시 반환 하고 실패 하게 되면 즉시 false를 반환한다. 대기 하지 않는다..
thread1은 당연히 true를 반환하며 잠김상태가 될것이며 그 이후에 바로 호출된 thread2는 잠김에 실패 할 것이다.

결국 21라인에서 thread1은 잠김을 해제를 하며 thread2는 잠기지 않았기 때문에 해제를 하게 되면 SynchronizationLockException이 발생하게 된다.
음 비유가 될려나 모르겠지만..
전화 통화는 일반적으로 한명하고만 할 수 있다.
thread1이라는 사용자가 전화를 하고 this 라는 개체와 전화를 하고 있다 (쿨럭..)
이 때 thread2라는 사용자가 this에게 전화를 걸면 어떤 현상이 일어 날까.
요즘은 잠시 대기 하고 thread2와 연결하여 지금통화중이니 나중에 하쇼..라고 말을 할 수 있지만..
약간 년 ..전에는 this.는 통화중이니 음성메시지를 남기라는 말만 남기고 연결이 종료된다..(음 억지인가..ㅡ.ㅡ;)
여튼.....잠김에 실패 하면 대기 하지 않는 다는거...

다음 시간엔 ReaderWriterLock에 대해서 알아 보자...기대는 조금만...안해도 무관..ㅡ.ㅡ;


추가사항

댓글의 성태님의 글을 보아..혹시나 지나칠수 있는 분도 있을테고..해서 추가 하자면..
Enter() 메서드는 잠금을 시작하는 상태이기 때문에 Exit() 로 릴리즈를 시켜주지 않으면 계속 잠겨 있게 된다.
그래서 아래 성태님 말처럼 만약에 Enter()이후에 예외가 발생되어 throw가 일어나면 사용자가 finally 블럭에서  Exit() 메서드를 추가 하여야 한다. 마치 file 이나 db 연결과 같은 케이스라고 보면 되겠다.
try / finally 의 대목은 이 뜻을 의미한다...


마지막 업데이트 : (8/23/2007 9:14:26 AM)

TAG : Thread 



첨부 파일 보기 (1)
Trackback 보기 (0)
댓글 보기 (3)
정성태님의 글 (8/22/2007 10:22:42 PM)
예제코드이니만큼 감안을 해야겠지만.
그래도 Monitor.Enter/Exit 메서드를 직접 호출하는 것보다는 lock 예약어를 사용하는 것이 더 좋지 않을까요?
lock 을 안 쓰더라도, 적어도 Monitor 에서 그 안의 코드 만큼은 try / finally 처리는 되어야 하지 않을런지. ^^

예제 코드대로라면, 만약 Enter 한 다음에, 이후의 코드에서 예외가 발생하면?... ^^

(그렇지 않아도 나중에 쓸 얘기였는데.. 참견했다면 죄송... ^^)



이방은님의 글 (8/23/2007 9:05:18 AM)
ㅎㅎㅎ
먼저 Monitor.Enter/Exit는 lock과 같은 동작을 한다는 것은 이미 언급을 했었구요.
예제는 심플해야 한다는 기조아래 중요하지 않는 것은 없앴습니다..
원하는 작동이 잘 동작하는 수준의 예제만 하거든요...
그래야 해당 글에서 얘기하고자 하는 것을 쉽게 받아 드릴수 있지 않을까요...
아니면...그러한 처리까지 한 완벽한(?) 코드여야 할까요???
음..생각해보니..그문제도 약간은 생각을 해 보아야 겠네용.......
성태님 말씀도 일리가 있으니..ㅡ.ㅠ;



정성태님의 글 (8/24/2007 2:08:42 PM)
헛... ^^ 나중에 쓸 얘기를 참견한 것이 아니라.
벌써 이전 토픽에서 언급한 내용을 제가 참견했군요. ^^;



댓글 쓰기

Sychronized Code Regions - Monitors(1)

현재 주소 복사
트랙백 주소 복사
방실이님의 글 (8/8/2007 2:39:24 PM) Viewing : 2656

이제 특정 코드 블럭을 동기화 하는 부분을 알아 보도록 하겠다.
이르자면 특정 코드 블럭을 앞에서 살펴 보았던 Critical Section(임계영역)으로 설정해서 동기화를 시키는..방법이 되겠다.
이번 게시글은 그 첫번째인...Monitor에 대해서 알아 보도록 하겠다.

Monitors

Monitor 클래스를 이용한 동기화 방법은 Monitor.Enter() 정적메서드로 잠금을 시작하며, Monitor.Exit() 정적 메서드로 잠금을 해제한다.
그 외에 Wait() Pulse() PulseAll() 과 같은 메서드가 있긴 하지만 먼저 Enter() 메서드와 Exit() 메서드를 이용한 기본 사용법을 살펴 보도록 하겠다.

Monitor 클래스의 메서드는 모두 정적 메서드이며 이 자체를 인스턴스화 시킬수 없다.

.NET Framework에서의 잠김 매커니즘은 개체의 인스턴스에 단지 하나의 스레드만이 액세스가 가능하도록 되어 있다. 앞서 살펴 보았던 동기화기법 또한 마찬가지이며 뒤에 살펴볼 동기화 기법도 마찬가지이다.
하나의 스레드가 액세스를 하고 있을때는 Wait 상태를 가지게 된다.이런 잠김 메커니즘은 쉽게 말해 스레드간의 통신을 위해 생긴 것이라고 볼 수 있겠다.
이런 여러가지의 잠김 메커니즘은 단지 하나의 스레드가 개체의 임계영역에 진입을 하게 되고 해제가 되면 다른 스레드가 그 개체의 임계영역에 다시 진입을 하게 되는 그런 구조이다.
마치 화장실에 줄을 쭉...서서 기다리면서 단지 한사람만이 화장실을 이용하고..이용이 끝나면 다음 사람이 이용하고...하듯이..

그렇다면 첫번째 스레드가 해제되었다는 것을 두번째 스레드는 어떻게 알 수 있을까..?
화장실에서 줄을 서는 것이라면..뭐..사람 나오는게 보일테니...알겠지만..@.@

A스레드와 B스레드가 있다고 가정해보자.
A스레드가 먼저 개체의 임계영역에 진입을 하게 되면 B스레드는 대기(Wait())하게 된다. 그 후 A스레드의 작업이 모두 끝났다면 A스레드는 Pulse() 메서드를 호출하여 신호를 주게 된다. 이제 B스레드는 이 신호를 보고 이 개체의 임계영역에 접근을 할 수 있게 되겠다.  Wait and Pulse 매커니즘으로 작동이 된것이다.

자 이제 잡담은 고만 하고..기다리던 예제를 보자..ㅡ.ㅡ;

public class EnterExit

{

  private int result = 0;

 

  public void NonCriticalSection()

  {

    Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + "번 스레드 진입");

 

    for (int i = 1; i <= 5; i++)

    {

      Console.WriteLine("Result = " + this.result++ + " ThreadID " + Thread.CurrentThread.ManagedThreadId.ToString());

 

      Thread.Sleep(1000);

    }

 

    Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + "번 스레드 종료");

  }

 

  public void CriticalSection()

  {

    Monitor.Enter(this);

 

    this.NonCriticalSection();

 

    Monitor.Exit(this);

  }

}

위 클래스는 2개의 메서드를 가지고 있는데 Monitor의 기능을 알아 보기 위해 기능은 같으나 Monitor를 사용하지 않는 메서드(NonCriticalSection())과 사용하고 있는 메서드 (CriticalSection())를 정의 해 놓았다.

CriticalSection()메서드에서 임계영역은 Monitor.Enter(this) 메서드와 Monitor.Exit(this)사이의 코드 블럭으로 정의 되어 있다. 다들 알겠지만 this 라는 매개변수는 현재의 개체를 의미한다. 
Enter() 메서드에는 object형의 매개변수가 필수인데 이것에 어떤 개체를 할당해주어야 하는지..항상 고민된다..ㅡ.ㅠ;
이 개체에 어떤 다른 스레드도 액세스 할수 없도록 잠길을 설정하고 싶을때 this 포인터를 넘기면 될것이다.

음 ..예를 들자면..
스레드 안전 래퍼 클래스를 보도록 하자.

AccountWrapper 클래스의 Withdraw메서드 안에 정의되어 있는 lock의 매개변수를 보면 this 포인터 보다는 account라는 개체가 더 적절하다고 볼 수 있다. 
왜냐하면 잠김을 하고자 하는 의도는 Account 개체를 잠그기위함이니 AccountWrapper 개체를 잠그기위함이 아니기 때문이다. 다중스레드에서 Account 개체에 대한 액세스를 제한 하고 싶은것이지 AccountWrapper 개체에 대해서 액세스를 제한하고 싶은게 아닌것이다..

static void Main(string[] args)

{

  EnterExit e = new EnterExit();

 

  //Thread t1 = new Thread(e.NonCriticalSection);

  //t1.Start();

 

  //Thread t2 = new Thread(e.NonCriticalSection);

  //t2.Start();

 

  Thread t3 = new Thread(e.CriticalSection);

  t3.Start();

 

  Thread t4 = new Thread(e.CriticalSection);

  t4.Start();

}

Main 메서드는 위와 같다.  t1과 t2는 주석처리 되었는데 각자 주석을 제거 하면서 테스트를 해보자.
짐작이 되겠지만...
먼저 t1과 t2를 사용해 보면..결과는 아래와 같다.

이정도는 이제 예상이 되어야 한다고 본다..ㅡ.ㅡ;
이제 t3과 t4를 사용해 보면 결과는 아래와 같다.

다음 강좌에서는 Wait()와 Pulse()에 대해서 살펴 보도록 하겠다.


마지막 업데이트 : (8/8/2007 2:39:24 PM)

TAG : Thread 



첨부 파일 보기 (1)
Trackback 보기 (0)
댓글 보기 (0)
댓글 쓰기

Synchronization Context(수정)

현재 주소 복사
트랙백 주소 복사
방실이님의 글 (8/1/2007 5:51:54 PM) Viewing : 2692

정말 오랜만에 글을 쓴다..
흐흐흐..
바쁘다며 이 핑계 저 핑계 대다보니 ...
어쨋든 기다리는 사람도 없겠지만...이왕 시작한거 끝은 봐야 겠기에...계속 끄적여 보기로 하겠다.


Synchronization Context

 Context (컨텍스트) 라는게 뭘까..
아...주..어려워 보인다..
한글로는 문맥이라고 번역이 되는 듯 한데..이 의미가 더 아리송 하다.
컨텍스트라는 것에 대해 이런 저런 자료를 찾아 보다가...
유경상님의 글을 찾게 되었다..무려.....5년전에 쓰신 글이다..쿨럭...

강좌 보러 가기

위 강좌의 서두에서 보여 지듯이 상당히 어려운 내용이다.^^;
그러나  이 글의 주제인 Synchronization 에 대한 내용은 간단한 것이니...

설명하고자 하는 것은 SynchronizationAttribute 이다.

MSDN 보기

MSDN에 의하면...이 특성을 할당 받은 클래스의 인스턴스를 사용하게 되면 모든 컨텍스트에서 하나의 스레드만 사용하게 된다..라고 나와있다.
(참고 MSDN에도 나와 있지만 사용하기 위해서는...System.EnterprizeServices.dll을 참조 추가하여야 한다.)

이 SynchronizationAttribute 특성은 ContextBoundObject 개체 혹은 상속 받은 개체와 더불어 쉽게 동기화를 구현할 수 있게 해준다.
이 개체는 Context-bound 개체로 일컬어지는 컨텍스트 규칙에 의해 바운드되고 하나의 컨텍스트에 위치하게 된다. 
그리고 이 개체에 접근하는 다른 경쟁하는 스레드로부터 자동으로 잠금(lock)을 설정한다.
이는..이 개체가 클래스의 인스턴스들(필드, 메서드 등등-static은 당연하겠지만..해당되지 않는다.)에 모두 적용 되기 때문에 동기화를 수동적으로 다룸에 있어 익숙하지 않는 개발자에게 용이하다고 할 수 있다.

별 어려운 의미는 아니니..이전 강좌에서 사용했던 의사 코드를 수정하여 살펴 보도록 하겠다.

[Synchronization(SynchronizationOption.Required)]

public class Account : ContextBoundObject

{

  public ApprovedOrNot Withdraw( Amount )

  {

    1. 잔금 체크

    2. 계좌의 업데이트

    3. ATM 의 승인

  }

}

SynchronizationAttribute 는 2개의 생성자를 갖고 있는데 기본 생성자와 SynchronizationOption 열거형을 갖는 2가지가 있다.
기본 생성자는 SynchronizationOption .Required 값을 갖는다.

SynchronizationOption 열거형의 종류는 아래와 같다.

멤버 이름 설명
Disabled COM+는 개체의 컨텍스트를 결정할 때 구성 요소의 동기화 요구 사항을 무시합니다. 
NotSupported 이 값을 가진 개체는 호출자의 상태에 관계 없이 동기화에 참여할 수 없습니다. 이 설정은 트랜잭션이 아니고 JIT(Just-In-Time) 활성화를 사용하지 않는 구성 요소에만 사용할 수 있습니다. 
Required 구성 요소에서 만들어진 모든 개체가 동기화되는지 확인합니다. 
RequiresNew 이 값을 가진 개체는 호출에 관련된 모든 구성 요소 대신 COM+가 컨텍스트와 아파트를 관리하는 새로운 동기화에 참여해야 합니다. 
Supported 이 값을 가진 개체는 동기화가 있는 경우 동기화에 참여합니다. 

마지막 업데이트 : (8/7/2007 6:47:22 PM)

TAG : Thread 



Trackback 보기 (0)
댓글 보기 (2)
김태진님의 글 (8/30/2007 1:53:54 PM)
어렵긴 하지만 잘 보고 있습니다!

쓰레드 구현에 접근하기가 너무 어려웠는데..
그래도 도움이 많이 되고있습니다..^^

좋의 강좌입니다요~>ㅅ<b



이방은님의 글 (8/30/2007 2:03:08 PM)
흐흐흑..
감사합니다..ㅡ.ㅠ;



댓글 쓰기

동기화(1) - MethodImplAttribute

현재 주소 복사
트랙백 주소 복사
방실이님의 글 (6/8/2007 4:06:59 PM) Viewing : 3186

닷넷에서의 동기화

닷넷에서는 스레드 안전성을 위해 System.Threading, System.EnterpriseServices, System.Runtime.Compiler 네임스페이스에서 몇가지의 클래스를 지원한다.
그중에 자주 쓰이는 중요한 클래스에 대해서 개략적으로 알아 보고 후에 하나씩 살펴 보도록 하자.

CLASS Discription 
Monitor

 Monitor 클래스는 하나의 스레드에 개체 잠금을 부여하여 개체에 대한 액세스를 제어합니다. 개체 잠금 기능을 사용하면 일반적으로 임계 영역이라고 불리는 코드 블록에 대한 액세스를 제한할 수 있습니다. 한 스레드에서 개체 잠금을 소유하는 동안에는 다른 스레드에서 해당 잠금을 가져올 수 없습니다. 다른 스레드에서 잠겨 있는 다른 개체를 사용하여 코드를 실행하는 경우에만 Monitor를 사용하여 다른 스레드에서 잠금 소유자가 실행하고 있는 응용 프로그램 코드의 섹션에 액세스할 수 있도록 할 수도 있습니다.

WaitHandle

이 클래스는 일반적으로 동기화 개체의 기본 클래스로 사용됩니다. WaitHandle에서 파생된 클래스는 공유 리소스에 대한 액세스 또는 액세스 해제를 나타내는 신호 메커니즘을 정의하지만 공유 리소스에 대한 액세스를 기다리는 동안 상속한 WaitHandle 메서드를 사용하여 차단합니다.

이 클래스는 .NET 2.0이 되면서 약간의 변화가 생겼다.
기존의 이 클래스의 파생 클래스는 Mutex, AutoResetEvent, ManualResetEvent 가 있었으나 .NET 2.0으로 오게 되면서 새로이 Semaphore라는 파생 클래스가 새로 생겼으며 EventWaitHandle 클래스가 새로 생겨 기존의 AutoResetEvent 클래스와 ManualResetEvent 클래스는 이 EventWaitHandle 클래스를 상속 받게끔 변경되었다.

Interlocked  이 클래스의 메서드는 다른 스레드에서 액세스할 수 있는 변수를 스레드가 업데이트하는 동안 스케줄러가 컨텍스트를 전환할 때 또는 두 스레드가 별도의 프로세스에서 동시에 실행될 때 오류가 발생하지 않도록 보호해 줍니다. 이 클래스의 멤버는 예외를 throw하지 않습니다.
SynchronizationAttribute  SynchronizationAttribute를 컨텍스트 바인딩 개체에 적용하면 가비지가 명확하게 수집되지 않는 대기 핸들 및 자동 재설정 이벤트가 만들어집니다. 그러므로 SynchronizationAttribute로 표시된 컨텍스트 바인딩 개체를 짧은 기간 동안 많이 만들면 안 됩니다.
MethodImplAttribute  메서드의 구현 방법을 정의 합니다.

 

간단히 MSDN에 있는 설명을 발췌하였다.
이 부분에 대해서 하나씩 살펴 보도록 하겠다.

MethodImplAttribute Class

이 클래스는 System.Runtime.CompilerServices 네임스페이스에 속해 있는 어트리뷰트(특성)이다. 
이 특성은 메서드를 구현(선언)할때 어떤 방법으로 할 것인지 정하는 그런 특성이다.
그런데 왠 갑자기 이런 특성을 얘기 하고 나왔냐면..ㅡ.ㅡ;
이 특성의 생성자중에 MethodImplOptoins 열거형을 받는 생성자가 있다.
이 열거형에는 총 6개의 값들이 있는데 그 값은 다음과 같다.

 ForwardRef 메서드가 선언되지만 구현되지 않도록 지정합니다. 
 InternalCall 내부 호출을 지정합니다. 내부 호출은 공용 언어 런타임 자체 내에 구현된 메서드에 대한 호출입니다. 
 NoInlining 메서드를 인라인할 수 없도록 지정합니다. 
 PreserveSig 메서드 시그니처를 선언한 대로 정확하게 내보내도록 지정합니다. 
 Synchronized 한 번에 하나의 스레드에서만 메서드를 실행할 수 있도록 지정합니다. 정적 메서드는 형식을 잠그지만 인스턴스 메서드는 인스턴스를 잠급니다. 
 Unmanaged 비관리 코드에서 메서드를 구현하도록 지정합니다. 

이중에 우리가 주목해야 하는 것은 다들 눈치 챘겠지만..ㅡ.ㅡ;
Sysnchronized 라는 값이다. 설명을 보면 알겠지만 이는 lock 키워드와 기능이 같다. 
예제를 보도록 하자.

   11 [MethodImpl(MethodImplOptions.Synchronized)]

   12 public void DoSomeWorkSync()

   13 {

   14   Console.WriteLine("DoSomeWorkSync() -- {0} 스레드에 의해 잠김", Thread.CurrentThread.ManagedThreadId.ToString());

   15 

   16   Thread.Sleep(5000);

   17   Console.WriteLine("DoSomeWorkSync() -- {0} 스레드에 의해 풀림", Thread.CurrentThread.ManagedThreadId.ToString());

   18 }

   19 

   20 public void DoSomeWorkNoSync()

   21 {

   22   Console.WriteLine("DoSomeWorkNoSync() -- {0} 스레드에 진입", Thread.CurrentThread.ManagedThreadId.ToString());

   23 

   24   Thread.Sleep(5000);

   25   Console.WriteLine("DoSomeWorkNoSync() -- {0} 스레드에서 탈출", Thread.CurrentThread.ManagedThreadId.ToString());

   26 }

2개의 메서드를 정의 해 본다.
11행의 첫번째 메서드는 MethodImpl 특성으로 선언되어 있다. 당연하겠지만 MehtodImplOtions.Synchronized 라는 열거형 값으로 선언되어 있다.
그리고 같은 내용의 메서드를 20행에 하나더 선언하였다.
그리고 각각 시작점과 끝점 사이는 5초간의 슬립시간을 주었다.

   28 static void Main(string[] args)

   29 {

   30   Program p = new Program();

   31 

   32   Thread t1 = new Thread(p.DoSomeWorkNoSync);

   33   Thread t2 = new Thread(p.DoSomeWorkNoSync);

   34   t1.Start();

   35   t2.Start();

   36 

   37   Thread t3 = new Thread(p.DoSomeWorkSync);

   38   Thread t4 = new Thread(p.DoSomeWorkSync);

   39   t3.Start();

   40   t4.Start();

   41 }

Main 메서드의 코드를 보자.
먼저 동기화를 시키지 않은 메서드를 2개의 스레드가 호출을 한다. 
그리고 동기화를 시킨 메서드를 2개의 또 다른 스레드가 호출을 한다.
자 어떤 결과가 나올지 0.5초간 생각을 해보도록 하자.

충분히 예상 가능한 결과치다.
결과를 보게 되면 특성을 선언하지 않은 메서드에서는 스레드가 동시에 진입이 된다. 그리고 t3 스레드가 시작되었을때는 lock으로 묶여지게 된다.
t3이 묶여 있기 때문에 t4는 해당 메서드를 엑세스 할 수 없으며 대기 상태가 된다.
그러나 t1과 t2는 5초후에 사이좋게 스레드를 종료하게 되며, 잠시 후에 t3이 종료 되었을때 t4가 실행되게 된다.
완벽하게 lock과 같은 원리로 작동된다.


마지막 업데이트 : (6/8/2007 4:11:11 PM)

TAG : Thread 



첨부 파일 보기 (1)
Trackback 보기 (0)
댓글 보기 (1)
댓글 쓰기



<< < 1 2 > >>