지난번에 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);
}