Creative Commons License

Microsoft .NET

닷넷!시작하기
닷넷! Ver 2.0~
닷넷!스킬업
웹개발
윈폼개발
실용모듈개발
Tip & Tech
하루 한 문법

Microsoft .NET 개발자들을 위한 공간입니다. 기초강의에서 부터 고급 기술 정보 및 팁등을 다루도록 하겠습니다.

.

닷넷!스킬업

닷넷 기술을 조금 더 깊이 다루고자 합니다. 특정 주제를 정하지 않고 이슈 발생 시 마다 체계적으로 정리하여 공유하겠습니다. 이전 자료를 옮겨온 곳이기도 합니다.

[ExecuteUnit] Mulit Thread Programming

작성자 : 박종명
최초 작성일 : 2008-06-16 (월요일)
최종 수정일 : 2008-06-16 (월요일)
조회 수 : 3524

* Mulit Thread Programming

 

앞서 Process & Thread 개요 에서 기본적인 Thread 개념을 살펴 보았다.

지금부터는 멀티 쓰레드 프로그래밍에 대한 개념과 동작방식 그리고 주의점등을 알아보자

 

 

1. Mulit Thread ?

 

이전 아티클에서 Thread란 응용프로그램의 실행단위인 Process 내에 존재하는 실제 CPU 에 의해 실행되는 최소의 실행단위라고 하였다. 일반적으로 Process 는 하나의 주(Main) Thread 를 반드시 가진다.

순차적인 작업처리로 만족하는 또는 단순한 환경에서 사용되는 응용프로그램에서는 단일 Thread 만으로도 충분한 역할을 할 것이다.

예를 들어, 개인이 사용하는 일기장,개인 사업자가 사용하는 전표관리 툴 등을 들수 있겠다.

(물론 위 프로그램이 꼭 단일 Thread 로 만족한다는 보장은 없지만 일반적으로 충분하다)

그러나 다음과 같은 응용프로그램 환경을 생각해 보자

a.       주식시세를 실시간으로 갱신하면서 사용자의 이벤트를 받아서 특정 처리를 해야하는 환경.

b.       사용자와 상호 작용을 계속 하면서 백그라운드에서는 뭔가 계속 조사해야 하는 환경
(
위의 a와 동일한 상황인가 .. -.-;)

c.       대용량의 파일을 처리하는 동안 사용자는 다른 작업을 할 수 있어야 하는 환경.
(
이런.. 역시 동일환 상황인 듯…)

d.       동시(동일한 시간)에 여러 작업을 처리해야 하는 환경.

e.       채팅 프로그램처럼 네트워크 저 너머에서 넘어오는 Data 를 받으면서 사용자가 쓴 글을
또 다시 네트워크 저 너머로 보내야 하는 환경.

 

위 상황들은 말이 조금씩 다를 뿐 모두 하나의 요구조건을 만족해야 한다.

그것은 바로 하나의 응용프로그램에서 동시에 두 가지 이상의 처리를 지원해야 한다이다.

 

이렇듯 하나의 응용프로그램 내에서 처리되는 여러 가지 작업들을 동시에 처리될 수 있도록 구현하는 프로그래밍방식을 Mulit Thread Programming 이라 한다.

 

   

2. Mulit Thread 동작 방식

 

기본적으로 단 한 개의 CPU 를 가진 컴퓨터는 한번에 하나의 작업만 처리 할 수 있다.

, 한번에 하나의 Thread 만 수행할 수 있다는 말이다. 그러나 OS(운영체제)에서는 특정 기법을 사용하여 동시에 여러 작업을 처리할 수 있도록 지원한다.

 

2-1. Thread Scheduling

OS가 응용프로그램의 수행을 위해서 시간을 할당하는 것은 Process 가 아닌 Thread 이다.

실행중인 프로세스의 많은 Thread 에게 시간을 분할하여 할당하는 시분할 기법으로 Mulit Thread 를 지원하는데 이 시분할을 위한 핵심은 OS스케줄러(Scheduler) 이다. 스케줄러는 수행을 기다리는 많은 Thread 들에게 CPU를 할당하는 것이다. 다시 한번 말하지만, 단일 CPU 를 가진 컴퓨터에서는 한 순간에 실행될 수 있는 Thread 오직 하나이다. 하지만 스케줄러는 여러 Thread 들에게 CPU 시간을 나누어서 할당함으로써 사용자는 여러 가지 작업들이 동시에 이루어 지는 것처럼 느끼는 것이다. 스케줄러는 OS(운영체제)에서 지원하며 각각의 운영체제는 나름대로의 스케줄링 방시과 알고리즘을 가지고 있다.

다음은 Windows 운영체제의 스케줄링에 대한 특징이다.

