Thread 개념
Process
•
실행중인 프로그램
•
스케줄링(생성 → 준비 → 실행 → 대기 → ... → 종료)
•
멀티태스킹
Thread
•
OS가 CPU 시간을 할당하는 기본 단위
•
프로세스는 하나 이상의 스레드로 구성
•
활용할 수 있는 대표적인 상황 : file read/write
•
단점
◦
구현, 디버그, 유지보수의 어려움
◦
여러 스레드 중 하나의 스레드에만 문제가 생겨도 프로세스 전체에 영향을 줄 가능성
◦
빈번한 컨텍스트 스위칭이 일어나면서 성능이 저하될 가능성
C# Thread
Namespace | System.Threading |
Inheritance | Object → CriticalFinalizerObject → Thread |
public sealed class Thread : System.Runtime.ConstrainedExecution.CriticalFinalizerObject
C#
복사
using System;
using System.Diagnostics;
using System.Threading;
public class Example
{
public static void Main()
{
var th = new Thread(ExecuteInForeground);
th.Start();
Thread.Sleep(1000);
Console.WriteLine("Main thread ({0}) exiting...",
Thread.CurrentThread.ManagedThreadId);
}
private static void ExecuteInForeground()
{
var sw = Stopwatch.StartNew();
Console.WriteLine("Thread {0}: {1}, Priority {2}",
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.ThreadState,
Thread.CurrentThread.Priority);
do {
Console.WriteLine("Thread {0}: Elapsed {1:N2} seconds",
Thread.CurrentThread.ManagedThreadId,
sw.ElapsedMilliseconds / 1000.0);
Thread.Sleep(500);
} while (sw.ElapsedMilliseconds <= 5000);
sw.Stop();
}
}
C#
복사
Thread 사용하기
Thread 생성
•
함수에 매개변수가 없는 경우 ThreadStart 델리게이트를 받는 생성자 호출
public delegate void ThreadStart()
C#
복사
using System;
using System.Threading;
class Test
{
static void Main()
{
ThreadStart threadDelegate = new ThreadStart(Work.DoWork);
Thread newThread = new Thread(threadDelegate);
newThread.Start();
Work w = new Work();
w.Data = 42;
threadDelegate = new ThreadStart(w.DoMoreWork);
newThread = new Thread(threadDelegate);
newThread.Start();
}
}
class Work
{
public static void DoWork()
{
Console.WriteLine("Static thread procedure.");
}
public int Data;
public void DoMoreWork()
{
Console.WriteLine("Instance thread procedure. Data={0}", Data);
}
}
// This code example produces the following output (the order of the lines might vary):
// Static thread procedure.
// Instance thread procedure. Data=42
C#
복사
•
함수에 매개변수가 있는 경우 ParameterizedThreadStart 델리게이트를 받는 생성자 호출
public delegate void ParameterizedThreadStart(object? obj);
C#
복사
using System;
using System.Threading;
public class Work
{
public static void Main()
{
// Start a thread that calls a parameterized static method.
Thread newThread = new Thread(Work.DoWork);
newThread.Start(42);
// Start a thread that calls a parameterized instance method.
Work w = new Work();
newThread = new Thread(w.DoMoreWork);
newThread.Start("The answer.");
}
public static void DoWork(object data)
{
Console.WriteLine("Static thread procedure. Data='{0}'",
data);
}
public void DoMoreWork(object data)
{
Console.WriteLine("Instance thread procedure. Data='{0}'",
data);
}
}
// This example displays output like the following:
// Static thread procedure. Data='42'
// Instance thread procedure. Data='The answer.'
C#
복사
Thread 실행
스레드 시작
•
Start()
•
IsBackground = true // 백그라운드에서 실행 → 프로세스 종료시 스레드도 함께 종료
스레드 중지
•
Abort()
◦
함수의 종료를 보장하지 않음
•
Join()
◦
함수의 종료를 보장함
◦
메인 스레드가 동작 중인 함수(Joing한 스레드)의 끝까지 대기
•
Interrupt()
◦
함수의 종료를 보장하지 않음
◦
Exception throw
Thread 동기화
•
데이터나 변수들을 공유하는 경우(critical section)
◦
공유하는 자원에 차례로 접근할 수 있도록 하는 것이 동기화
◦
lock(object){}로 묶인 블록 내의 코드가 동작하는 동안에는 다른 스레드가 해당 블록을 실행하지 못함
Deadlock
•
위와 같이 스레드에 필요한 서로 다른 리소스가 각각 다른 스레드에 lock되었을 때 두 스레드 모두 진행이 되지 않는 교착상태(deadlock)에 빠질 위험이 있음
Monitor
•
보통 try...finally로 묶인 블록을 lock블록과 같이 수행
•
Monitor.Enter(object)
•
Monitor.Exit(object)
class MyClass
{
private int counter = 1000;
private object lockObject = new object();
public void Run()
{
// 10개의 쓰레드가 동일 메서드 실행
for (int i = 0; i < 10; i++)
{
new Thread(SafeCalc).Start();
}
}
// Thread-Safe하지 않은 메서드
private void SafeCalc()
{
// 한번에 한 쓰레드만 lock블럭 실행
Monitor.Enter(lockObject);
try
{
counter++;
// 가정 : 다른 복잡한 일을 한다
for (int i = 0; i < counter; i++)
for (int j = 0; j < counter; j++) ;
Console.WriteLine(counter);
}
finally
{
Monitor.Exit(lockObject);
}
}
}
C#
복사
•
Monitor.Wait(object)
◦
현재 스레드를 잠시 중지하고, lock을 Release한 후, WaitSleepJoin 상태로 진입
◦
해당 스레드는 lock을 놓고 Wait Queue에 입력, 다른 스레드가 lock을 획득하고 작업 수행
•
Monitor.Pulse(object) / Monitor.PulseAll()
◦
다른 스레드가 자신의 작업을 마치고 Pulse() 메서드를 호출하면 Waiting Queue의 가장 첫 스레드를 꺼낸 뒤 Ready Queue에 입력
◦
Ready Queue에 입력된 스레드는 입력된 차례대로 lock을 획득하고 작업 수행
class Program
{
static Queue Q = new Queue();
static object lockObj = new object();
static bool running = true;
static void Main(string[] args)
{
// reader 쓰레드 시작
Thread reader = new Thread(ReadQueue);
reader.Start();
// writer 쓰레드들 시작
List<Thread> thrds = new List<Thread>();
for (int i = 0; i < 10; i++)
{
var t = new Thread(new ParameterizedThreadStart(WriteQueue));
t.Start(i);
thrds.Add(t);
}
// 모든 writer가 종료될 때까지 대기
thrds.ForEach(p => p.Join());
// reader 종료
running = false;
}
static void WriteQueue(object val)
{
lock (lockObj)
{
Q.Enqueue(val);
Console.WriteLine("W:{0}", val);
Monitor.Pulse(lockObj); // lock 블록 안에서 실행
}
}
static void ReadQueue()
{
while (running)
{
lock (lockObj)
{
while (Q.Count == 0)
{
Monitor.Wait(lockObj); // lock 블록 안에서 실행
}
int qCount = Q.Count;
for (int i = 0; i < qCount; i++)
{
int val = (int)Q.Dequeue();
Console.WriteLine("R:{0}", val);
}
}
}
}
}
C#
복사
Mutex
•
Monitor클래스와 같이 Critiacal Section을 locking
•
Monitor는 하나의 프로세스 내에서만 사용가능하지만, Mutex는 여러 프로세스간에서도 locking
•
대신 Mutex를 사용하면 monitor 보다 느리기 때문에 한 프로세스내에서만 locking이 필요한 경우는 lock이나 Monitor를 사용
using System;
using System.Threading;
using System.Collections.Generic;
namespace MultiThrdApp
{
class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(() => MyClass.AddList(10));
Thread t2 = new Thread(() => MyClass.AddList(20));
t1.Start();
t2.Start();
t1.Join();
t2.Join();
// 메인쓰레드에서 뮤텍스 사용
using (Mutex m = new Mutex(false, "MutexName1"))
{
// 뮤텍스를 취득하기 위해 10 ms 대기
if (m.WaitOne(10))
{
// 뮤텍스 취득후 MyList 사용
MyClass.MyList.Add(30);
}
else
{
Console.WriteLine("Cannot acquire mutex");
}
}
MyClass.ShowList();
}
}
public class MyClass
{
// MutexName1 이라는 뮤텍스 생성
private static Mutex mtx = new Mutex(false, "MutexName1");
public static List<int> MyList = new List<int>();
// 데이터를 리스트에 추가
public static void AddList(int val)
{
// 먼저 뮤텍스를 취득할 때까지 대기
mtx.WaitOne();
// 뮤텍스 취득후 실행 블럭
MyList.Add(val);
// 뮤텍스 해제
mtx.ReleaseMutex();
}
// 리스트 출력
public static void ShowList()
{
MyList.ForEach(p => Console.WriteLine(p));
}
}
}
C#
복사
참고한 사이트