티스토리 뷰

Software

쓰레드 기본

2010. 3. 26. 15:50
가려운 곳을 전반적으로 다 긁어준 쓸 수 있는 좋은 자료네용~

출처: http://blog.naver.com/darkyeon/70014016214


I. 쓰레드 기본

 

  1. 개요

 

    1) 쓰레드 정의

       : 프로그램이 실마리를 풀어가듯 순서대로 진행되어 나가는 흐름.

       - 쓰레드는 커널 오브젝트의 일종이다.


    2) 커널 오브젝트

       - 운영체제(operating system) : H/W를 유기적으로 작동할 수 있도록, 혹은 다른 프로그램

         들이 컴퓨터의 하드웨어를 이용하여 잘 수행될 수 있도록 해주는 소프트웨어.

         . 운영체제가 하는 일

            ① 여러개 프로그램을 동시에 실행될 수 있도록 순서를 정하고, 하나의 프로그램이

              실행 될 수 있는 시간을 정한다. 즉, 프로세스를 관리한다.

            ② 여러개의 프로그램이 메모리등의 자원을 공유 할 수 있도록 한다.

            ③ 메모리, 하드디스크, CPU, 모니터 등의 하드웨어 장치로부터의 입출력을 관리

            ④ Application이 운영체제의 서비스나 리소스를 이용할 수 있도록 인터페이스를

              제공한다. (API)

            ⑤ GUI OS의 경우 각 Application이 사용하는 윈도우들을 관리한다.


       - 커널 : OS가 갖추어야 할 핵심기능을 말한다.

         . 커널의 기능

            ① 여러 프로그램이 경쟁적 커널 서비스 요청을 처리하는 인터럽트 처리

            ② 여러 프로그램에 대하여 커널 처리시간 할당을 위한 순서를 결정하는 스케쥴러

            ③ 여러 프로그램에게 각종 자원의 사용권한을 부여하는 관리자

            ④ 메모리, 디스크 내에서 운영체제의 주소공간을 관리하고 고르게 할당하는 메모리

              관리자


       - OS가 제공하는 OS 오브젝트

         . 커널 오브젝트

         . UI 오브젝트

         . GDI 오브젝트


       - 커널 오브젝트 종류 (시스템의 가장 핵심적인 요소들에 대한 오브젝트들)

         . Event : 이벤트

         . File : 파일 입출력 관련

         . File mapping : 공유메모리 관련

         . Heap : 힙메모리 관련

         . Mutex : 뮤텍스

         . Pipe : 파이프

         . Process : 프로세스

         . Semaphore : 세마포어

         . Socket : 소켓

         . Thread : 쓰레드

         . Timer : 타이머


       - UI 오브젝트의 종류

         . Accelerator table : 가속키

         . Caret : 캐럿

         . Cursor : 마우스 포인터

         . Desktop : 데스크탑

         . Hook : 훅

         . Icon : 아이콘

         . Menu : 메뉴

         . Window :  윈도우


       - GDI 오브젝트 : 그래픽 관련

         . Bitmap : 비트맵

         . Brush

         . DC

         . Font

         . Memory DC

         . Metafile

         . palette

         . Pen

         . Region


       - 커널 오브젝트의 특성

         ① 커널 내부 구조에 접근할 수 없다.

            => 대신 커널 오브젝트의 사용을 위한 API를 제공한다.

              (windows.h, afxwin.h를 인크루드)

         ② 커널 오브젝트에게는 그것을 제어할 수 있는 핸들을 제공한다. (포인터를 쓰지 않음)

            . 핸들은 32비트 정수값이다.

            . Win3.1 에서는 핸들 값이 전체 시스템에 대한 고유값이었으나 Win95 이후부터는

              프로세스가 독립적인 메모리 영역으로 분리되면서 핸들은 각 프로그램 내에서만

              유일한 값이 되었다. 따라서 프로세스가 가지는 인스턴스 값도 동일하게 지정되며

              각 프로세스는 프로세스 ID를 통해 관리 된다.

         ③ 시그널 혹은 넌시그널중 한가지의 상태를 가진다.

            => Wait 계열의 함수 사용시 의미가 있다.

         ④ 보안 속성을 갖는다. (별로 신경쓰지는 않는다)

         ⑤ 문자열 형태의 고유 이름을 가질 수 있다.

            . 핸들이 비록 고유값을 갖지만 그것은 같은 프로세스 내에서만 고유할 뿐이다.

              -> 다른 프로세스에서 같은 값의 핸들을 가질 수 있다는 것.

            . 전체 시스템을 통해 고유하기 위해서는 고유 이름을 지정해야 하며, 이 이름을

              통해 각기 다른 프로세스에서 하나의 오브젝트를 공유해서 사용할 수 있다.

              -> 프로세스 동기화에 이용

         ⑥ 사용계수를 체크 하여 소멸 여부를 판단한다.

         ⑦ 커널 오브젝트들은 커널오브젝트 핸들 테이블에 관리된다.


       - 커널 오브젝트의 생성과 소멸

         . 생성 : Create+ 계열 함수를 사용한다.

              성공하면 오브젝트에 대한 핸들을 리턴.

              실패시 INVALID_HANDLE_VALUE(-1값) 리턴.(NULL(0값)을 리턴하는 함수도 있음)

         . 소멸 :  CloseHandle(<핸들>)

   

         * MFC에서는 MFC 클래스 객체와 커널 객체가 별도로 존재 하기 때문에 두 객체를 붙이고

            떼는 별도의 작업이 필요하다.

            . 붙이기 : MFC객체에서 Create함수를 사용하면 커널객체가 생성된 후 MFC객체와

                 붙는다. 기존 커널 객체를 사용하려면 Attach 함수를 사용해야 한다.

            . 떼기 : MFC객체와 커널 객체를 떼어 낼때는 Detach함수를 사용하거나 Delete계열의

                 함수를 사용한다.


       - 기타 API

         . 오브젝트 복사 : DuplicateHandle 함수

         . 프로세스의 핸들 : GetCurrentProcess 함수



    3) 쓰레드 관련 용어들

       - 스케줄링 : 여러 프로세스나 쓰레드를 교대로 샐행하기 위해 순서를 정해 주는 것.

       - Context : 쓰레드가 스케줄링에 의해 잠시 중단 되었을 때, 중단될 당시의 데이터나 실행

            위치등의 정보를 말한다.

               => 중단된 작업 (스레드)를 진행시키기 위한 참고 데이터

       - Context Switching : 쓰레드가 교체되는 순간에 Context가 교환 되는 것을 의미.

         * Windows OS는 Context 내용을 저장하기 위해 CONTEXT 구조체를 사용.

            (레지스터의 내용이 들어가 있음)


    4) 쓰레드의 구성

       : 쓰레드는 프로세스에서 실행의 의미만 분리 시킨 것이다. 스레드를 실행시킬 때에는 이미

        존재하는 가상메모리 영역에서 스택영역을 별도로 할당하고 CPU를 사용할 수 있는 환경만

       있으면 된다.

       - 스레드 생성시 추가되는 항목 : 스택영역(기본 1M) + 레지스터

       - 프로세스의 static 영역과 힙영역은 공유 한다.

 

 

 