a.      Priority Driven : 스케줄러에 의해 관리되는 Thread 들은 Priority 를 가지며 이 것은 여러
                           Thread
의 실행 우선권(우선순위)를 지정한다.

b.      Round-robin : 여러 Thread CPU 의 일정 클럭수 만큼 조금씩 실행하는 방식.
                      
, 여러 Thread 를 일정한 클럭만큼 나눠서 번갈아 가며 실행하는 방식이다.
                      
실제로 Windows 환경에서는 Round-robin withing a priority level 방식으로
                      
동작한다.(우선순위가 있는 라운드 로빈 방식)

c.      Quantum : 스케줄러는 Thread 를 수행시킬 때, CPU를 사용할 일정한 시간을 Thread에게
                
할당하는데 이것을 Quantum 이라 한다.

 

2-2. ‘시분할(Timeslicing)’ 방식

다음 그림은 이전 아티클에서 설명되었던 시분할방식의 흐름도 이다.



2-3. Ex> Round-robin

스케줄링 방식의 하나인 라운드로빈의 동작방식을 간단한 예를 들어보자


아래와 같은 Thread Quantum 이라 가정한다.

Quantum = 2 , Thread1 = 5 Clock , Thread2 = 3 Clock , Thread3 = 2 Colck

(Thread Clock은 이 Thread 를 실행완료 하기 위한 CPU 클럭 수 이다)

, 말로 표현하자면 CPU는 특정 Thread 를 한번 수행하기 위해 2 클럭만큼의 시간이 할당되고 Thread 들은 자신들의 실행을 완료하기 위해 적당한 클럭수를 가진다. 라운드 로빈 방식은 여러 Thread 를 번갈아 가며 특정 클럭수 만큼 수행한다. 아래와 같이..



