Search
Duplicate
🧵

C#에서 Thread 사용하기

간단소개
코드를 짜다보면 필연적으로 마주치게 되는 스레드..! C#에서는 어떻게 사용할까
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
C#
Thread
Scrap
태그
9 more properties

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#
복사
참고한 사이트