닷넷프로그래밍정복 김상형저 가메출판사

쓰레드는 코드의 실행흐름이다.

쓰레드 생성 대리자

public delegate void ThreadStart()

public delegate void ParamiterizedThreadStart(Object obj)

둘 다 리턴 값은 없는 쓰레드는 생성후 독립적으로 실행되다가 작업이 완료되면 자동으로 파괴되는 것이므로 작업결과를 반환하지 않는다. 쓰레드로 전달한 작업내용이 없으면 인수가 없는 델리게이트 타입을 사용하고 전달한 인수가 있으면 인수가 있는 델리게이트 타입을 사용한다.

생성자는 쓰레드 객체를 만들기만 하고 실행을 즉시 하지 않는다. 쓰레드는 정지된 상태로 생성만 되며 이후 Start 메소드를 호출하여 시작한다. Suspend는 쓰레드 실행을 일시중지하고 Resume은 일시 중지된 쓰레드의 실행을 재개한다. Sleep 정적 메소드는 쓰레드의 실행을 일정시간 잠시 중지하는데 단위는 1/1000 초이다. 이 기간 동안 쓸데는 실행을 중지하고 다른 쓰레드를 위해 CPU를 양보한다. Sleep은 백그라운드 쓰레드의 실행속도를 제어하는 주요한 수단이다.

ex.

static void ThreadProc()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(i);
                Thread.Sleep(500);

            }
            Console.WriteLine("작업 쓰레드 종료");
        }


        static void Main(string[] args)
        {
            Thread t = new Thread(new ThreadStart(ThreadProc));

            t.Start();

            while (true)
            {
                ConsoleKeyInfo cki;
                cki = Console.ReadKey();
                if (cki.Key == ConsoleKey.A)
                {
                    Console.WriteLine("inserted A");
                }
                else
                {
                    Console.WriteLine("inserted {0}", cki.Key);

                }
                if (cki.Key == ConsoleKey.B)
                {
                    break;
                }

            }
            Console.WriteLine("주 쓰레드 종료");

}

}

카운트 증가와 키입력대기라는 두개의 작업을 병렬적으로 하는 예제이다. 작업쓰레드의 진입함수는 필요할 경우 쓰레드로부터 작업에 필요한 인수를 object타입으로 받을수 있다. 오브젝트 값은 진입함수의 인자로 object를 받는 것으로 수정하고 ThreadStart를ParameterizedThreadStart로 생성한 다음 Start시에 넣어주면 된다.

작업쓰레드는 생성후 자신의 소임을 다 하면 자동으로 종료된다. 통상 백그라운드 작업이란 영원히 실행되는 것이 아니라 주 쓰레드를 대신하여 시간이 걸리는 작업을 대신 하는 것이므로 진입함수가 종료되면 쓰레드도 끝나는 것이 자연스럽다. 만약 외부에서 쓰레드를 강제로 종료하고 싶다면 다음 메서드를 사용한다.

ex Thread.Abort() Thread.Join

Abort는 실행중인 쓰레드를 강제종료하고 필요한 정리작업을 한다. Join은 쓰레드가 완전히 종료되고 자원을 해제할때까지 대기하는 역할을 한다.

 

쓰레드의 프로퍼티

  • Name - 쓰래드의 이름을 지정하는 문자열. 지정하지 않으면 null이고 이름은 최초 딱 한번만 지정할수 있으며 실행 중에 변경하지는 못한다.
  • IsAlive - 쓰레드가 아직 살아있는지 확인
  • IsBackground - 배경쓰레드인지, 전경 쓰레드인지 조사 또는 지정한다. 주 쓰레드가 종료되었을때 모든 전경쓰레드가 종료되어야 응용프로그램이 무사히 종료할수 있다. 만약 전경 쓰레드 중 하나라도 실행중이라면 응용프로그램은 종료되지 않고 대기한다. 이에 비해 배경 쓰레드는 실행중이라도 응용프로그램 종료에는 별 영향을 주지 않는다. 디폴트는 false이고 전경쓰레드로 생성된다. 그래서 중요한 것은 전경쓰레드로, 배경이나 효과같이 덜 중요한 것은 배경쓰레드로 생성하면 좋다.
  • Prioirty - 쓰레드의 우선순위. 우선순위는 쓰레드가 CPU시간을 얼마나 많이 받아야 하는지를 지정하며 우선순위가 높을수록 더 많은 실행시간을 확보할수 있다. 우선순위는  ThreadPriority 열거형으로 표현하며 기본값은 Normal이다. 시스템이 쓰레드의 실행순서를 정밀하고 공평하게 제어하므로 가급적이면 우선순위는 조정하지 않는 것이 좋다. 하지만 백그라운드의 작업쓰레드보다 사용자의 입력을 받는 쓰레드는 더 중요한 일을 하므로 우선순위를 높여 반응성을 높이는 것이 바람직하다.
  • ThreadState - 쓰레드의 현재 상태를 조사한다. 읽기전용이다
  • CurrentThread - 현재 실행중인 쓰레드를 나타내는 정적 프로퍼티이다.

 

