CLOSE SEARCH

Thread in C#

멀티태스킹과 프로세스, 스레드

멀티태스킹은 윈도우가 지원하는 많은 기능 중 하나로 동시에 여러개의 프로그램들이 실행될 수 있도록 해준다. 이때, 실행중인 프로그램들은 프로세스로 관리된다. 프로세스가 실행된 후에는 운영체제에서 자동적으로 멀티태스킹을 지원해 준다. 하나의 프로세스는 기본적으로 하나의 스레드를 가지며 필요한 경우 다수의 스레드를 가질 수 있다. 스레드는 프로세스 내에서 실행되는 하나의 함수로 이해해도 좋다. 일반적으로 프로세스는 특정 시점에 하나의 함수(메인스레드)만을 수행하지만 스레드를 통해 다수의 함수를 동시에 실행할 수 있다.

동시에 여러개의 함수가 실행될 경우 이들의 실행순서와 동기화에 신경을 써야한다.

 

스레드의 상태

스레드는 아래와 같은 4가지 상태를 갖는다.

  • Unstarted : 스레드가 생성되었지만 아직 실행되기 전
  • Runnable : 스레드 생성 후 Start() 를 호출하면 Runnable 상태가 되며 CLR에 의해 큐(실행 큐) 형태로 관리된다. 그 후 시스템에 의해 우선순위에 따라 실행된다. 오직 실행큐에 있는 스레드만이 CPU 사용권한을 가진다. 
  • Suspended : 실행중인 스레드에서 Suspend()나 Sleep() 함수를 호출할 경우 해당 스레드는 실행 큐에서 대기 큐로 이동되며 CPU 사용 권한을 상실한다. Resume() 함수를 호출하거나 지정된 대기 시간이 지나면 다시 Runnable 상태가 될 수 있다. 
  • Stopped : 스레드가 실행을 마친 후에 리턴되거나 실행중 Abort()를 호출한 경우
스레드의 현재 상태를 알아보기 위해서는 Thread 클래스의 ThreadState 속성을 얻는다. 이 속성은 아래와 같은 값들을 가질 수 있다.
  • Unstarted
  • Running
  • WaitSleepJoin
  • SuspendRequest
  • Suspended
  • AbortRequested
  • Stopped
스레드의 상태를 조작하는데 사용되는 함수는 다음과 같다.
  • public void Start() : 스레드를 시작한다.
  • public bool Join(int millisecondsTimeout) : 스레드가 종료되기까지 대기한다.
  • public void Suspend() : 스레드를 대기상태로 변경한다.
  • public void Resume() : 스레드를 재시작한다. 
  • public static void Sleep(int millisecondsTimeout) : 지정된 시간만큼 대기한다. 
  • public void Abort() : 스레드를 종료한다. 


우선순위

우선순위은 Priority를 통해 설정한다. 우선순위가 높은 스레드는 우선순위가 낮은 스레드에 비해 더 많은 실행 기회를 갖게된다.

스레드의 우선순위는 Priority 속성을 통해 알 수 있으며, 아래와 같은 값을 가질 수 있다.

  • Lowest
  • BelowNormal
  • Normal (기본)
  • AboveNormal
  • Highest

 

스레드 생성

C#에서는 Thread 클래스를 통해 스레드와 관련된 모든 작업이 이루어진다.

21라인에서 스레드에서 실행할 함수를 ThreadStart 델리게이트를 통해 지정한다. 22라인에서 이 델리게이트를 통해 스레드를 생성하고 23라인에서 생성된 스레드를 시작한다.

 

스레드를 대기상태로(Sleep(), Suspend())

Sleep() 함수는 파라미터로 지정한 밀리세컨드만큼 스레드를 일시정지 시킨 후, 지정된 시간이 지나면 자동적으로 스레드를 다시 시작한다. 또한 이러한 기본적인 기능외에 다른 스레드에게 실행 기회를 주는 역할도 한다. 즉, 특정 스레드에서 Sleep() 함수가 호출되면, 나머지 다른 스레드들이 자신의 우선순위에 따라 실행 기회를 갖게 된다.

Suspend() 함수는 스레드를 대기상태로 보낸다. Sleep() 함수와 달리 자동적으로 실행상태가 되지 못하며, 반드시 Resume() 함수를 호출해 주어야 다시 실행상태가 될 수 있다.

 

동기화

동기화는 하나의 공유자원에 다수의 스레드가 접근할 때, 접근 순서와 공유자원의 무결성을 보장하기 위해 사용되는 기법이다.

아래의 코드에서 MyThread 클래스에 있는 count에 두개의 스레드가 동시에 접근을 하므로 이 count는 공유자원이 된다. 앞으로 이 코드를 토대로 여러가지 동기화 기법을 테스트 해보도록 하겠다.

