소프트웨어/C# & ASP.NET

C# 쓰레딩 [멀티쓰레딩]

falconer 2009. 8. 12. 19:20
지난번에 Thread를 왜 쓰레드라고 표기한지에 대해서와 단일 쓰레드의 시작과 종료에 대해 썰을 풀었다.
이번에는 멀티 쓰레드와 동기화를 함해보고 담에는 쓰레드 풀에 대해 썰을 풀어보자.

using System;
using System.Threading;

//데이터를 주고받아야 하니 Class를 하나 만들고 인터페이스도 만들어 두자.
class work{
  int a; //받을 인자값

  //인터페이스 메소드
  public work(int a){
   this.a = a;
  }
 
  //실제 일할놈
  public void runit(){
   for (int i=0; i<10; i++){
    Console.WriteLine("Thread{0} Running : {1}", a, i);
    Thread.Sleep(100);
   }
  }
}

//메인쓰레드가 있는 클래스
class Test {
  static void Main(){
   Console.WriteLine("쓰레드 시작");
   work wk1 = new work(1); //1로 지정
   work wk2 = new work(2); //2로 지정
   ThreadStart td1 = new ThreadStart(wk1.runit); //시작쓰레드 선언하고
   ThreadStart td2 = new ThreadStart(wk2.runit);
   Thread t1 = new Thread(td1); //돌릴준비하고
   Thread t2 = new Thread(td2);
   t1.Start(); //돌리자
   t2.Start();
  }
}

사용자 삽입 이미지
실행후 1번과 2번 쓰레드가 동시에 작동한다.

그런데 1번보다 2번 쓰레드가 중요하다던가 하는 상황에서는 어떻게 하면 될까?
그때는 "ThreadPriority"메소드를 사용하여 우선순위를 지정할 수 있다.

using System;
using System.Threading;
class work{
  int a;
  public work(int a){
   this.a = a;
  }
 
  public void runit(){
   for (int i=0; i<10; i++){
    Console.WriteLine("Thread{0} Running : {1}", a, i);
    Thread.Sleep(100);
   }
  }
}
class Test {
  static void Main(){
   Console.WriteLine("쓰레드 시작");
   work wk1 = new work(1);
   work wk2 = new work(2);
   ThreadStart td1 = new ThreadStart(wk1.runit);
   ThreadStart td2 = new ThreadStart(wk2.runit);
   Thread t1 = new Thread(td1);
   Thread t2 = new Thread(td2);
   t1.Priority = ThreadPriority.Lowest; //이 부분 추가됨. 1번 쓰레드 우선 순위 최하
   t2.Priority = ThreadPriority.Highest; //이 부분 추가됨. 2번 쓰레드 우선 순위 최고
   t1.Start();
   t2.Start();
  }
}

1번 쓰레드에 우선 순위를 최하로 부여하고 2번 쓰레드에는 우선 순위를 올려 보았다.
(설정은 "Highest, AboveNormal, Normal, BelowNormal, Lowest"로 5단계로 설정할 수 있다.)
사용자 삽입 이미지
본 코드가 적용되기전의 결과와는 다르게 2번 쓰레드가 먼저 생성되고 종료된다.
그러나 믿지는 말자. 인텔 계열의 CPU는 0부터 31까지의 값을 가지고 있고 이는 윈도우에서 사용하는 우선 순위나 별반 다르지 않다.

* 작업관리자에서 우선순위를 설정해 보신분들은 금방 알아볼 것이다. 다음의 그림처럼...
사용자 삽입 이미지
암튼 지맘이니까 믿지는 말자. 쓰레드의 위험성은 이처럼 결과를 예측하고 그 예측이 맞을것이라 바라는것 되겠다. 절대 하지말아야 할 주의점 이다.

이렇게 멀티로 작업을 하다보면 공통 변수로 작업해야 할때가 있다. 이때 여러개의 쓰레드가 1개의 값을 건드리다보면 변수의 값이 엉뚱하게 나오기도 한다.

using System;
using System.Threading;
class work{
  public int a;
  public void runit(){
   int tp = a + 1;
   Console.WriteLine(tp.ToString());
   Thread.Sleep(10);
   a = tp;   
  }
}
class Test {
  static void Main(){
   Console.WriteLine("쓰레드 시작");
   work wk = new work();
   Thread[] td = new Thread[5];
   for (int i=0; i<5; i++){
   td[i] = new Thread(new ThreadStart(wk.runit));
   td[i].Start();
   }
   Thread.Sleep(1000);
   Console.WriteLine("최종값:{0}",wk.a);
  } 
}

요녀석을 실행함 해보자.
사용자 삽입 이미지
결과값은 분명 5가 나와야 하지만 그렇지 않고 1로 고정이 되어버렸다.
다음과 같이 lock이란 녀석을 한번 넣어보자.
using System;
using System.Threading;
class work{
  public int a;
  public void runit(){
   lock(this){ //바로 여기
   int tp = a + 1;
   Console.WriteLine(tp.ToString());
   Thread.Sleep(10);
   a = tp;   
    }
  }
}
class Test {
  static void Main(){
   Console.WriteLine("쓰레드 시작");
   work wk = new work();
   Thread[] td = new Thread[5];
   for (int i=0; i<5; i++){
   td[i] = new Thread(new ThreadStart(wk.runit));
   td[i].Start();
   }
   Thread.Sleep(1000);
   Console.WriteLine("최종값:{0}",wk.a);
  } 
}


실행을 하면 다음과 같이 정상값으로 나온다.
사용자 삽입 이미지
lock이란것으로 동시에 호출하는 쓰레드에 대해 작업중이니 기다리라는 명령을 내릴 수 있는것이다. 그러나 기능의 다양성을 원한다면 lock보다는 "Monitor.Enter()"와 "Monitor.Exit()"를 써주기 바란다.

  public void runit(){
   Monitor.Enter(this);
   int tp = a + 1;
   Console.WriteLine(tp.ToString());
   Thread.Sleep(10);
   a = tp;   
    Monitor.Exit(this);
  }

출처 : http://www.wolfpack.pe.kr/123?category=2