동기화

쓰레드는 프로세스에 속한 전역, 정적 자원이나 하드웨어 같은 본질적으로 전역인 자원을 공유하기때문에 하나의 자원을 두고 두개 의 쓰레드가 서로 싸울수 있다.이를 경쟁상태(Race Condition)하고 하고 경쟁을 해결하려면 한 쓰레드가 공유지원을 사용하는 동안 다른 쓰레드는 대기하도록 하는 동기화(synchronization)가 필요하다. 동기화를 하다보면 양쪽이 서로를 기다리는 교착상태(DeadLock)에 빠지기도 한다.

멀티쓰레드에서 동기화의 문제가 발생하는 근본적인 원인은 쓰레드의 실행순서가 비동기적이라는 것이다.

ex. 경쟁관계에 있는 쓰레드 예제

class Site
{
    public string name;
    public Site(string aname) { name = aname; }
}
 class ThreadExample1
    {
        private static Site site = new Site("www.winapi.co.kr");

        static void ThreadProc()
        {
            for (int i = 0; i <= 100; i++)
            {
                Console.SetCursorPosition(0, 0);
                Console.WriteLine("{0} 에서 {1}% 다운로드 중", site.name, i);
                Thread.Sleep(1000);
            }
        }

        static void DoSomething()
        {
            string old = site.name;
            site.name = "www.loseapi.co.kr";
            for (int i = 0; i <= 100; i += 10)
            {
                Console.SetCursorPosition(0, 1);
                Console.WriteLine("{0} 에서 {1}% 다운로드 중", site.name, i);
                Thread.Sleep(500);
            }
            site.name = old;
        }
        static void Main(string[] args)
        {
            Thread t = new Thread(new ThreadStart(ThreadProc));
            t.Start();
            Thread.Sleep(2000);
            DoSomething();
        }

이 예제는 사이트별로 따로 자원을 쓴다면 문제가 없는 코드이다. 하지만 실제로는 자원을 공유해야되서 동기화가 필요해진다. 동기화란 거창하게 생각할 필요 없이 한쪽에서 쓰는 동안 다른 쪽을 대기시키는 것이라고 생각 하면 된다.

ex.

static void ThreadProc()
        {
            for (int i = 0; i <= 100; i++)
            {
                lock (site)
                {

                    Console.SetCursorPosition(0, 0);
                    Console.WriteLine("{0} 에서 {1}% 다운로드 중", site.name, i);
                }
                Thread.Sleep(500);
            }
        }
        static void Main(string[] args)
        {
            Thread t = new Thread(new ThreadStart(ThreadProc));
            t.Start();
            Thread.Sleep(2000);
            lock (site)
            {

                DoSomething();
            }
        }

lock 블록에 잠그조가 하는 객체를 지정하면 이 객체를 액세스하는 쓰레드들은 한번에 하나씩 순서대로 실행된다. 주 쓰레드에서 DoSOmething을 호출하는 동안 lock블록으로 site객체를 잠근다. 이렇게 되면 다운로드 쓰레드는 lock 블록안으로 들어가지 못하고 잠시 대기한다. DoSomething은 이 때 site객체의 정보를 원하는 값으로 잠시 변경한 후 작업을 하고 완료후 원래대로 복구한다. 작업 완료후 site에 대한 잠금을 풀렴 대기하고 있던 쓰레드로 정상 동작을 계속할수 있다.

lock 블록에는 값타입의 변수는 지정할수 없으며 참조차입의 객체만 지정할수 있다. 이 때 lock에 잠그는 객체는 동기화가 필요한 블록에 대한 이롲ㅇ의 식별자 역할을 하므로 임의의 객체이기만 하면된다. site가 아니고 임의의 a객체를 선언하고 잠글수도 있지만 가급적 보호대상 자체를 잠그는 것이 의미가 명확하다.

 

C#에서 동기화를 하는 좀 더 일반적인 객체는 Monitor이다. Monitor는 Win32의 CriticalSection에 해당하며 동시에 두개의 블록이 같이 실행되지 못하도록 방지한다. 이 객체의Enter, Exit 메소드로 잠그고자 하는 객체와 범위를 지정하면 된다. 이 방법을 좀 더 쉽게 언어차원에서 제공하는 것이 lock이며 lock도 내부적으로 Monitor객체를 쓴다.

 

프로세스는 실행중인 응용프로그램을 의미한다. 하드 디스크에 실행파일 형태로 존재하는 프로그램이라는 개념과는 다른데 프로그램이 실행되어 메모리로 올라오면 비로소 프로세스가 된다. 프로세스는 실행에 필요한 메모리와 각종 핸들, 쓰레드를 담는 컨테이너이며 운영체제는 프로세스 단위로 자원을 할당하고 관리한다.

 

프로세스생성 - 두개 이상의 프로세스가 협조적으로 작업을 해야 할떄는 부모프로세스가 새로운 프로세스를 실행시킬수 있다.

새로 실행된 프로세스는 부모와는 논리적인 연관 없이 독립적으로 실행된다. 사실 프로세스끼리는 부모, 자식 관계가 성립되지 않으며  상호평등한 관계이되 실행을 시작시킨 프로세스를 관행상 부모프로세스라고 부를뿐이다. 프로세스끼리는 임의의 프로세스를 제어할수 있다. 단 이렇게 하기 위해서는 해당 프로세스에 대한 객체를 먼저 구해야 한다.

ex.

 Process Proc = Process.Start("notepad.exe");
 Thread.Sleep(2000);
  Proc.Kill();//그냥 죽이는 것

//CloseMainWindow();  정상종료를 종용하는 것

 

프로세스열거

ex

  Process[] Procs = Process.GetProcesses();
            foreach (Process p in Procs)
            {
                Console.WriteLine("ID = {0,5}, 이름 = {1}", p.Id, p.ProcessName);
            }

 

AppDomain

Win32환경은 프로세스들이 각각의 최대 4G 주소공간을 가지며 서로 격리되어 있다. 프로세스끼리 사용하는 메모리 공간이 완전히 분리되어 있기때문에 한 프로세스가 실수나 고의로 다른 프로세스에 악영향을 미칠수 없도록 되어 있고 그래서 최악의 경우라도 문제가 된 프로그램만 종료되며 시스템은 안전성을 유지할수 있다.

닷넷의 프로세스 구조도 win32와 마찬가지로 격리에 의해 안전성으 확보하는 방식이되 격리의 단위가 프로세스가 아니라 AppDomain(앱도매인)이다. 닷넷은 프로세스를 여러개의 앱도메인으로 분할하며 엡도메인은 닷넷에서 실행코드의 논리적인 경계가 된다. 즉, 한 AppDomain에서 문제가 발생해도 다른 앱도메인의 코드에는 영향을 줄수 없도록 되어 있다. 앱도메인은 안잔성을 위한 장치일뿐 아니라 보안 및 버전 관리, 어셈블리 로드/언로드를 위한 역할을 하기도 한다.

앱도메인에 의한 격리에도 프로세스 주소공간의 격리에서와 마찬가리조 통신의 불편함이 따르는데 앱도메인상의 응용프로그램끼리는 통상적인 방법으로 통신할수 없고 닷넷리모팅 통해서만 통신할수 있다. 프로토콜을

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 8. 28. 12:03