Creative Commons License

Microsoft .NET

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

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

.

닷넷!스킬업

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

[Thread] 공급자/소비자 문제로 알아보는 동기화 문제

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

지금까지 Thread 의 기본 개념과 Mulit Thread Programming 동작 방식 그리고 Thread Safety(쓰레드 안정성) 에 대한 사항들을 면밀히 살펴 보았다.

 

앞서 계속해서 언급했듯이 동시 다발적으로 실행되는 Thread 들이 같이 공유하여 사용하는 리소스가 있다면 그리고 이 리소스가 무결성이 보장되어야 한다면 (이를 Critical Section 이라 한다)  Thread 이 리소스에 대한 접근을 적당히 조절할 필요가 있다.

 

이번 글에서는 멀티 Thread 환경에서의 전형적인 상황 예시를 통해 Critical Section 의 접근을 제어하는 방법에 대해 알아보자.

 

 

* 공급자/소비자 문제

 

멀티 Thread 환경에서의 동일한 자원에 대한 동시 다발적인 공급과 소비 시나리오를 통해 Thread Safety 을 알아보자. 이 공급자/소비자 문제는 멀티 Thread 환경에서의 공유 자원에 대한 동기화 처리이슈로 소개되는  비교 상황이다.

시나리오 : 엄마가 밥상을 차리면 아이가 차린 밥을 먹는다.

           , 엄마가 밥을 차릴 동안에는 아이는 기다려야 한다.

           만일 기다리지 않고 밥상을 차리는 중간에 밥을 먹으려고 하면 당연 밥 못 먹는다.

           따라서 밥상은 엄마,아들이 접근하는 공유되는 리소스인 셈이다.

 



역할 : 엄마 ? 공급자 , 아들 ? 소비자 , 밥상 ? 공유 리소스

 

 

위 시나리오를 프로그래밍으로 구현해 보자. 다음은 클래스 다이어그램이다.




Mother : 엄마 클래스를 나타낸다. 엄마 객체는 밥상(Table) 객체를 가진다.
         Do
메서드는 밥상(Table) 에 밥(Meal) 을 차리는 작업을 한다.

Child: 아이 클래스를 나타낸다.

        Do 메서드는 차려놓은 밥상(Table) 을 먹어 치운다.

Table : 밥상 클래스를 나타낸다.

        PrepareMeal 메서드는 엄마에 의해 호출되는 밥을 차리는 메서드이다.

        TakeMeal 메서드는 아이에 의해 호출되는 밥을 먹는 메서드이다.

Meal : 밥상에 올려질 밥(고기)를 나타낸다.

 

 

Code>> Mother, Child, Meal 클래스와 Client(Main) 코드를 우선 살펴 보자.

a. Mother

public class Mother

{

        private Table table;

        public Mother(Table t)

{

               this.table = t;

}

        public void Do()

        {

               this.table.PrepareMeal(new Meal(500,"아침밥"));

               this.table.PrepareMeal(new Meal(300,"점심밥"));

               this.table.PrepareMeal(new Meal(400,"저녁밥"));                     

        }

}

 

b. Child

public class Child

{

        private Table table;

        public Child(Table t)

        {

               this.table = t;

        }

        public void Do()

        {

               for(int i=0;i<3;++i)

               {

                       this.table.TakeMeal();

               }                     

        }

}

 

c. Meal

public class Meal

{

        public int weight;

        public string name;

        public Meal(int weight,string name)

        {

               this.weight = weight;

               this.name = name;

        }

        public override string ToString()

        {

               return string.Format("{0}({1}g)",this.name,this.weight);

        }

}

 

d. Main

public class MainApp

{

        public static void Main()

        {

               Table aTable = new Table();

               //Mother,Child 객체는 Table 객체를 공유함.

               Mother aMother = new Mother(aTable);

               Child aChild = new Child(aTable);           

 

               //Mother Thread Table 객체에 밥상을 차리는 작업을 한다

               Thread MotherThread = new Thread(new ThreadStart(aMother.Do));

               //Child Thread Mother가 차려놓은 밥상을 먹어치운다.

               Thread ChildThread = new Thread(new ThreadStart(aChild.Do));                                             

               ChildThread.Start();         

               MotherThread.Start();

        }

}

 

 

1.1.            Thread Safety 를 고려하지 않은 프로그래밍

Thread 간 공유 자원에 대한 어떠한 접근 제어 장치(동기화)를 하지 않는다면, 엄마 객체와 아이 객체가 동시에 밥상에 접근하게 되면 밥상이 차려지기도 전에 밥을 먹거나 밥을 먹는 도중에 밥상을 차리는 우스운 결과가 발행할 것이다. 아래코드와 같이 Table 클래스를 만들었다고 가정하자.

public class Table

{

        private bool IsReady = false;

        private Meal meal;

        public void PrepareMeal(Meal m)

        {                                           

               this.meal = m;

               this.IsReady = true;

               Console.WriteLine("{0}이 준비되었습니다",this.meal);        

        }

