수색…
비고
스레드 는 다른 부분과 독립적으로 실행할 수있는 프로그램의 일부입니다. 다른 스레드와 동시에 작업을 수행 할 수 있습니다. 멀티 스레딩 은 프로그램이 동시 처리를 수행하여 한 번에 둘 이상의 작업을 수행 할 수있게 해주는 기능입니다.
예를 들어 스레딩을 사용하여 백그라운드에서 타이머 또는 카운터를 업데이트하는 동시에 포 그라운드에서 다른 작업을 수행 할 수 있습니다.
멀티 스레드 응용 프로그램은 사용자 입력에 더 잘 반응하며 작업 부하가 증가 할 때 개발자가 스레드를 추가 할 수 있으므로 쉽게 확장 할 수 있습니다.
기본적으로 C # 프로그램에는 스레드 (주 프로그램 스레드)가 하나 있습니다. 그러나 보조 스레드를 만들고 기본 스레드와 병렬로 코드를 실행하는 데 사용할 수 있습니다. 이러한 스레드를 작업자 스레드라고합니다.
스레드의 작업을 제어하기 위해 CLR은 스레드 스케줄러라는 운영 체제에 함수를 위임합니다. 스레드 스케줄러는 모든 스레드가 적절한 실행 시간을 할당 받도록 보장합니다. 또한 차단되거나 잠긴 스레드가 CPU 시간을 많이 소비하지 않는지 확인합니다.
.NET Framework System.Threading
네임 스페이스를 사용하면 스레드를 더 쉽게 사용할 수 있습니다. System.Threading은 많은 클래스와 인터페이스를 제공하여 멀티 스레딩을 가능하게합니다. 특정 스레드에 대한 유형 및 클래스를 제공하는 것 외에도 스레드 컬렉션, 타이머 클래스 등을 보유하는 유형을 정의합니다. 또한 공유 데이터에 동기화 된 액세스를 허용하여 지원을 제공합니다.
Thread
는 System.Threading
네임 스페이스의 기본 클래스입니다. 다른 클래스에는 AutoResetEvent
, Interlocked
, Monitor
, Mutex
및 ThreadPool
됩니다.
System.Threading
네임 스페이스에있는 일부 대리자는 ThreadStart
, TimerCallback
및 WaitCallback
합니다.
System.Threading
네임 스페이스의 열거 형에는 ThreadPriority
, ThreadState
및 EventResetMode
됩니다.
.NET Framework 4 및 이후 버전에서는 System.Threading.Tasks.Parallel
및 System.Threading.Tasks.Task
클래스, Parallel LINQ (PLINQ), System.Collections.Concurrent
새로운 동시 수집 클래스를 통해보다 쉽고 간단하게 다중 스레드 프로그래밍이 가능합니다. System.Collections.Concurrent
네임 스페이스 및 새로운 작업 기반 프로그래밍 모델.
간단한 완벽한 스레딩 데모
class Program
{
static void Main(string[] args)
{
// Create 2 thread objects. We're using delegates because we need to pass
// parameters to the threads.
var thread1 = new Thread(new ThreadStart(() => PerformAction(1)));
var thread2 = new Thread(new ThreadStart(() => PerformAction(2)));
// Start the threads running
thread1.Start();
// NB: as soon as the above line kicks off the thread, the next line starts;
// even if thread1 is still processing.
thread2.Start();
// Wait for thread1 to complete before continuing
thread1.Join();
// Wait for thread2 to complete before continuing
thread2.Join();
Console.WriteLine("Done");
Console.ReadKey();
}
// Simple method to help demonstrate the threads running in parallel.
static void PerformAction(int id)
{
var rnd = new Random(id);
for (int i = 0; i < 100; i++)
{
Console.WriteLine("Thread: {0}: {1}", id, i);
Thread.Sleep(rnd.Next(0, 1000));
}
}
}
작업을 사용한 간단한 완벽한 스레딩 데모
class Program
{
static void Main(string[] args)
{
// Run 2 Tasks.
var task1 = Task.Run(() => PerformAction(1)));
var task2 = Task.Run(() => PerformAction(2)));
// Wait (i.e. block this thread) until both Tasks are complete.
Task.WaitAll(new [] { task1, task2 });
Console.WriteLine("Done");
Console.ReadKey();
}
// Simple method to help demonstrate the threads running in parallel.
static void PerformAction(int id)
{
var rnd = new Random(id);
for (int i = 0; i < 100; i++)
{
Console.WriteLine("Task: {0}: {1}", id, i);
Thread.Sleep(rnd.Next(0, 1000));
}
}
}
명시 적 작업 패럴림
private static void explicitTaskParallism()
{
Thread.CurrentThread.Name = "Main";
// Create a task and supply a user delegate by using a lambda expression.
Task taskA = new Task(() => Console.WriteLine($"Hello from task {nameof(taskA)}."));
Task taskB = new Task(() => Console.WriteLine($"Hello from task {nameof(taskB)}."));
// Start the task.
taskA.Start();
taskB.Start();
// Output a message from the calling thread.
Console.WriteLine("Hello from thread '{0}'.",
Thread.CurrentThread.Name);
taskA.Wait();
taskB.Wait();
Console.Read();
}
암시 적 작업 병렬 처리
private static void Main(string[] args)
{
var a = new A();
var b = new B();
//implicit task parallelism
Parallel.Invoke(
() => a.DoSomeWork(),
() => b.DoSomeOtherWork()
);
}
두 번째 스레드 생성 및 시작
긴 계산을 여러 번 수행하는 경우 컴퓨터의 다른 스레드에서 동시에 실행할 수 있습니다. 이를 위해 우리는 새로운 스레드를 만들고 다른 스레드 를 가리킨다.
using System.Threading;
class MainClass {
static void Main() {
var thread = new Thread(Secondary);
thread.Start();
}
static void Secondary() {
System.Console.WriteLine("Hello World!");
}
}
매개 변수가있는 스레드 시작
using System.Threading;
class MainClass {
static void Main() {
var thread = new Thread(Secondary);
thread.Start("SecondThread");
}
static void Secondary(object threadName) {
System.Console.WriteLine("Hello World from thread: " + threadName);
}
}
프로세서 당 하나의 스레드 생성
Environment.ProcessorCount
현재 시스템의 논리 프로세서 수를 가져옵니다.
그러면 CLR은 각 스레드를 논리 프로세서에 예약합니다. 이는 이론적으로 다른 논리 프로세서의 각 스레드, 단일 논리 프로세서의 모든 스레드 또는 일부 다른 조합을 의미 할 수 있습니다.
using System;
using System.Threading;
class MainClass {
static void Main() {
for (int i = 0; i < Environment.ProcessorCount; i++) {
var thread = new Thread(Secondary);
thread.Start(i);
}
}
static void Secondary(object threadNumber) {
System.Console.WriteLine("Hello World from thread: " + threadNumber);
}
}
동시에 데이터 읽기 및 쓰기 방지
때로는 스레드가 동시에 데이터를 공유하기를 원합니다. 이런 일이 발생하면 코드를 인식하고 잘못 될 수있는 부분을 잠그는 것이 중요합니다. 두 개의 스레드 카운트의 간단한 예가 아래에 나와 있습니다.
다음은 위험한 (잘못된) 코드입니다.
using System.Threading;
class MainClass
{
static int count { get; set; }
static void Main()
{
for (int i = 1; i <= 2; i++)
{
var thread = new Thread(ThreadMethod);
thread.Start(i);
Thread.Sleep(500);
}
}
static void ThreadMethod(object threadNumber)
{
while (true)
{
var temp = count;
System.Console.WriteLine("Thread " + threadNumber + ": Reading the value of count.");
Thread.Sleep(1000);
count = temp + 1;
System.Console.WriteLine("Thread " + threadNumber + ": Incrementing the value of count to:" + count);
Thread.Sleep(1000);
}
}
}
1,2,3,4,5를 세는 대신에 우리는 1,1,2,2,3을 계산합니다.
이 문제를 해결하려면 여러 개의 다른 스레드가 동시에 읽고 쓸 수 없도록 count 값을 잠글 필요가 있습니다. 잠금과 키가 추가되어 스레드가 동시에 데이터에 액세스하는 것을 방지 할 수 있습니다.
using System.Threading;
class MainClass
{
static int count { get; set; }
static readonly object key = new object();
static void Main()
{
for (int i = 1; i <= 2; i++)
{
var thread = new Thread(ThreadMethod);
thread.Start(i);
Thread.Sleep(500);
}
}
static void ThreadMethod(object threadNumber)
{
while (true)
{
lock (key)
{
var temp = count;
System.Console.WriteLine("Thread " + threadNumber + ": Reading the value of count.");
Thread.Sleep(1000);
count = temp + 1;
System.Console.WriteLine("Thread " + threadNumber + ": Incrementing the value of count to:" + count);
}
Thread.Sleep(1000);
}
}
}
Parallel.ForEach 루프
foreach 루프를 사용하여 속도를 높이고 출력 순서가 마음에 들지 않으면 다음을 수행하여 병렬 foreach 루프로 변환 할 수 있습니다.
using System;
using System.Threading;
using System.Threading.Tasks;
public class MainClass {
public static void Main() {
int[] Numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Single-threaded
Console.WriteLine("Normal foreach loop: ");
foreach (var number in Numbers) {
Console.WriteLine(longCalculation(number));
}
// This is the Parallel (Multi-threaded solution)
Console.WriteLine("Parallel foreach loop: ");
Parallel.ForEach(Numbers, number => {
Console.WriteLine(longCalculation(number));
});
}
private static int longCalculation(int number) {
Thread.Sleep(1000); // Sleep to simulate a long calculation
return number * number;
}
}
교착 상태 (서로 대기하는 두 개의 스레드)
교착 상태는 두 개 이상의 스레드가 서로 기다리고 영원히 대기하는 방식으로 자원을 완료하거나 해제 할 때 발생합니다.
서로 기다리는 두 가지 스레드의 일반적인 시나리오는 Windows Forms GUI 스레드가 작업 스레드를 기다리고 작업자 스레드가 GUI 스레드가 관리하는 개체를 호출하려고 할 때입니다. 이 코드 예제에서는 button1을 클릭하면 프로그램이 중단됩니다.
private void button1_Click(object sender, EventArgs e)
{
Thread workerthread= new Thread(dowork);
workerthread.Start();
workerthread.Join();
// Do something after
}
private void dowork()
{
// Do something before
textBox1.Invoke(new Action(() => textBox1.Text = "Some Text"));
// Do something after
}
workerthread.Join()
은 workerthread가 완료 될 때까지 호출 스레드를 차단하는 호출입니다. textBox1.Invoke(invoke_delegate)
는 GUI 스레드가 invoke_delegate를 처리 할 때까지 호출 스레드를 차단하는 호출이지만 GUI 스레드가 호출 스레드가 완료 될 때까지 대기중인 경우이 호출로 인해 교착 상태가 발생합니다.
이 문제를 해결하기 위해 대신 텍스트 상자를 호출하는 비 차단 방법을 사용할 수 있습니다.
private void dowork()
{
// Do work
textBox1.BeginInvoke(new Action(() => textBox1.Text = "Some Text"));
// Do work that is not dependent on textBox1 being updated first
}
그러나 먼저 업데이트되는 텍스트 상자에 종속적 인 코드를 실행해야하는 경우 문제가 발생할 수 있습니다. 이 경우 호출을 호출의 일부로 실행하십시오. 그러나 이것이 GUI 스레드에서 실행되도록합니다.
private void dowork()
{
// Do work
textBox1.BeginInvoke(new Action(() => {
textBox1.Text = "Some Text";
// Do work dependent on textBox1 being updated first,
// start another worker thread or raise an event
}));
// Do work that is not dependent on textBox1 being updated first
}
또는 새로운 전체 스레드를 시작하고 GUI 스레드에서 대기하게하여 workerthread가 완료 될 수 있도록하십시오.
private void dowork()
{
// Do work
Thread workerthread2 = new Thread(() =>
{
textBox1.Invoke(new Action(() => textBox1.Text = "Some Text"));
// Do work dependent on textBox1 being updated first,
// start another worker thread or raise an event
});
workerthread2.Start();
// Do work that is not dependent on textBox1 being updated first
}
상호 대기 중 교착 상태에 빠질 위험을 최소화하려면 가능한 경우 스레드 간의 순환 참조를 방지하십시오. 낮은 순위의 스레드가 높은 순위의 스레드에 대해서만 메시지를 남기고 결코 기다리지 않는 스레드 계층 구조는 이러한 종류의 문제를 일으키지 않습니다. 그러나 리소스 잠금에 기반한 교착 상태에는 여전히 취약합니다.
교착 상태 (리소스 보유 및 대기)
교착 상태는 두 개 이상의 스레드가 서로 기다리고 영원히 대기하는 방식으로 자원을 완료하거나 해제 할 때 발생합니다.
thread1이 자원 A에 대한 잠금을 보유하고 thread2가 자원 B를 보유하고 자원 A가 해제되기를 기다리는 동안 자원 B가 해제되기를 기다리는 경우 교착 상태가됩니다.
다음 예제 코드에서 button1을 클릭하면 응용 프로그램이 앞서 설명한 교착 상태가되어 정지하게됩니다.
private void button_Click(object sender, EventArgs e)
{
DeadlockWorkers workers = new DeadlockWorkers();
workers.StartThreads();
textBox.Text = workers.GetResult();
}
private class DeadlockWorkers
{
Thread thread1, thread2;
object resourceA = new object();
object resourceB = new object();
string output;
public void StartThreads()
{
thread1 = new Thread(Thread1DoWork);
thread2 = new Thread(Thread2DoWork);
thread1.Start();
thread2.Start();
}
public string GetResult()
{
thread1.Join();
thread2.Join();
return output;
}
public void Thread1DoWork()
{
Thread.Sleep(100);
lock (resourceA)
{
Thread.Sleep(100);
lock (resourceB)
{
output += "T1#";
}
}
}
public void Thread2DoWork()
{
Thread.Sleep(100);
lock (resourceB)
{
Thread.Sleep(100);
lock (resourceA)
{
output += "T2#";
}
}
}
}
이 방법으로 교착 상태가 발생하지 않도록하려면 Monitor.TryEnter (lock_object, timeout_in_milliseconds)를 사용하여 객체에 대한 잠금이 이미 있는지 확인합니다. Monitor.TryEnter가 timeout_in_milliseconds 전에 lock_object에 대한 잠금을 획득하지 못하면 false를 반환하여 스레드에게 다른 보유 된 자원을 해제하고 항복 할 기회를 주므로 다른 스레드에 위의 약간 수정 된 버전과 같이 완료 할 수있는 기회를줍니다 :
private void button_Click(object sender, EventArgs e)
{
MonitorWorkers workers = new MonitorWorkers();
workers.StartThreads();
textBox.Text = workers.GetResult();
}
private class MonitorWorkers
{
Thread thread1, thread2;
object resourceA = new object();
object resourceB = new object();
string output;
public void StartThreads()
{
thread1 = new Thread(Thread1DoWork);
thread2 = new Thread(Thread2DoWork);
thread1.Start();
thread2.Start();
}
public string GetResult()
{
thread1.Join();
thread2.Join();
return output;
}
public void Thread1DoWork()
{
bool mustDoWork = true;
Thread.Sleep(100);
while (mustDoWork)
{
lock (resourceA)
{
Thread.Sleep(100);
if (Monitor.TryEnter(resourceB, 0))
{
output += "T1#";
mustDoWork = false;
Monitor.Exit(resourceB);
}
}
if (mustDoWork) Thread.Yield();
}
}
public void Thread2DoWork()
{
Thread.Sleep(100);
lock (resourceB)
{
Thread.Sleep(100);
lock (resourceA)
{
output += "T2#";
}
}
}
}
이 해결 방법은 스레드 2가 잠금에 대해 고집스럽고 스레드 1이 기꺼이 양보하여 thread2가 항상 우선 순위를 갖게한다는 것에 유의하십시오. 또한 thread1은 리소스 A를 잠그고 나면 작업을 다시 실행해야합니다. 그러므로 두 개 이상의 항복 스레드를 사용하여이 접근법을 구현할 때 조심하십시오. 두 개의 스레드가 작업의 첫 번째 비트를 계속 수행 한 다음 서로 상호 작용할 때 발생하는 상태 인 소위 라이브 록을 입력 할 위험이 있습니다. 반복적으로 다시 시작합니다.