코드의 실행 결과는 아래와 같다.

 

– lock을 통한 동기화

lock 키워드를 통해 동기화를 적용할 블럭을 작성해 주면 해당 위치로의 접근은 동기화된다. 이때 동기화가 적용된 블럭을 임계영역(Critical Section)이라고 부른다.
아래의 코드와 같이 공유자원인 count에 접근하는 부분을 lock 키워드 블록으로 묶어주면 이 부분은 한번에 하나의 스레드를 통해서만 접근되게 된다.

이렇게 변경한 코드의 실행경과를 위의 동기화 적용전의 결과와 비교해보면 First Thread와 Second Thread가 동시에 실행되는 것이 아니라 First Thread의 작업이 끝난 후에, 즉 First Thread의 lock 블록 안의 for문이 끝난 후에 Second Thread 의 for문이 실행되는 것을 볼 수 있다. 두 스레드 모두 동시에 시작되었지만 lock 블록에 의해서 First Thread가 실행되는 동안 Second Thread는 실행대기 중인 상태가 되는것이다.

 

– Monitor를 통한 동기화

Monitor을 lock 보다 조금 더 정교한 동기화를 제공한다. lock가 블록으로 임계영역을 지정하는 것이 비해 Monitor는 Exit() 함수와 End() 함수를 통해 임의의 지점을 동기화의 시작과 끝 지점으로 지정할 수 있다. 그리고 TryEntry()라는 함수를 지원하기 때문에 이를 적절히 사용할 경우 데드락의 위험성을 크게 줄일 수 있다는 장점이 있다. TryEntry() 함수는 객체의 잠금을 시도하고 실패한 경우 false를 리턴하고 다른 작업을 계속 진행할 수 있도록 해주는 역할을 한다.
아래의 코드는 Monitor를 통해 Test 함수를 동기화 시킨 예이다. Monitor을 사용할 때는 반드시 적절한 위치에서 Exit() 함수를 호출해서 동기화를 종료해야 한다. 그렇지 않으면 데드락이 발생할 수 있다.

 

– Interlocked를 통한 정수값의 증가/감소 동기화

C#에서는 정수형식의 공유자원에 대한 증가/감소를 동기화해 주는 Interlocked 클래스를 제공한다. 대표적인 두개의 정적 메소드(Inrement(), Decrement())를 제공하는데 공유자원의 참조를 파라미터로 넘겨주면 해당 공유자원을 동기화를 유지한 상태에서 증가/감소시킨다.

 

– Mutex(뮤텍스)를 통한 동기화

뮤텍스는 공유자원을 통해 동기화를 구현하는 것으로, Mutex라는 공유자원을 하나 생성하고 이 공유자원의 사용권한을 하나의 스레드에게만 허가한다. 스레드를 생성하고 뮤텍스를 요청하는 경우, 뮤텍스를 소유하고 있는 스레드가 없다면 요청한 스레드에 사용권한을 주고 스레드 실행을 시작한다. 그러나 뮤텍스가 이미 다른 스레드에 의해 소유중인 경우에는 뮤텍스가 다시 사용가능해질 때가지 스레드가 대기하게 된다. 뮤텍스를 요청할 때에는 WaitOne() 함수를 호출하고, 뮤텍스의 사용을 끝낸후에는 ReleaseMutex() 함수를 호출해서 다른 스레드가 뮤텍스의 사용권한을 가질 수 있도록 한다.

 

– ReaderWriteLock

ReaderWriteLock 클래스는 두개의 권한(Reader, Writer)을 가지고 동기화를 구현하는 클래스이다. 어느 한 스레드가 Reader 권한을 얻어서 실행중이라면 Writer 권한을 얻은 스레드는 실행중인 스레드가 종료되고 Reader권한을 해제하기 전까지 대기상태가 된다. Acquire<Reader/Writer>Lock() 함수를 통해 사용권한을 얻고, Release<Reader/Writer>Lock() 함수를 통해 사용권한을 해제한다. 그러므로 반드시 각 함수를 쌍으로 사용해야 한다. 단, 두 개의 동일한 권한을 얻어서 실행하게 되는 경우에는 동기화가 보장되지 않으므로 이에 대한 동기화는 따로 구현해 주어야 한다.

아래의 예제코드는 Reader/Writer 권한을 반복적으로 획득/해제 하면서 0(Reader 권한을 얻은 경우)과 1(Writer 권한을 얻은 경우)을 출력하도록 MyThread 클래스를 수정한 예이다.

asdf

Filed under: Programming Languages