위 그림은 우선순위(Priority 개념을 제외한 Round-robin 동작방식을 나타낸다.
 
 
 

3. Thread Safety (쓰레드 안정성)
 

Mulit Thread Programming 은 분명 많은 장점을 지니고 있으며 복잡한 현시대의 응용프로그램에서는 거의 필수에 가까운 프로그래밍 환경이라고도 할수 있다.그러나 장점이 있으면 언제나 단점도 있는 것이다.

단점이라 표현하기는 좀 뭣하지만 Mulit Thread 환경에서는 주의해야 할 것들이 많다.

일단, Thread Safety Mulit Thread 환경 내에서 정상적으로 동작하는 것을 말한다.

다시 말해 Mulit Thread 프로그램 구현시 Thread Safety 을 반드시 고려해서 개발해야 한다는 것이다.

지금부터 Thread Safety 를 위해 고려해야 할 사항들에 대해 알아 보자

 

 

3-1. Atomic Operation

Mulit Thread 환경에서는 수 많은 작업(Thread)들이 CPU에 번갈아 가며 할당되어 처리된다고 하였다.

결국 이렇게 처리되는 작업들이란 특정 코드 단위(함수)의 실행이라 볼 수 있는데 이를 Operation 이라 한다.

그리고 Atomic 이란 원자성을 가진이란 뜻으로 최소단위를 뜻하는데 Atomic Operation 이란 원자성을 가진 실행이라고 직역할 수 있다. 결국 특정 Operation 이 실행도중에는 다른 Operation 으로부터 간섭(개입)을 받지 않는 Operation 을 의미한다. Mulit Thread 에서는 각 Operation 들이 Atomic 하게 동작해야 할 경우가 많이 있다.

윈도우 스케줄러는 Mulit Thread 를 관리하기 위해 시분할기법을 사용하기 때문에 Operation 들이

Atomic Operation 이라 보장할 수 없다. 따라서 특정 Operation Atomic Operation 이 되기 위해서는 프로그래밍적으로 별도 처리를 해주어야 한다.

 

 

3-2. Race Conditions

Race Condition 이란 Mulit Thread 프로그램에서 처리 순서에 따라 발생하는 버그를 말한다.

, 여러 Thread 에서 처리하는 연산의 순서에 대해서 잘못 가정했을 경우 발생하며, Mulit Thread 프로그램에서 자주 발생하는 문제이다. 예를 들어, (Main) Thread 에서 파일을 열고 이 파일에 대한 핸들을 다른 여러 Thread 에서 사용한다고 가정했을 경우, 프로그램이 종료할 때에 주 Thread 가 파일을 닫았는데, 그 사이 다른 Thread 가 종료되지 않고, 그 파일의 핸들에 Operation 을 수행할 경우 생기는 문제이다.

 

이런 류의 버그는 버그 잠재성을 내포하고 있더라도 항상 버그가 발생하는 것이 아니기 때문에 버그상황을 재현하기가 매우 힘들며, 여러 Thread 에서 동시에 수행되기 때문에 디버깅 또한 매우 힘들 수 밖에 없다.

따라서 프로그램 설계 시 Race Conditino 이 일어날만한 경우를 미리 충분히 고려하는 것이 무엇보다 요구 된다.

 

 

3-3. Mutual Exclusion (상호 배제)

Mutual Exclusion 란 특정 Thread 가 공유 리소스(Critical Section)를 사용하고 있을 때 다른 Thread 가 그 리소스를 사용하지 못하게 제어하는 기법이다.

, 특정 Thread 실행 시 일정한 시간 동안 하나의 리소스(Critical Section)에 대해서 전권을 가지는 것을 말한다. OS(운영체제)는 이렇게 한 리소스에 대해서 특정 시간에 한 Thread 접근할 수 있도록 Mutex를 제공한다

 

Critical Section

- 여러 Thread 에 의해 공유 및 동시에 접근,조작 했을 때 문제가 될 수 있는 부분

 

3-3-1. Mutex

뮤텍스(Mutex, mutual exclusion, 상호 배제)동시 프로그래밍에서 공유 불가능한 자원의 동시 사용을 피하기 위해 사용되는 알고리즘이다. Critical Section 에 다수의 Thread 의 접근을 제어,관리 하는 Mutex 에는 다음과 같은 두 개의 Operation 이 있다.

a.      Acquire(취득,습득,소요) : 특정 Thread 는 공유 리소스(Critical Section) Mutex 를 소요하도록 요청한다. OS(운영체제)는 이 Thread 에게 소유권을 줄 수 있으면, Acquire 를 호출한 Thread 에게 Mutex에 대한 소유권을 넘긴다.만일, 이미 다른 Thread 가 소유권을 가진 상태라면 OS 는 소유권을 줄 수 있을 때 까지 요청한 Thread Blocking 시킨다.

b.      Release : 특정 Thread 가 소유한 공유 리소스와 관련된 Mutex 의 소유권을 OS(운영체제) 에게 돌려 준다. OS는 이 리소스에 대한 Acquire 호출 후 Blocking 되어 있던 Thread에게 소유권을 넘긴다.

c.      Code Sample

MutexForX.Acquire(); // 리소스 X 에 대한 소유권을 요청한다

X.f();               // Operation f() Atomic 하게 실행된다

X.g();              // Operation g() Atomic 하게 실행된다

MutexForX.Release(); // 리소스 X 에 대한 소유권을 반납한다

 

 

 

 

3-4. Semaphores

Semaphore 역시 Mutex 처럼 Mulit Thread 환경에서의 공유 리소스를 관리하는 도구 중 하나이다.

Mutex 와의 차이점은 공유리소스의 수 이다.

Mutex 는 한 순간에 하나의 공유 리소스에 하나의 Thread 만이 접근할 수 있는 반면, Semaphore 는 한 종류의 여러 리소스에 대한 Thread 접근 제어를 지원 하는 알고리즘 이다. Semaphore 는 다음과 같은 하나의 Field 와 두 개의 Operation 을 가진다.

a.      Count : 사용 가능한 공유 리소스 개수
        Semaphore
는 초기에 사용 가능한 리소스 수로 초기화 된다.

b.      Wait : Thread 는 공유 리소스를 사용하기 위해 그 리소스에 대한 Semaphore
      Wait
를 호출한다. 만일 Count > 0 이면 사용 가능한 리소스가 있기 때문에 Count
      1
감소 시키고 Thread 에게 리소스를 사용할 수 있도록 한다.
     
반면, Count <= 0 이면, 사용할 수 있는 리소스가 없기 때문에 Thread Blocking
     
시킨다.

c.      Release : Mutex Release 와 유사한데, 리소스에 대한 사용이 끝나면 Release를 호출
        
하는데 이 리소스 사용을 대기 하고 있는 다른 Thread 가 있다면 대기중인
         Thread
에게 리소스를 할당하고 그렇지 않으면, Semaphore Count 1 증가
        
시킨다

 

 

 

3-5. Starvation(기아 상태)

Windows 운영체제의 Mulit Threading Round-robin + Priority Level 방식으로 동작한다고 하였다.

, Thread 의 실행 우선순위를 부여 할 수 있는데 이 Priority 기반의 스케줄 환경에서는 우선순위가 높은 Thread CPU 를 많이 점유하여 우선순위가 낮은 Thread CPU 시간을 얻지 못하는 경우가 발생할 수 있다. 이런 상황을 Starvation 이라 한다. 즉 특정 Thread 가 필요로 하는 리소스에 대한 권한을 얻지 못하여 자신의 일을 하지 못하게 되는 경우이다. Windows 운영체제는 이런 기아 상태를 피하기 위한 기법을 이미 가지고 있지만, 이것이 확실한 해결책이 되진 않으므로, Mulit Thread Programming 시 기아상태가 발생하지 않도록 주의해야 한다

 

 

 

3-6. Priority Inversion (역 우선순위)

Starvation(기아 상태) 문제처럼 Priority (우선순위) 기반의 스케줄링 환경에서 발생하는 문제이다.

우선순위에 의해 Thread 의 실행 조건이 엉킴으로써 우선순위가 높은 Thread 가 수행되지 못하는 현상
Priorty Inverstion
이라 한다.

 

다음과 같은 상황을 가정해 보자.

Thread 1 : 우선순위 높음,

Thread 2 : 우선순위 보통

Thread 3 : 우선순위 낮음.

공유 리소스 A : Thread 1 , Thread3 가 사용하는 공유 리소스

 

이 경우 Thread 3가 실행되어 A(공유 리소스) 를 획득하고 있을 때(Thread 3의 실행도중),

Thread 1 이 리소스에 대한 소유권을 요청하면 Thread 3 A 를 반환할 때까지 Thread 1 Blocking 상태가 된다.

(만일 여기까지라면 Thread 3은 다시 실행을 마무리 하여 Thread 1 에게 A의 소유권이 넘어갈 것이므로 정상 동작이 가능할 것이다)

그러나 이때 Thread 2 Ready 상태가 되면 스케줄러는 Thread 3 대신 Thread 2 수행시키게 된다(우선 순위에 의해). 이렇게 되면 우선순위가 높은 Thread 1 는 수행할 수 없는 상황이 된다.

이 문제를 해결하기 위해 Windows OS 에서는 임시적으로 Priority Boosting 을 사용한다.

 

 

 

3-7. Deadlock(교착 상태)

Deadlock DB 에서 발생하는 그 Deadlock 과 동일한 개념이다.

다음의 상황을 가정해 보자

Thread 1 : 공유 리소스(A) 소유

Thread 2 : 공유 리소스(B) 소유

이 상태에서..

Thread 1 : B 요청

Thread 2 : A 요청

 

서로가 잡고 있는(소유하고 있는) 리소스에 대해 Cross 하게 요청을 하기 때문에 이 두 Thread 는 영원히 대기 상태에 빠지게 된다.



결국 Thread 1 Thread 2 B의 반납을 기다리게 되며 Thread 2 Thread 1 A 를 반납하기를 기다리게 되는 것이다. 이렇게 두 Thread 는 서로가 요청한 리소스를 잡고 있으면서 상대의 반납만을 무한정 기다리게 되는 것이다.

 

Deadlock 를 피하기 위해서는 공유 리소스에 대한 여러 Thread 들의 접근의 순서를 Sequence 하게 정해야 한다.

또한 특정 Thread 특정 리소스를 얻을 수 없는 상태가 되면 그 즉시 모든 리소스를 반납하고 다시 리소스를 요청하도록 해야 한다.

결론
각 OS(운영체제)는 멀티 쓰레드의 실행을 위한 나름의 스케줄링 알고리즘을 이용하여 동시에 여러 처리를 가능토록 지원하며
멀티 쓰레드 환경에서는 쓰레드의 안정적(Thread Safety)인 실행을 위해 많은 노력을 기울여야 한다.

결국 동시에 실행되는 많은 쓰레드들이 공유하는 리소스에 대한 관리가 무엇보다도 중요하며 또한 모든 쓰레드들의 실행이 보장되어야 한다.

1. 공유 리소스 무결성 보장
동시에 실행되는 많은 쓰레드들이 마구 접근하려는 공유 리소스는 쓰레드들의 동시 실행에 의해 그 자체의
변질 또는 의도하지 않은 변화를 가지지 않아야 한다. 이를 위해 특정 쓰레드의 실행은 Atomic Operation 이 되어야 하며 각 쓰레드들의 리소스 접근은 Mutual Exclusion(상호배제) 되어야 한다. 상호배제를 위한 도구로써,
Mutex 와 Semaphore 가 있다.

2. 공유 리소스에 대한 접근성 보장(Thread 의 실행보장)
리소스의 무결성을 보장하기 위해 한 쓰레드가 리소스에 접근할때 다른 쓰레드는 대기해야 한다.
이런 환경에서 주의해야 할 것이 바로 모든 쓰레드가 리소스에 접근할 수 있어야 한다는 것이다.
Starvation(기아상태) 나 Priority Inverstion(역 우선수위),Deadlock 같은 상황이 생길만한 소지를 없애야 한다.


이름
비밀번호
홈페이지
MO <- 왼쪽의 문자를 오른쪽 박스에 똑같이 입력해 주세요