Search
moon
sun
🧵

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