        public void TakeMeal()

        {                                           

               Console.WriteLine("{0}을 먹었습니다",this.meal);

               this.meal = null;

               this.IsReady = false;                

        }

}

 

결국 Mother , Child 의 두 Thread Table 에 대고 Mother 은 밥상을 차리고

Child 는 밥을 먹는 Thread 들이 동시에 실행됨으로써 아래와 같은 결과가 발생한다.



결과화면을 보면 아침밥은 우연찮게(?) 잘 차리고 먹게 되었다.

그러나 엄마가 점심밥과 저녁밥을 차리기도 전에 아이가 밥을 먹으려고 했다는 것을 알 수 있다.

, 아이는 밥을 먹지 못했고 엄마는 생뚱 맞게 아이가 밥상에서 떠난 뒤에 밥을 차린 것이다.

결국 Critical Section 인 밥상에 대한 Thread 간 접근 제어 장치가 없기 때문에 위와 같은 현상이 발생하는 것이다.

물론 테스트를 해보면 그때그때 결과는 위와 조금씩 다를 수 있다. Thread 특성상 어떤 것이 먼저 실행될지 알 수 없기 때문이다. 그러나 결론적으로는 상황은 꼬이도록 되어 있는 것이다.

 

물론 IsReady 값을 Check 하여 다음과 같이 프로그래밍 할 수도 있다.

public void PrepareMeal(Meal m)

{                            

        if(IsReady == true)

        {

               return;                      

        }

        //이하 동일..                

}

public void TakeMeal()

{             

        if(IsReady != true)

        {

               return;                      

        }

        //이하 동일..         

}

 

그러나 결과 화면을 대체로 아래와 같다.

 

엄마가 밥상을 차리기도 전에 아이가 밥을 3끼다 먹었다는 현상인 것이다.

IsReady Check 하여 Return 하기 때문에 결과 화면에는 표시가 되지 않았던 것이다.

결국 이렇게 해도 공유 데이터의 무결성은 깨어진 것이다.

이 프로그램의 문제는 단순히 아이가 밥을 못 먹거나 먹고 있는 도중에 밥을 차리거나 하는 문제(?)일 뿐이지만 실제 환경에서의 중대한 응용프로그램에서의 공유데이터 무결성 문제는 보다 심각한 결과를 초래할 것이다.

 

 

1.2.            Thread Safety 를 고려한 프로그래밍

그럼 이번에는 위와 같은 현상이 발생하지 않도록 해보자.

, 반드시 엄마가 밥을 차린 후에 아이가 밥을 먹도록 하고 반대로 아이가 밥을 먹고 있으면 다 먹고 난 뒤 밥을 차리도록 처리한다.

 

Table Class 의 코드가 변경되었다.

 

public class Table

{

        private bool IsReady = false;

        private Meal meal;

        public void PrepareMeal(Meal m)

        {

               Monitor.Enter(this);

               if(IsReady == true)

               {

                       Monitor.Wait(this);                         

               }

               this.meal = m;

               this.IsReady = true;

               Console.WriteLine("{0}이 준비되었습니다",this.meal);

               Monitor.Pulse(this);

               Monitor.Exit(this); 

        }

        public void TakeMeal()

        {

               Monitor.Enter(this);

               if(IsReady != true)

               {

                       Monitor.Wait(this);                         

               }

               Console.WriteLine("{0}을 먹었습니다",this.meal);

               this.meal = null;

               this.IsReady = false;

               Monitor.Pulse(this);

               Monitor.Exit(this);

        }

}

 

Thread 의 동기화를 위해 닷넷 프레임워크가 제공하는 Monitor 클래스를 이용하여 접근 제어를 상황에 따라 처리한 것이다. 우선 결과 화면을 보자.

 

아주 정확하게 동작한다.

엄마가 밥을 차리고 난 후 아이가 밥을 먹는 것이다. Thread 의 우선 실행이 매 실행마다 다를지라도 결과는 항상 이와 같을 것이다.

 

닷넷에서 제공하는 Monitor 클래스를 이용하면 이처럼 멀티 Threading Programming 환경에서의 공유리소스 접근에 대한 동기화 방법을 제공해 준다. 이로써 Thread Safety 이 보장되는 것이다.

 

Monitor 클래스에 대한 자세한 사항은 다음 글에서 소개하도록 하겠다.

첨부파일에 이 글에 사용된 소스코드가 포함되어 있습니다.

∵Commented by 연습생 at 2008-07-15 오전 11:14:24  
다운로드가 안되요~~ ㅠㅜ
∵Commented by 박종명 at 2008-07-15 오전 11:36:11  
한국어로 인코딩 하시나 보네요.. 파일에 한글명 제거했습니다. 다시 다운로드 받으세요
이름
비밀번호
홈페이지
WJ <- 왼쪽의 문자를 오른쪽 박스에 똑같이 입력해 주세요