|
## Thread
|
|
## **Threading**
|
|
|
|
**Thread**는 Process 내부에서 생성되는 명령어를 실행하기 위한 스케줄링의 단위이다. Process를 생성할 때 기본적으로 한 개의 Main Thread를 생성한다. Muti-Core CPU에서는 여러 개의 Thread를 동시에 실행할 수 있어서 이를 활용한 응용 프로그램을 만들 수 있다.
|
|
|
|
|
|
|
|
> System.Threading
|
|
|
|
|
|
## Task
|
|
### **Thread**
|
|
|
|
**Thread**를 생성하고 실행하는 데 필요한 명령어를 가진 클래스다.
|
|
|
|
|
|
|
|
#### **Foreground Thread**
|
|
|
|
**Foreground Thread**는 Main Thread의 종료와 상관없이 모든 Thread가 종료되어야 Process가 종료된다.
|
|
|
|
|
|
## Async
|
|
```
|
|
|
|
static void Main()
|
|
|
|
{
|
|
|
|
Console.WriteLine("Main Thread Start");
|
|
|
|
|
|
|
|
Thread thread = new Thread(threadFunc);
|
|
|
|
thread.Start();
|
|
|
|
|
|
|
|
Console.WriteLine("Main Thread End");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void threadFunc()
|
|
|
|
{
|
|
|
|
Console.WriteLine("Foreground Thread Start");
|
|
|
|
|
|
|
|
Thread.Sleep(3000); // 3초 동안 실행 중지
|
|
|
|
|
|
|
|
Console.WriteLine("Foreground Thread End");
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
실행 결과 :
|
|
|
|
Main Thread Start
|
|
|
|
Foreground Thread Start
|
|
|
|
Main Thread End
|
|
|
|
Foreground Thread End
|
|
|
|
```
|
|
|
|
|
|
|
|
#### **Background Thread**
|
|
|
|
**Background Thread**는 Thread의 종료 여부와 상관없이 Main Thread가 종료되면 Process도 종료된다. 단, Join()을 사용하면 해당 Thread가 종료할 때까지 현재 Thread는 대기한다.
|
|
|
|
|
|
|
|
```
|
|
|
|
static void Main()
|
|
|
|
{
|
|
|
|
Console.WriteLine("Main Thread Start");
|
|
|
|
|
|
|
|
Thread thread = new Thread(threadFunc);
|
|
|
|
thread.IsBackground = true;
|
|
|
|
thread.Start();
|
|
|
|
|
|
|
|
thread.Join(); // thread가 종료할 때까지 대기
|
|
|
|
|
|
|
|
Console.WriteLine("Main Thread End");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void threadFunc()
|
|
|
|
{
|
|
|
|
Console.WriteLine("Background Thread Start");
|
|
|
|
|
|
|
|
Thread.Sleep(3000); // 3초 동안 실행 중지
|
|
|
|
|
|
|
|
Console.WriteLine("Background Thread End");
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
실행 결과 :
|
|
|
|
Main Thread Start
|
|
|
|
Background Thread Start
|
|
|
|
Background Thread End
|
|
|
|
Main Thread End
|
|
|
|
```
|
|
|
|
|
|
|
|
### **Monitor**
|
|
|
|
**Monitor**는 임의의 객체를 잠금으로써 특정 코드의 접근을 막는 기능을 가지고 있다. 여러 Thread가 하나의 공유자원을 동시에 사용하면 문제가 발생하는데 **Monitor**를 통해 하나의 Thread에서만 공유자원을 사용할 수 있도록 함으로써 Thread를 동기화할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
|
|
static void Main()
|
|
|
|
{
|
|
|
|
MyData data = new MyData(); // MyData 클래스 생략
|
|
|
|
|
|
|
|
Thread thread1 = new Thread();
|
|
|
|
Thread thread2 = new Thread();
|
|
|
|
|
|
|
|
thread1.Start(data);
|
|
|
|
thread2.Start(data);
|
|
|
|
|
|
|
|
thread1.Join();
|
|
|
|
thread2.Join();
|
|
|
|
|
|
|
|
Console.WriteLine(data.Number);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void threadFunc(object inst)
|
|
|
|
{
|
|
|
|
Mydata data = inst as MyData;
|
|
|
|
|
|
|
|
for (int i = 0; i < 100000; i++)
|
|
|
|
{
|
|
|
|
Monitor.Enter(data);
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
data.compute(); // MyData의 Method
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
Monitor.Exit(data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
* `lock` 키워드를 사용하면 try~finally와 Monitor의 역할을 대신 할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
|
|
static void threadFunc(object inst)
|
|
|
|
{
|
|
|
|
Mydata data = inst as MyData;
|
|
|
|
|
|
|
|
for (int i = 0; i < 100000; i++)
|
|
|
|
{
|
|
|
|
lock (data)
|
|
|
|
{
|
|
|
|
data.compute();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### **Interlocked**
|
|
|
|
**Interlocked**는 다중 Thread의 공유자원에 대한 **Atomic Operation**를 제공한다. 특히 64bit 변수의 연산 과정에서 동기화 문제가 생길 수 있는데 **Atomic Operation**을 통해 하나의 Thread에서 한 번에 처리할 수 있다. 이 경우에 **Monitor**을 사용하여 Thread를 동기화할 수 있지만, **Interlocked**에서 제공하는 연산 기능으로 간단하게 구현할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
|
|
static void threadFunc(object inst)
|
|
|
|
{
|
|
|
|
long number = 0; // 8byte(64bit)
|
|
|
|
|
|
|
|
Interlocked.Exchange(ref number, 3); // number = 5;
|
|
|
|
Interlocked.Add(ref number, 5); // number += 5;
|
|
|
|
Interlocked.Increment(ref number); // number++;
|
|
|
|
Interlocked.Decrement(ref number); // number--;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### **ThreadPool**
|
|
|
|
**ThreadPool**은 필요할 때마다 Thread를 꺼내 쓰고 다시 반환할 수 있는 기능을 제공한다. **Thread**를 따로 생성할 필요 없이 `QueueUserWorkItem()`에게 Thread가 자동으로 생성되고 지정한 Method가 할당되어 실행한다.
|
|
|
|
|
|
|
|
```
|
|
|
|
static void Main()
|
|
|
|
{
|
|
|
|
Mydata data = new Mydata();
|
|
|
|
|
|
|
|
ThreadPool.QueueUserWorkItem(threadFunc, data);
|
|
|
|
ThreadPool.QueueUserWorkItem(threadFunc, data);
|
|
|
|
|
|
|
|
Thread.Sleep(3000); // 3초 동안 실행 중지
|
|
|
|
|
|
|
|
Console.WriteLine(data.Number);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### **EventWaitHandle**
|
|
|
|
**EventWaitHandle**은 Signal과 Non-Signal의 상태 변화를 통해 Thread를 동기화하는 방법이다. `WaitOn()`에서 Non-Signal이 Signal 상태로 바뀔 때까지 Thread가 더 실행되지 못하게 하고 대기 상태로 만든다. 앞서 나온 **Background Thread**의 `Join()`과 유사한 역할을 한다.
|
|
|
|
|
|
|
|
```
|
|
|
|
static void Main()
|
|
|
|
{
|
|
|
|
Console.WriteLine("Main Thread Start");
|
|
|
|
|
|
|
|
EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset); // Non-Signal 상태, 수동 Reset
|
|
|
|
|
|
|
|
Thread thread = new Thread(threadFunc);
|
|
|
|
thread.IsBackground = true;
|
|
|
|
thread.Start(ewh);
|
|
|
|
|
|
|
|
ewh.WaitOn(); // Signal 상태로 바뀔 때까지 대기
|
|
|
|
|
|
|
|
Console.WriteLine("Main Thread End");
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
static void threadFunc(object state)
|
|
|
|
{
|
|
|
|
EventWaitHandle ewh = state as EventWaitHandle;
|
|
|
|
|
|
|
|
Console.WriteLine("Background Thread Start");
|
|
|
|
|
|
|
|
Thread.Sleep(3000); // 3초 동안 실행 중지
|
|
|
|
|
|
|
|
Console.WriteLine("Background Thread End");
|
|
|
|
|
|
|
|
ewh.Set(); // Signal 상태로 전환
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
실행 결과 :
|
|
|
|
Main Thread Start
|
|
|
|
Background Thread Start
|
|
|
|
Background Thread End
|
|
|
|
Main Thread End
|
|
|
|
```
|
|
|
|
|
|
|
|
## **Acync**
|
|
|
|
### **Async Method**
|
|
|
|
`acync`와 `await` 키워드를 사용하여 간편하게 **비동기 호출**을 할 수 있다. `async`는 내부에 `await`를 사용했으므로 해당 Method를 **Async Method**로 정의해주는 역할을 하고, `await`는 대기 작업이 완료될 때까지 Method의 실행을 일시 중단하여 비동기를 적용한다.
|
|
|
|
|
|
|
|
```
|
|
|
|
async void AwaitDownloadString()
|
|
|
|
{
|
|
|
|
WebClient webClient = new WebClient();
|
|
|
|
|
|
|
|
string text = await webClient.DownloadStringTaskAsync("http://www.novonetworks.com/");
|
|
|
|
|
|
|
|
Console.WriteLine(text);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### **Task**
|
|
|
|
**Task**는 Thread Pool에서 Thread를 가져와 비동기 작업을 실행한다. **Task** 객체를 만들기 위해서 생성자에 **Method Delegate**를 지정한다.
|
|
|
|
|
|
|
|
#### **반환 값이 없는 Task**
|
|
|
|
```
|
|
|
|
Task task = new Tack(() => { Thread.Sleep(3000); });
|
|
|
|
task.Start();
|
|
|
|
```
|
|
|
|
|
|
|
|
* **Task**를 생성할 필요 없이 **Action Delegate**를 전달하고 바로 작업을 시작할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
|
|
Task task = Task.Factory.StartNew(() => { Thread.Sleep(3000); });
|
|
|
|
```
|
|
|
|
|
|
|
|
#### **반환 값이 있는 Task<TResult>**
|
|
|
|
|
|
|
|
```
|
|
|
|
Task<int> task = new Task<int>(() =>
|
|
|
|
{
|
|
|
|
Thread.Sleep(3000);
|
|
|
|
|
|
|
|
return 3;
|
|
|
|
});
|
|
|
|
|
|
|
|
task.Start();
|
|
|
|
|
|
|
|
Console.WriteLine(task.Result); // 출력 결과 : 3
|
|
|
|
```
|
|
|
|
|
|
|
|
* 마찬가지로 **Task<TResult>**는 **Func Delegate**를 전달하고 바로 작업을 시작할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
|
|
Task<int> task = Task.Factory.StartNew<int>(() =>
|
|
|
|
{
|
|
|
|
Thread.Sleep(3000);
|
|
|
|
|
|
|
|
return 3;
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
Console.WriteLine(task.Result); // 출력 결과 : 3
|
|
|
|
```
|
|
|
|
|
|
|
|
### **Async Method가 아닌 경우의 비동기 처리**
|
|
|
|
**Async Method**가 아닌 Method의 반환 형식을 **Task<TResult>**로 바꾸면 `await`를 이용해 **비동기 호출**을 적용할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
|
|
async void AwaitDownloadString()
|
|
|
|
{
|
|
|
|
string text = await MyDownloadString("http://www.novonetworks.com/");
|
|
|
|
|
|
|
|
Console.WriteLine(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
Task<string> MyDownloadString(string uri)
|
|
|
|
{
|
|
|
|
WebClient webClient = new WebClient();
|
|
|
|
|
|
|
|
return Task.Factory.StartNew(() =>
|
|
|
|
{
|
|
|
|
return webClient.DownloadString(uri);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### **비동기 호출의 병렬 처리**
|
|
|
|
`async`와 `await` 키워드를 사용해 **비동기 호출**을 적용하면 여러 작업을 병렬로 처리할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
|
|
static void Main()
|
|
|
|
{
|
|
|
|
DoAsyncTask();
|
|
|
|
|
|
|
|
Console.WriteLine("Main Thread End");
|
|
|
|
}
|
|
|
|
|
|
|
|
static async void DoAsyncTask()
|
|
|
|
{
|
|
|
|
Stopwatch stopWatch = new Stopwatch();
|
|
|
|
|
|
|
|
var task1 = ThreeSeconds();
|
|
|
|
var task2 = FiveSeconds();
|
|
|
|
|
|
|
|
stopWatch.Start();
|
|
|
|
await Task.WhenAll(task1, task2);
|
|
|
|
stopWatch.Stop();
|
|
|
|
|
|
|
|
TimeSpan timeSpan = stopWatch.Elapsed;
|
|
|
|
|
|
|
|
Console.WriteLine("Total : " + (task1.Result + task2.Result));
|
|
|
|
Console.WriteLine("Result : " + timeSpan.Seconds);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Task<int> ThreeSeconds()
|
|
|
|
{
|
|
|
|
return Task.Factory.StartNew(() =>
|
|
|
|
{
|
|
|
|
Thread.Sleep(3000);
|
|
|
|
return 3;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
static Task<int> FiveSeconds()
|
|
|
|
{
|
|
|
|
return Task.Factory.StartNew(() =>
|
|
|
|
{
|
|
|
|
Thread.Sleep(5000);
|
|
|
|
return 5;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
출력 결과 :
|
|
|
|
Main Thread End
|
|
|
|
Total : 8
|
|
|
|
Result : 5
|
|
|
|
``` |
|
|
|
\ No newline at end of file |