2. 쓰레드 프로그래밍

 

    1) Win32 API를 이용한 쓰레드 프로그래밍

       - 쓰레드 생성

       HANDLE CreateThread(

         LPSECURITY_ATTRIBUTES lpThreadAttributes,

         SIZE_T dwStackSize,

         LPTHREAD_START_ROUTINE lpStartAddress,

         LPVOID lpParameter,

         DWORD dwCreationFlags,

         LPDWORD lpThreadId

       );

 

    <인자 설명>

         lpThreadAttributes : 커널 오브젝트의 보안 속성. NULL을 주로 지정

         dwStackSize : 쓰레드의 스택(TLS:Thread Local Storage)크기를 지정.

                    NULL 지정은 기본값

         lpStartAddress : 커널 오브젝트가 실행할 함수에 대한 포인터

              여기에 들어갈 함수의 형식은 다음과 같이 정해져 있다.

              DWORD WINAPI 함수명 (VOID *인자)

         lpParameter : 쓰레드 함수에 전달할 데이터에 대한 포인터.

              넘겨 받은 후 원래의 타입으로 형변환 후 사용한다.

         dwCreationFlags : 시작시 스레드 상태에 대한 플래그

              . NULL : 커널 객체 생성과 동시에 쓰레드 함수 시작

              . CREATE_SUSPENDED : ResumeThread 호출전까지 대기상태에 있음.

        lpThreadId : 스레드 ID값을 넘겨 받는다. Win95계열에서는 NULL을 지정할 수 없다.

            쓰레드 ID는 시스템 전체를 통해 유일한 값이므로 이것을 많이 이용하게 된다.

            특히 쓰레드에 메시지를 전달하는 PostThreadMessage()함수는 쓰레드ID를 쓴다.

 

 


       - 쓰레드 종료

         ① 쓰레드 함수를 리턴 한다. => 자체 종료

         ② 쓰레드 함수 자체에서 ExitThread 함수를 호출한다.  => 자체 종료

         ③ 외부 쓰레드(메인 함수 포함)에서 Terminate 쓰레드를 호출한다.(비 권장)

         * 메인 쓰레드가 종료하면 서브 쓰레드들도 모두 종료한다.

 

 


       - 쓰레드의 상태 파악

         BOOL GetExitCodeThread (<쓰레드 핸들>, <종료코드를 담을 주소-DWORD>);

         종료코드 : STILL_ACTIVE (실행중)

                    ExitThread시 공통으로 들어가는 인자값

                    쓰레드 함수가 리턴한 값

                    쓰레드 종료 과정에서 발생한 예외값


       - Win32 API 쓰레드 프로그래밍시 주의 점

         . C런타임 라이브러리 함수를 혼용하면 안 된다. (쓰레드 Safe하지 않음)

         . Win32 API함수들만 이용하도록 한다.

 

 

 

    2) C런타임 라이브러리를 이용한 쓰레드 프로그래밍

       : C런타임 라이브러리는 원래 쓰레드 safe하지 않기 때문에 쓰레드 프로그래밍시에 사용을

         권하지 않는다. 그러나 VC++에서 런타임라이브러리 지정시 다중쓰레드 옵션을 주면

         사용할 수 있다.

       (process.h 인크루드)


       - 쓰레드의 생성

         uintptr_t _beginthread(

            void( __cdecl *start_address )( void * ),

            unsigned stack_size,

            void *arglist

         );

 

         <인자 설명>

            void( __cdecl *start_address )( void * ) : 쓰레드 함수 (형식에 주목)

            unsigned stack_size,     : 스택의 크기 (0지정하면 기본 크기)

            void *arglist              : 함수에 전달된 인자 시작 주소


         uintptr_t _beginthreadex(

            void *security,           // 보안 속성

            unsigned stack_size,     // 스택 크기

            unsigned ( __stdcall *start_address )( void * ),   // 쓰레드 함수(형식

                                                                                에 주목)

            void *arglist,             // 스레드 함수 인자

            unsigned initflag,         // 실행상태 지정

            unsigned *thrdaddr       // 쓰레드 ID

         );

 

 

 


       - 쓰레드 종료

         => EndThread함수나 return를 쓰지 않고 반드시 아래 함수를 사용해야 한다.

         void _endthread();

         void _endthreadex(<종료코드>);

 

       - 실행 함수 형식

            unsigned  __stdcall start_address ( void *p );

 

       - 특징

         : CreateThread 함수들을 쓰는 것보다 이들 함수를 사용하면 C런타임 라이브러리들을

         사용할 수 있으므로 편리하다. WinAPI 함수도 사용가능.

          . C++ 클래스의 메소드를 쓰레드로 실행 하려면 static 멤버이어야 한다.

            => 이 경우 객체의 일반멤버들의 사용이 필요하므로 자신에 대한 포인터를 넘기는

              것이 일반적이다.

         ex) // 생성자에서 아래 코드를 실행

            m_hThread = (void*)_beginthread(NULL,0,Func,this,0,&addr);

            static unsigend __stdcall Func(void *p)

            {     CMyThread *c = (CMyThread*) p; .....              }

 

 

 


    3) MFC를 이용한 쓰레드 프로그래밍

       : CWinThread 클래스를 사용한다.

       - 특징

         . CWinThread 클래스에는 자체적으로 메시지 펌프가 존재하기 때문에 이벤트를 발생시

            켜 메시지를 처리 할 수 있다.

         . CWinApp가 CWinThread를 상속받은 것이다.

         . CWinThread 용도

            ① 작업자 쓰레드 : 사용자의 입력이 필요없는 작업시

            ② UI 쓰레드 : 사용자의 입력을 받거나 이벤트를 받아 실행할 목적의 쓰레드


       가) Worker Thread만들기

         : 특정 함수를 이용해서 CWinThread를 생성하는 방법.

         - 쓰레드 생성

            CWinThread* AfxBeginThread(

              AFX_THREADPROC pfnThreadProc,

              LPVOID pParam,

              int nPriority = THREAD_PRIORITY_NORMAL,

              UINT nStackSize = 0,

              DWORD dwCreateFlags = 0,

              LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );


            <인자설명>

              . pfnThreadProc : 실행시킬 함수의 포인터

                 * 함수 형식

                   UINT 함수이름 (void* pParam)

              . LPVOID pParam : 함수에 전달할 인자 포인터

              . int nPriority = THREAD_PRIORITY_NORMAL : 쓰레드 우선 순위

             

