차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.

차이 보기로 링크

멀티스레딩 [2011/06/28 09:43] (현재)
raychani 새로 만듦
줄 1: 줄 1:
 +{{tag>​csharp programming}}
 +======멀티스레딩======
 +**멀티스레딩(multithreading)**은 한 [[프로세스]]((실행중인 프로그램,​ 프로그램이 클래스라면 프로세스는 그 클래스의 객체이다. 자신만의 메모리 영역을 가진다.))를 여러 수행 단위([[스레드]]((소프트웨어의 수행 경로, CPU 시간을 할당받는 수행의 단위. 작업을 실제로 수행하는 실행의 흐름)))로 나누어 처리하는 것을 말한다.
 +
 +멀티 태스킹과 유사한 점이 있다. 멀티태스킹은 한 PC에서 여러 작업(프로세스)를 동시에 수행하고,​ 멀티스레딩은 한 프로세스에서 여러 실행의 흐름(스레드)를 동시에 수행한다.
 +
 +물론 CPU는 한번에 한 명령만 처리할 수 있으므로 동시라고 해도 한 작업을 일정부분 처리하고,​ 다른 작업을 일정부분 처리하고,​ 다시 처음 작업을 조금 처리하고... 이와 같은 과정을 반복하여 동시처럼 보이게 한다.
 +
 +===== 장점 =====
 +주 사용 이유는 다음과 같다.
 +  * CPU 이용률 최대화
 +    * CPU는 PC에서 가장 중요한(비싼) 자원이다. 이 자원을 쉴틈없이 활용하는 것은 굉장히 중요하다.
 +    * 그러나 CPU는 가장 빠르고, 다른 자원은 비교적 느리기 때문에 최대한 활용하는 것이 쉽지 않다.
 +  * 대기시간,​ 응답시간 최소화
 +    * 오랜 시간이 걸리는 작업을 수행하는 동안에도 사용자의 입력을 처리할 수 있다.
 +  * 실행의 흐름을 명확히 분리
 +    * 한 작업을 한 스레드로 만들어, 개념적으로 실행의 흐름을 분리할 수 있다.
 +===== 단점 =====
 +이렇게 장점이 많지만 단점도 많다. 가장 어려운 점은 어렵다는 것. 멀티스레드 프로그래밍을 할 경우 다음 사항을 항상 주의하여야 한다.
 +  * **race condition**
 +  * 멀티 스레드 프로그램은 예측 불가능
 +  * 실행 순서가 보장되지 않음
 +  * 언제나, 어디서나 문맥 교환(context switching) 발생 가능
 +  * 스레드는 작은 변화에 매우 민감
 +  * 스레드는 즉시 시작하지 않을 수 있음
 +
 +===== C# 멀티스레드 환경에서 공유자원에 대한 접근 동기화 =====
 +**HOWTO: Visual C# .NET을 사용하여 다중 스레딩 환경에서 공유 리소스에 대한 액세스 동기화**((http://​support.microsoft.com/​default.aspx?​scid=kb;​en-us;​816161))를 번역한 것이다. 상당히 의역했고,​ 주요 단어에 대해서는 각주를 달았다.
 +
 +==== 개요 ====
 +
 +멀티스레딩을 이용하면 여러 작업을 동시에 수행할 수 있다. 멀티스레딩은 프로그램의 성능과 응답성을 향상시킨다.
 +
 +여러 스레드가 한 자원에 동시에 접근할 수 있기 때문에 이를 제어하여야 한다. 이 문서에서 그 방법에 대하여 설명한다.
 +
 +==== 공유 데이터를 보호하기 ====
 +public 필드((필드:​ 클래스 또는 구조체의 직접 액세스할 수 있는 데이터 멤버))은 여러 스레드에서 접근할 수 있다. public 필드에 대한 접근을 동기화하기 위해서는 필드보다 속성((프로퍼티(property)))를 이용하는 것이 좋다. 그리고 접근 제어를 위해 ReaderWriterLock 객체를 사용한다. 다음 코드에서 Number 프로퍼티의 구현을 잘 보자.
 +
 +<​code>​
 +using System;
 +using System.Threading;​
 +
 +namespace MultiThreadApplication
 +{
 + class Class1
 + {
 + private ReaderWriterLock rwl = new ReaderWriterLock();​
 + private long myNumber;
 + public long Number ​  // the Number property
 + {
 + get
 + {
 + //​Acquire a read lock on the resource.
 + rwl.AcquireReaderLock(Timeout.Infinite); ​               ​
 + try
 + {
 + Console.WriteLine("​Thread:​{0} starts getting the Number",​ Thread.CurrentThread.GetHashCode());​
 + Thread.Sleep(50);​
 + Console.WriteLine("​Thread:​{0} got the Number",​ Thread.CurrentThread.GetHashCode());​
 +
 + }
 + finally
 + {
 + //​Release the lock.
 + rwl.ReleaseReaderLock();​
 + }
 + return myNumber;
 + }
 + set
 + {
 + //​Acquire a write lock on the resource.
 + rwl.AcquireWriterLock(Timeout.Infinite);​
 + try
 + {
 + Console.WriteLine("​Thread:​ {0} start writing the Number",​ Thread.CurrentThread.GetHashCode());​
 + Thread.Sleep(50);​
 + myNumber = value;
 + Console.WriteLine("​Thread:​ {0} written the Number",​ Thread.CurrentThread.GetHashCode());​
 + }
 + finally
 + {
 + //​Release the lock.
 + rwl.ReleaseWriterLock();​
 + }
 + }
 + }
 +
 + [STAThread]
 + static void Main(string[] args)
 + {
 + Thread []threadArray = new Thread[20]; ​
 + int threadNum;
 +
 +
 + Class1 Myclass = new Class1();
 + ThreadStart myThreadStart = new ThreadStart(Myclass.AccessGlobalResource);​
 +
 + //Create 20 threads.
 + for( threadNum = 0; threadNum < 20; threadNum++)
 + {
 + threadArray[threadNum] = new Thread(myThreadStart);​
 + }
 +
 + //Start the threads.
 + for( threadNum = 0; threadNum < 20; threadNum++)
 + {   
 + threadArray[threadNum].Start();​
 + }
 +
 + //Wait until all the thread spawn out finish.
 + for( threadNum = 0; threadNum < 20; threadNum++)
 + threadArray[threadNum].Join();​
 +
 + Console.WriteLine("​All operations have completed. Press enter to exit"​);​
 + Console.ReadLine();​
 + }
 +
 + public void AccessGlobalResource()
 + {
 + Random rnd = new Random();
 + long theNumber;
 +
 + if (rnd.Next() % 2 != 0)
 + theNumber = Number;
 + else
 + {
 + theNumber = rnd.Next();
 + Number = theNumber;
 + }
 +
 + }
 + }
 +}
 +</​code>​
 +==== 클래스를 보호하기 ====
 +멀티스레딩에서는 여러 스레드에서 한 객체에 동시에 접근하려고 할 수 있다. 둘 이상의 스레드가 한 객체에 접근하려고 할 때, 그 객체를 수정하는 스레드가 있다면, 다른 스레드는 그 객체의 잘못된 값을 가지게 될 수 있다.((race condition))
 +
 +이런 상황((race condition))을 피하기 위해서는 lock을 사용해서 코드의 주요 부분((critical section))을 보호할 수 있다. lock은 C#에서 lock이라는 키워드로 자체 지원한다. 이 키워드를 이용하면 한 코드 내에 동시에 한 스레드만 진입할 수 있다. 다음 코드에서 TeacherName이라는 프로퍼티를 자세히 보자.
 +
 +<​code>​
 +using System;
 +using System.Threading;​
 +
 +namespace MultiThreadLockApplication
 +{
 + class Student
 + {
 + private static string myTeacherName = "​Bill";​
 + private string myName = "​Grace";​
 + private static object somePrivateStaticObject = new Object();
 +
 + public static string TeacherName
 + {
 + get
 + {
 + string theName;
 +
 + // Synchronize access to the shared member.
 + lock(somePrivateStaticObject)
 + {
 + Console.WriteLine("​Thread {0} starts to get the teacher'​s name",​Thread.CurrentThread.GetHashCode());​
 + theName = myTeacherName;​
 +
 + // Wait for 0.3 second.
 + Thread.Sleep(300);​
 + Console.WriteLine("​Thread {0} finished to get the teacher'​s name:​{1}.",​ Thread.CurrentThread.GetHashCode(),​ theName);
 + }
 + return theName;
 + }
 +
 + set
 + {
 + lock(somePrivateStaticObject)
 + {
 + Console.WriteLine("​Thread {0} starts to set the teacher'​s name.",​ Thread.CurrentThread.GetHashCode());​
 + myTeacherName = value;
 +
 + // Wait for 0.3 second.
 + Thread.Sleep(300);​
 + Console.WriteLine("​Thread {0} finished to set the teacher'​s name:​{1}.",​ Thread.CurrentThread.GetHashCode(),​ value);
 + }
 + }
 + }
 +
 + public string GetName()
 + {
 + string theName;
 + lock(this)
 + {
 + Console.WriteLine("​Thread {0} starts to get the student'​s name.",​ Thread.CurrentThread.GetHashCode());​
 + theName = myName;
 +
 + // Wait for 0.3 second.
 + Thread.Sleep(300);​
 + Console.WriteLine("​Thread {0} finished to get the student'​s name:​{1}",​ Thread.CurrentThread.GetHashCode(),​ theName);
 + return theName;
 + }
 + }
 +
 + public string SetName(string NewName)
 + {
 + string theOldName;
 + lock(this)
 + {
 + Console.WriteLine("​Thread {0} starts to set the student'​s name.",​ Thread.CurrentThread.GetHashCode());​
 + theOldName = myName;
 + myName = NewName;
 +
 + // Wait for 0.3 second.
 + Thread.Sleep(300);​
 + Console.WriteLine("​Thread {0} finished to set the student'​s name:​{1}",​ Thread.CurrentThread.GetHashCode(),​ NewName);
 + }
 + return theOldName;
 + }
 + }
 +
 + class Class1
 + {
 + public static int WorkItemNum = 20;
 + public static AutoResetEvent Done = new AutoResetEvent(false);​
 +
 + public static void AccessClassResource(object state)
 + {
 + Random rnd = new Random();
 + string theName;
 + Student AStudent = (Student) state;
 +
 + if( (rnd.Next() %2) != 0)
 + {
 + if( (rnd.Next() %2) != 0)
 + {
 + switch (rnd.Next() %3 )
 + {
 + case 0:
 + Student.TeacherName = "​Tom";​
 + break;​
 + case 1:
 + Student.TeacherName = "​Mike";​
 + break;​
 + case 2:
 + Student.TeacherName = "​John";​
 + break;​
 + }
 + }
 + else
 + {
 + theName = Student.TeacherName;​
 + }
 + }
 + else
 + {
 + if( (rnd.Next() %2) != 0)
 + {
 + switch (rnd.Next() %3 )
 + {
 + case 0:
 + AStudent.SetName("​Janet"​);​
 + break;​
 + case 1:
 + AStudent.SetName("​David"​);​
 + break;​
 + case 2:
 + AStudent.SetName("​Ben"​);​
 + break;​
 + }
 + }
 + else
 + {
 + theName = AStudent.GetName();​
 + }
 + }
 +
 + if(Interlocked.Decrement( ref WorkItemNum) == 0)
 + {
 + Done.Set();​
 + }
 + }
 +
 + [STAThread]
 + static void Main(string[] args)
 + {
 + int threadNum;
 + Student AStudent = new Student();
 +
 + // Queue up 20 work items in the ThreadPool.
 + for (threadNum = 0 ; threadNum <= WorkItemNum -1 ; threadNum++) ​
 + {
 + ThreadPool.QueueUserWorkItem(new WaitCallback(AccessClassResource),​AStudent);​
 + }
 +
 + Done.WaitOne();​
 + Console.WriteLine("​All operations have completed. Press enter to exit"​);​
 + Console.ReadLine();​
 + }
 + }
 +}
 +</​code>​
 +
 +~~LINKBACK~~
 +~~DISCUSSION~~