우선순위 상수

내  용

THREAD_PRIORITY_TIME_CRITICAL

 우선순위 15

THREAD_PRIORITY_HIGHEST

 프로세스 우선 순위 + 2단계

THREAD_PRIORITY_ABOVE_NORMAL

 프로세스 우선 순위 + 1단계

THREAD_PRIORITY_NORMAL

 프로세스 우선 순위와 동일

THREAD_PRIORITY_BELOW_NORMAL

 프로세스 우선 순위 - 1단계

THREAD_PRIORITY_LOWEST

 프로세스 우선 순위 - 2단계

THREAD_PRIORITY_IDLE

 우선순위 1


              . UINT nStackSize = 0          : 스택 크기

              . DWORD dwCreateFlags = 0  : 실행 상태

              . LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL : 보안 속성


         - 쓰레드 종료

            . 쓰레드 자체내에서 종료(쓰레드 함수 끝부분에 넣는다.)

              void AfxThreadEnd(UINT 종료코드)

            . 외부 쓰레드에서 종료

              TerminateThread API함수 사용

 

 


       나) UI쓰레드

         : 함수 호출대신에 메시지를 받아 수행 하는 쓰레드 방식. CWinThread는 자체적으로

         메시지 큐를 갖게 된다. (Run 함수에 메시지 펌프 루틴이 들어가 있음). 따라서 어떤

         조건에 따라 다른 처리를 하는 쓰레드를 구성하고 싶은 경우 작업 쓰레드 보다는

         UI쓰레드가 편리하다.


         - 내부 실행순서

            ① 실행할 쓰레드 함수가 없으므로 곧바로 CWinThread의 InitInstance()함수를 실행

            ② Run() 호출. 메시지 펌프가 들어가 있음


         - 쓰레드 생성

         CWinThread* AfxBeginThread(

              CRuntimeClass* pThreadClass,         // UI 쓰레드 클래스 객체 지정

              int nPriority = THREAD_PRIORITY_NORMAL,     // 우선 순위

              UINT nStackSize = 0,                          // 스택 크기

              DWORD dwCreateFlags = 0,                   // 시작 형태

              LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );     // 보안


         ex) AfxBeginThread(RUNTIME_CLASS(MyThread), 0, 0, CREATE_SUSPEND,0);


         <주의> 생성후 바로 동작하지 않도록 네 번째 인자에 CREATE_SUSPEND를

                  지정한다.

                  시작은 ResumeThread() 함수로 한다.


            * UI 쓰레드 생성시 내부 동작

              ① CWinThread객체 생성

              ② 쓰레드 루틴 실행 (CreateObject)

              ③ 우선순위 결정(SetThreadPriority)

              ④ 쓰레드 시작(ResumeThread)


            cf. MFC _tWinMain에서 CWinThread역할

              => 생성된 메인쓰레드에 메시지 루프를 생성


       다) 작업쓰레드 vs UI쓰레드

         : 결국은 같은 클래스(CWinThead)에서 처리를 하는 데 유일하게 구분하는 기준은

         쓰레드 함수의 존재 여부(포인터의 존재)로 판단한다.

         CreateThread함수는 _beginethreadex를 호출하고 그 함수는 _AfxThreadEntry

            전역함수를 쓰레드 함수로 해서 실행 시킨다.

         . _AfxThreadEntry 함수 내용을 보면 m_pfn_ThreadProc멤버를 체크 하는 부분이 있는

            데 이것이 쓰레드 함수의 지정 여부를 판단하는 부분이다.

         . 만약 이 부분에서 m_pfnThreadProc멤버가 Null이 아니면 쓰레드 함수를 호출

         하고,  Null이면 CWinThread의 InitInstance() 실행후 곧바로 Run()함수를

         호출해서 메시지 루프를 돈다. 그리고 종료시에 ExitInstance() 호출.


       라) CWinThread클래스를 그대로 이용하는 방법

         ① CWinThread클래스를 상속 받는 새로운 클래스를 정의한다.

            ex) class MyThread : public CWinThread

         ② Run() 함수를 오버라이딩한다.

            => 쓰레드로 처리할 로직을 이곳에 정의 메시지 루프 대신에 처리 로직을 넣는다.

            ex) virtual int Run(void);

         ③ 외부 쓰레드(외부 함수)에서 쓰레드 클래스의 객체를 생성한다.

            ex) MyThread *my = new MyThread();

                my->m_bAutoDelete = TRUE;

         ④ 객체에 CreateThread함수를 호출 시켜서 쓰레드 로직을 실행 시킨다.

            =>  _AfxThreadEntry전역함수에 의해 Run함수가 실행된다.

            ex) my->CreateThread();         // Run()함수가 간접 호출됨.


       마) CWinThread 멤버들

         - 멤버 변수

            m_bAutoDelete : 쓰레드가 끝나면 m_hThread를 자동 해제 여부 지정.

              <주의>

                 Wait 계열 함수에 쓰레드 핸들을 지정하는 경우에는 FALSE로 해 두어야 한다.

                 TRUE로 해 둔 경우에는 쓰레드 클래스 객체를 delete하지 않는다.

            m_hThread    : 쓰레드 핸들

            m_nThreadID  : 쓰레드 ID

            m_pMainWnd  : Holds a pointer to the main window of the application.


         - 멤버 함수

            CreateThread : 쓰레드 시작 

            GetMainWnd : Retrieves a pointer to the main window for the thread.

            GetThreadPriority : Gets the priority of the current thread.

            SetThreadPriority : Sets the priority of the current thread.

            ResumeThread : Decrements the suspend count in a thread.

            SuspendThread : Increments the suspend count in a thread.

             ExitInstance : Override to clean up when your thread terminates.

            InitInstance  : Override to perform thread instance initialization.

            OnIdle : Override to perform thread-specific idle-time processing.

            PreTranslateMessage Filters messages before they are dispatched to the

               Windows CE TranslateMessage and DispatchMessage functions.

            Run :  Controlling method for threads with a message pump.

                 Override to customize the default message loop.





   

 

    바) Worker Thread 코딩 스타일

       ① 활용 1 => AfxBeginThread() 함수 사용

         - Thread 함수 정의

                UINT Sum(LPVOID pParam)

                {               ............        }

         - Thread 실행

                CWinThread* pThread = AfxBeginThread(Sum, &si);

                while(GetExitCodeThread(pThread->m_hThread, &dwExitCode))

                {

                        if (dwExitCode != STILL_ACTIVE)

                        {

                                break;

                        }

                        else

                        {

                                WriteString(TEXT("."));

                                Sleep(20);

                        }

                }


       ② 활용 2 => CWinThread 파생

         - CWinThread 파생 클래스 정의

                class CSumThread : public CWinThread

                {

                        DECLARE_DYNCREATE(CSumThread)


                //protected: new를 사용하여 객체 생성이 가능하도록 public으로 수정한다.

                public:

                        CSumThread();      // 동적 만들기에 사용되는 protected 생성자입니다.

                        virtual ~CSumThread();


                public:

                        virtual BOOL InitInstance();

                        virtual int ExitInstance();

                        virtual int Run(void);

                };


       - Run 함수 정의

                int CSumThread::Run(void)

                {

                        :

                        return 0;

                }

       - 쓰레드 생성 실행

                DWORD dwExitCode;


                // CSumThread의 생성자와 소멸자를 public으로 변경해야 한다.

                CSumThread* pSumThread = new CSumThread;


                // 클래스의 멤버 변수 설정

                pSumThread->m_bAutoDelete = TRUE;


                // 쓰레드 루틴 실행

                pSumThread->CreateThread(0 ,0);


                // 쓰레드가 종료할 때까지 0.02초 간격으로 "."을 화면에 찍는다.

                while(GetExitCodeThread(pSumThread->m_hThread, &dwExitCode))

                {

                        if (dwExitCode != STILL_ACTIVE)

                        {

                                break;

                        }

                        else

                        {

                                WriteString(TEXT("."));

                                Sleep(20);

                        }

                }

       

                // 자동 소멸되도록 CSumThread::m_bAutoDelete를 FALSE로

                // 하였기 때문에

                // 소멸하면 아니 된다.

                // delete pSumThread;


                return 0;

 

 

 

II. 쓰레드 제어 및 동기화

  1. 개요

    1) 동기화

       : 두가지 이상의 것을 서로 맞추는 것.

       - 할당된 시간 만큼 쓰레드가 수행된 후 Context 스위칭이 일어 난다.

       - 경쟁 조건

         : CPU를 사용하기 위해 여러개의 쓰레드가 질주하는 상황

         -> CPU를 차지하는 것은 특정 시점에서 하나 뿐이다.

       - 병행과 병렬

         . 병행(concurrent) : 여러 작업을 실행하지만 실제 실행은 순서적으로 하는 것.

         . 병렬(parallel) : 여러작업을 거의 동시에 실행 시키는 것.

       - 쓰레드 실행에 대한 원자성

         : 행 단위가 아니라 CPU 연산의 단위로 이루어진다.

         ex) value += 5; 

            => value 값의 레지스터 로드, 5를 더하는 연산, 연산 결과(레지스터값)을 value에

              다시 저장하는 3가지 CPU작업이 이루어진다.

       - 동기화 Object => 시그널/넌시그널 상태를 갖는다.

         ① 이벤트 , 뮤텍스, 세마포어, 대기 타이머 : 사용자가 시그널 상태를 지정할 수 있다.

            Object 생성시에도 지정가능.

         ② 프로세스, 쓰레드 : 생성시 넌시그널, 종료시 시그널 (임의 변경 불가)

         * wait 계역 함수와 같이 사용 된다.

            => 지정된 커널 Object가 시그널 상태가 되면 깨어나서 리턴 된다.

              즉, 실행을 중단 시켰다가 시그널을 받고 다음 행으로 실행을 이어간다.


 

  2. 쓰레드 제어

    1) 쓰레드의 Context 스위칭이 일어나는 조건

       ① 쓰레드에 할당된 시간이 모두 지난 경우 (time-out)

       ② 쓰레드가 IO작업으로 인해 Block되었을 때

       ③ 쓰레드가 Sleep하게 된 경우

       ④ 쓰레드가 Suspend 하게 된 경우

       ⑤ 쓰레드가 Wait 함수등에 의해 Wait하게 된 경우

       ⑥ 쓰레드가 종료하게 된 경우


    2)  쓰레드 상태

       ① 실행대기(RTL (Released Thread List)) : 쓰레드가 실행을 대기 하는 생태.

            => 자바의 Runnable 상태

       ② 대기 : Waiting 함수등에 의해 Thread가 대기 상태가 된 경우, Suspend한 경우,

              Sleep 한 경우, IO 작업으로 Block된 경우

            => 자바의 waiting 상태

       ③ 실행 :  실행중 => 자바의 Running 상태

       ④ 준비 : 실행대기 중이던 쓰레드가 실행에 들어가기 위해 준비함.


    3) 쓰레드 제어 함수

       - 지연 시키기 (외부함수에서 호출): SuspendThread(<쓰레드 핸들>)

       - 다시 시작 (외부함수에서 호출): ResumeThread(<쓰레드 핸들>)

       - 시그널을 받을 때 까지 대기 하기(쓰레드 함수에서 호출) : Wait 계열 함수들.

         WaitForSingleObject(<시그널을 발생 시킬 커널 객체>, <대기 시간-INFINITE>);

            => 특정 커널객체가 시그널을 발생 시킬때 까지 기다리는 함수. 대기 상태가 된다.

              대기 시간을 지정하면 대기시간 이후에 리턴 되는 데 이것을 리턴 값으로 타임아웃

              을 체크 할 수 있다.(WAIT_TIMEOUT)

              리턴값 : WAIT_ABANDONED : ReleaseMutex없이 쓰레도 종료

                      WAIT_OBJECT_0 : 기다리던 이벤트 발생

         WaitForMultipleObjects(<커널 객체 개수>,<커널객체들 포인터>,

              <모든 객체가 끝나길 기다릴 것인가 여부>, <기다리는 시간>);

            

커널 객체

시그널 조건

이벤트

 SetEvent() 호출 혹은 Overlapped IO에 의한 IO작업이 끝난 경우(구조체에 지정한 이벤트가 반응함)

뮤텍스

 뮤텍스를 소유한 쓰레드가 뮤텍스를 놓아 다른 쓰레드가 가질 수 있게 된 경우

세마포어

 쓰레드가 세마포어를 얻을 수 있게 된 경우(참조계수에 따름)

쓰레드, 프로세스

 종료시

입출력 객체

 입출력 완료시


       - 잠시 재우기(쓰레드 함수에서 호출) : Sleep()

 

 


  3. 쓰레드 동기화

    1) 크리티컬 섹션 (1인 화장실 쓰기)

       : 공유자원을 사용하는 로직 부분을 여러 쓰레드가 동시에 사용하지 못하도록 묶어 버림.

       - 공유자원을 사용하고 있어서 도중에 컨텍스트 스위칭이 일어나지 않도록 함.

       ① 크리티컬 섹션을 객체를 만들고 초기화하기

         CRITICAL_SECTION g_cs;

         ::InitializeCriticalSection(&g_cs);

       ② 임계영역 시작

         EnterCriticalSection(&g_cs);

       ③ 임계 영역 부분 코딩 (다른 쓰레드 동시 접근 불가)

       ④ 임계영역 해제

         LeaveCriticalSection(&g_cs);

       ⑤ 크리티컬 섹션 객체 해제

         DeleteCriticalSection(&g_cs);

       <주의> 크리티컬 섹션 객체는 커널 객체가 아니므로 전역변수로 만들어야 쓰레드 간에

         동기화를 제대로 구현 할 수 있다.

         시그널/넌시그널이 없고 문자열 이름이나 보안 속성들을 지정할 수 없다.


    2) 뮤텍스 (열쇠를 하나만 만들어 사용하기- 열쇠하나)

       : 뮤텍스를 가진 쓰레드만 자신의 로직을 실행할 수 있다.

       - 뮤텍스 객체는 유일성을 갖는다. (동시에 두개 이상의 쓰레드가 가질 수 없다)

       ① 뮤텍스 객체 생성

         HANDLE CreateMutex(

           LPSECURITY_ATTRIBUTES lpMutexAttributes, // 보안 속성. 주로 NULL

           BOOL bInitialOwner,               // 뮤텍스 생성쓰레드가 뮤텍스 소유할지 여부

           LPCTSTR lpName );      // 뮤텍스의 문자열 이름(시스템을 통해 고유해야)

         * 다른 쓰레드가 동일한 이름의 뮤텍스를 생성한 경우 API가 실패함.

            => GetLastError()로 확인 하면 ERROR_ALREAD_EXISTS를 리턴

            . 프로세스 간에도 사용가능


         cf. 기존 뮤텍스를 사용할 때

            HANDLE OpenMutex(

              DWORD dwDesiredAccess,     // 접근 속성

           BOOL bInheritHandle,      // 상속 옵션

           LPCTSTR lpName); // 뮤텍스 이름


       ② 동기화할 영역 시작부에 Wait계열 함수 위치

         => 뮤텍스를 쓰레드가 얻는 순간에 리턴 됨.


       ③ 동기화 영역이 끝나는 부분에 뮤텍스를 놓는 함수를 두어 다른 쓰레드가 뮤텍스를

         가질 수 있도록 한다.

         ReleaseMutext(g_hMutex);


       ④ 뮤텍스 해제

         CloseMutex(g_hMutex);


    3) 세마포어 (실행 가능한 명수에 제한 두기 - 입장권)

       : 세마포어를 가진 쓰레드만 계속 실행 할 수 있다.

       - 세마포어는 두개 이상의 쓰레드가 사용할 수 있다.

       - 참조 계수를 지정할 수 있다.


       ① 세마포어 생성

         HANDLE CreateSemaphore(

              LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,       // 보안 속성(NULL)

              LONG lInitialCount,     // 초기 카운트 (0이상의 값: 0은 세마포어가 없다는 것)

              LONG lMaximumCount, // 최대 세마포어 갯수

              LPCTSTR lpName );    // 세마포어 이름

         cf. 기존 세마포어 사용

            OpenSemaphore(<접근 속성>,<소유여부>,<이름>);

 

       ② 동기화할 영역 시작부에 Wait계열 함수 위치

         => 세마포어를 쓰레드가 얻는 순간에 리턴 됨.

            카운트가 0이 아닌동안에는 시그널로 되어 바로 리턴 된다. 그러면서 현 쓰레드가

            세마포어를 얻게 되고 카운트는 1 줄어들게 된다. (카운트 0이면 세마포어가 없는 것)


       ③ 동기화 영역이 끝나는 부분에 세마포어를 놓는 함수를 두어 다른 쓰레드가 세마포어를

         가질 수 있도록 한다. (카운터 1증가)

         ReleaseSemaphore(g_hS);


       ④ 세마포어 해제

         CloseHandle(g_hS);



    4) 이벤트 (실행 가능한지 신호로 알려 준다 - 신호등)

       : 이벤트 신호를 보고 TRUE상태면 실행 하고 아니면 실행을 대기 한다.

       - 가장 활용도가 높고 융통성 있는 동기화 방법이다.

       - 쓰레드를 원하는 시점에서 멈추거나 다시 실행 시킬 수 있다.

       - 다른 동기화 객체를 만들고자 할때나 사용할때에도 유용

       ① 생성

         HANDLE CreateEvent(

              LPSECURITY_ATTRIBUTES lpEventAttributes,     // 보안 속성(NULL)

              BOOL bManualReset,

              BOOL bInitialState,     // 초기 이벤트 시그널 상태

              LPCTSTR lpName);     // 이벤트 이름


            * bManualReset : 시그널 된 이벤트가 자동으로 넌 시그널이 되도록 지정여부

              FALSE : 자동 넌시그널,  TRUE : 시그널 유지


       ② 이벤트의 시그널 상태 변경

         - ResetEvent(hEvent) : 넌시그널로 바꿈.

         - SetEvent(hEvent) : 시그널로 바꿈

         - PulseEvent(hEvent)

            . 자동 리셋의 경우 : 시그널이 되기 위해 기다리던 쓰레드가 하나라도 실행이 재개

              되면 바로 넌 시그널이 됨(한개만 통과 하면 넌 시그널)

            . 수동 리셋의 경우 : 시그널 상태로 설정하고 대기하던 모든 쓰레드가 시작하면 곧바

              로 넌 시그널로 됨.(모두 통과 시킨후 넌시그널)


    5) Interlock 계열 함수 사용

       => 간단한 동기화 방법 (쓰레드 세이프하게 값을 증가 시킴)

       - long형 변수에 대해

         . ++ : InterlockedIncreament(&a)

         . -- : InterlockedDecrement(&a)

         . = : InterlockedExchange(&a, b)

         . 비교해서 같으면 대입

            : InterlockedCompareExchange(<변경변수>,<대입값>,<비교값>)


    6) 쓰레드간 통신

       - 메세지 전달 측

         PostThreadMessage(<쓰레드 ID>,<메세지 식별자>,wParam, lParam);

       - 메시지 수신 측 (CWinThread를 상속받은 것이어야)


    7) 쓰레드 풀

       : 쓰레드를 미리 로딩해 두었다가 필요할 때 기동시킨다.

       쓰레드 적정갯수 = CPU수 * 2 + a

댓글