상세 컨텐츠

본문 제목

C, C++ 스레드(Thread) 관련 WaitForSingleObject 함수

본문

반응형
728x170

 

=================================

=================================

=================================

 

 

출처: http://artisticbit.tistory.com/entry/WaitForSingleObject-%ED%95%A8%EC%88%98

 

 

DWORD WINAPI WaitForSingleObject(
  __in  HANDLE hHandle,
  __in  DWORD dwMilliseconds
);
 
위 함수는 커널 오브젝트의 상태 정보를 확인하는데 사용 됩니다.
즉, 해당 리소스의 커널 오브젝트가 Signaled인지 Non-Signaled인지 알 수 있습니다.
 
해당 리소스가 살아있으면 Non-Signaled (FALSE)이고 
해당 리소스가 종료되면 Signaled(TRUE)를 커널 오브젝트안에 가지고 있습니다.
 
첫번째 인자로는 해당 커널 오브젝트 핸들을 지정해 주고
두번째 인자는 Signaled 상태가 될 때까지 기다릴 수 있는 최대 시간을 밀리초 단위로 지정해 줍니다.
(INFINITE라고 인자를 주시면 해당 리소스가 종료 될 때까지 기다리고 있습니다. 
 즉, Signaled가 될 때까지 기다린다는 뜻입니다.)
 
 

MSDN

 
 
간단한 예제 소스로 결과를 확인해 봅시다.
 
===========================
예제 소스
===========================
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
 
int _tmain(int argc,TCHAR* args[])
{
STARTUPINFO start_info={0,};
PROCESS_INFORMATION process_info;
 
SetCurrentDirectory(_T("C:\\WINDOWS\\system32"));
TCHAR Command[] = _T("notepad.exe");
 
        /*메모장을 생성*/
CreateProcess(NULL,Command,NULL,NULL,TRUE,0,NULL,NULL,&start_info,&process_info);
       
       /*메모장이 종료 될때까지 멈춰있음*/
WaitForSingleObject(process_info.hProcess,INFINITE);

_tprintf(_T("Exit notepad. \n"));
 
return 0;
}
 
아마 실행해 보시면 메모장이 띄워지고 WaitForSingleObject 함수에서 멈춰 있는 것을 알 수 있습니다.
그리고 메모장을 끄시면 "Exit notepad."라는 문장이 출력되면서 예제소스 프로그램이 종료되는 것을 보실 수 있습니다.
 
======================================
쓰레드 동기화 시 사용되는 경우
======================================
쓰레드 동기화 시키는 방법에는 유저모드와 커널모드로 나눠지지만 WaitForSingleObject함수를 사용하는 경우는 
커널모드 쓰레드 동기화일 때만 사용합니다. 그에대한 2가지 방법으로  뮤텍스, 세마포어가 있습니다.
 
뮤텍스와 세마포어는 커널모드 쓰레드 동기화 방법 이므로 생성하면 커널 오브젝트 상태를 지닙니다.
 
1. 뮤텍스
임계영역에 들어가기 위해서는 무작정 들어가는 것이 아니라 키를 가지고 있어야만 들어갈 수 있는 것 입니다.
그럴 때 키는 얻는 함수로 사용되는 것이 WaitForSingleObject가 될 수 있습니다.
임계영역을 빠져나오면 키를 반환해야 하므로 그때 사용하는 함수는 ReleaseMutex가 되겠습니다.
간단하게 뮤텍스를 이용해 쓰레드들이 임계영역에 동시접근 하는 것을 막아보겠습니다.
 
상황을 간단하게 말하면
하나의 ATM기계가 있는데 10명의 사람들이 일을 보고 나오려고 하는 상황입니다.
각 사람들이 일보는 시간은 10~20초 사이입니다. ( 잘 안보이시면 클릭 )

 

위에 Working이라는 함수에서 WaitForSingleObject라는 함수를 사용하고 있는데 들어갈 수 있는 권한을 얻는 것입니다.
뮤텍스 커널 오브젝트도 마찬가지로 Non-Signed상태와 Signed상태가 있는데 해당 뮤텍스 커널 오브젝트가 Signed상태가 될때까지 기다리는 것입니다. Signed상태가 되면 기다리는 것들 중지하고 해당 쓰레드가 임계영역으로 들어가는 것입니다.
 
 

 

 
실행 결과 입니다.
 
동기화 방법 중에 뮤텍스 말고도 세마포어라는 것이 있는데 세마포어는 뮤텍스에 없는 카운트 기능이 있습니다.
즉, 임계영역에 동시에 들어 갈 수 있는 쓰레드의 개수를 정할 수 있는 기능이 있습니다.
하지만 WaitForSingleObject를 사용하는 방법은 같으므로 생략하도록 하겠습니다.
 



출처: http://artisticbit.tistory.com/entry/WaitForSingleObject-함수 [끄적끄적]

 

 

=================================

=================================

=================================

 

 

 

출처: http://winnerz.tistory.com/entry/WaitForSingleObject-%EC%82%AC%EC%9A%A9%EC%8B%9C-Thread-%EC%A2%85%EB%A3%8C%EB%90%98%EC%97%88%EB%8A%94%EC%A7%80-%EC%A3%BC%EC%9D%98%ED%95%A0-%EA%B2%83

 

 

Thread 의 종료를 기다리기 WaitForSingleObject 를 사용할때 주의할 사항이 있다.

 

[main]

SetEvent(exit_event);
DWORD waitResult;
waitResult = WaitForSingleObject(m_loadingThread, 20000);

[m_loadingThread]

while(true) {

    waitResult = WaitForSingleObject(exit_event, 20000);

    if (waitResult == WAIT_OBJECT_0) break;

    // Some Task that takes some time.

}

위의 코드를 보자. m_loadingThread 에는 exit_event 를 주면 Thread 가 종료되는 코드가 루프로 돌고 있고, main 에서는 exit_event 를 세팅하여 Thread 를 종료시키고자 한다. 하지만, 이 경우 문제점이 있다.

Thread 가 "Some Task" 를 실행중인 경우라면 WaitForSingleObject 문이 실행되는 시점에 Thread 가 살아있고, Thread 가 루프를 다시 돌아 break 를 만나 쓰레드가 종료되면 해피한 케이스로 모든 일이 종료될 것이다.

하지만, SetEvent 와 동시에 break 를 만난다면? SetEvent 와 동시에 쓰레드 루프에서 벗어나 쓰레드 함수가 종료될 것이고, WaitForSingleObject 는 이미 종료되어 버린 Thread 의 handle 을 무작정 기다리게 된다.

 

MSDN 에서는 "어떤 현상이 발생할지 알 수 없다." 고 경고하고 있다. (빨간색 부분)

(http://msdn.microsoft.com/en-us/library/ms687032(VS.85).aspx)

 

The WaitForSingleObject function returns when the specified object is in the signaled state or the time-out interval elapses.

To enter an alertable wait state, use the WaitForSingleObjectEx function. To wait for multiple objects, use the WaitForMultipleObjects.

 

DWORD WaitForSingleObject(   HANDLE hHandle,   DWORD dwMilliseconds ); 

Parameters

hHandle
[in] Handle to the object. For a list of the object types whose handles can be specified, see the following Remarks section.

If this handle is closed while the wait is still pending, the function's behavior is undefined.

The handle must have the SYNCHRONIZE access right. For more information, see Standard Access Rights.

dwMilliseconds
[in] Time-out interval, in milliseconds. The function returns if the interval elapses, even if the object's state is nonsignaled. If dwMilliseconds is zero, the function tests the object's state and returns immediately. If dwMilliseconds is INFINITE, the function's time-out interval never elapses.

실제 테스트 결과, 대체로 Hangup 이 걸리는 현상이 발생하였다.

사용시 Thread 가 종료된 상태로 WaitForSingleObject 를 만나는 일이 없도록 주의해야 한다.

 

Thread 종료시 Sleep 을 이용하는 방법도 있겠지만 Sleep 을 쓰는 방법은 가급적 지양하는 것이 좋을 것이므로

그래서 아래와 같이 로직을 보완하였다. thread_state 를 두어 Thread가 종료되었는지 체크하도록 한다.

 

[main]

SetEvent(exit_event);
DWORD waitResult;

if (thread_state != MY_STATE_EXIT)
     waitResult = WaitForSingleObject(m_loadingThread, 20000);

[m_loadingThread]

thread_state = MY_STATE_RUNNING;

while(true) {

    waitResult = WaitForSingleObject(exit_event, 20000);

    if (waitResult == WAIT_OBJECT_0) break;

    // Some Task that takes some time.

}

thread_state = MY_STATE_EXIT


출처 : http://blog.naver.com/hankawiii?Redirect=Log&logNo=90084211565

출처: http://winnerz.tistory.com/entry/WaitForSingleObject-사용시-Thread-종료되었는지-주의할-것 [조...좋은 개발자다!]

 

 

=================================

=================================

=================================

 

 

 

출처: http://teraphonia.tistory.com/19

 

다음은 소스코드.

//----------------------------------------------

#pragma comment(lib, "Ws2_32.lib")

 

#include <winsock2.h>

#include <Windows.h>

#include <process.h>

#include <stdio.h>

 

unsigned __stdcall handle(void *pData)

{

printf("thread 실행중\n");

Sleep(3000);

return 0;

}

 

void main()

{

HANDLE hThread;

DWORD dwThreadID;

DWORD dw;

 

hThread = (HANDLE)::_beginthreadex(nullptr, 0, handle, nullptr, 0, (unsigned*)&dwThreadID);

if(hThread == 0)

{

puts("_beginthreadex Error");

return;

}

 

dw = WaitForSingleObject(hThread, INFINITE); //

if(dw == WAIT_FAILED)

{

puts("thread error");

return;

}

 

printf("main 종료\n");

printf("thread 종료\n");

}

//--------------------------------------------

 

 

 

 

반응형

 

728x90

 

 

 

handle 함수에서 자식 스레드를 생성해서 돌리는데, 

만약 main이 먼저 종료된다면.. main은 부모 스레드이기 때문에 자식 스레드도 자연히 소멸된다. 

여기서 만약 WaitForSingleObject를 쓰지 않는다면, main이 먼저 종료되어 거의 실행 직후에 바로 종료가 될 것이다.

하지만 WaitForSingleObject를 사용하면 해당 스레드(hThread 핸들이 가리키는 스레드)가 2번째 인자로 들어오는 시간만큼 지연시켜 준다. 즉, 위 코드에서는 Sleep(3000)으로 인해 3초간 대기하게 되는 스레드가 생기는데, WaitForSingleObject의 2번째 인자를 2000으로 넣는다면, main스레드에서는 이순간부터 2초간 그 스레드가 Signal 상태가 되기를 기다린다(main 스레드는 정지된다). 2초가 지나면 다시 main스레드도 실행이 되기 시작한다. 2번째 인자를 INFINITE로 넣는다면, 해당 스레드가 완전히 종료될 때까지(Signal 상태가 될 때까지) main 스레드는 대기한다.

 

 

출처: http://teraphonia.tistory.com/19 [Teraphonia]

 

 

 

=================================

=================================

=================================

 

 

 

출처: http://hashs.tistory.com/208

 

 

 

모든 윈도우에서 사용되는 리소스는 커널오브젝트라는 것을 가진다.

이 커널 오브젝트는 2가지 상태값을 가지게 되는데, 시그널 상태 논시그널 상태이다.

CreateEvent함수는 개체를 시그널 or 논시그널로 핸들을 생성할 수 있다.

그리고 WaitForSingleObject함수로 CreateEvent에서 생성한 핸들이 시그널이 될때까지 기다리게 할 수 있다.

 

SetEvent는 핸들의 시그널 값을 논시그널에서 시그널로 바꿔주는 역할을 한다.

 

예제>

HANDLE WowEvent;

DWORD WINAPI ThreadProc(LPVOID lpParam)

{

Sleep(5000);

SetEvent( WowEvent ); // 3) 5초후 WowEvent 시그널 상태로 변경한다.

return 0;

}

 

int _tmain(int argc, _TCHAR* argv[])

{

DWORD ID = 0;

CreateThread(NULL, 0, ThreadProc, NULL, 0, &ID);

WowEvent = CreateEvent(0, FALSE, FALSE, 0); // 1) WowEvent를 논시그널 상태로 생성한다.

 

  WaitForSingleObject(WowEvent, INFINITE); // 2) WowEvent가 시그널 상태로 되기를 기다린다.

 

// 4) WaitForSingleObject를 빠져나온다.

 

return 0; 

}

 



출처: http://hashs.tistory.com/208 [V l i n k !]

 

 

=================================

=================================

=================================

 

 

 

출처: http://adolys.tistory.com/entry/%EC%8A%A4%EB%A0%88%EB%93%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8Thread-Event

 

 

* 이벤트는 

특정 사건의 발생을 다른 스레드에 알리는 경우

에 주로 사용.

 

 

* 이벤트를 이용한 스레드 동기화 과정

- 이벤트 A 비신호 상태로 생성 → 하나의 스레드가 작업을 진행 → 나머지 스레드는 이벤트 A가 신호 상태가 될때까지 대기

→ 작업을 진행하던 스레드가 완료되면 이벤트 A를 신호 상태로 변경 → 나머지 스레드 작업 시작

 

* 이벤트는 신호상태일때 접근 가능, 비신호 상태일때는 접근 불가능

 

 

* 이벤트 생성함수

CreateEvent( SECURITY_ATTRIBUTES, (대부분 

NULL

로 사용)

                         bMaunalReset,               (

TRUE이면 수동리셋

FALSE이면 자동리셋 

이벤트 생성)

                         bInitialState,                   (

TRUE이면 신호 상태

FALSE이면 비신호 상태

로 시작)

                         lpName                          (이벤트를 서로 다른 프로세스에 속한 스레드가 사용할 수 있도록

                                                              이름을 줄 수 있다. NULL을 사용하면 이름없는 이벤트 생성)

)

 

* 자동리셋 이벤트

- 이벤트를 신호상태로 바꾸면 기다리는 스레드 중 하나만 깨운후 자동으로 비신호 상태가 된다.

  따라서, 자동리셋 이벤트에 대해서는 

ResetEvent()함수를 사용할 필요가 없다.

 

* 수동리셋 이벤트

- 이벤트를 신호상태로 바꾸면 계속 신호상태를 유지하므로 결과적으로 대기중인 스레드를 모두 깨우게 된다.

  이 경우 비신호 상태로 바꾸려면 ResetEvent()함수를 사용하여야 한다.

 

* 이벤트 상태변경 함수

- BOOL SetEvent( 이벤트 핸들 )         

// 비신호 → 신호

- BOOL 

ResetEvent

( 이벤트 핸들)      

// 신호 → 비신호

 

<이벤트 예제 코드>

  - 하나의 스레드가 버퍼에 데이터를 쓰고, 나머지 스레드는 버퍼를 출력하는 예제

 

#include <windows.h>
#include <stdio.h>

 

#define BUFSIZE 16

 

HANDLE hReadEvent;
HANDLE hWriteEvent;
char buf[BUFSIZE];

 

DWORD WINAPI WriteThread(LPVOID arg)
{
      DWORD retval;
 
      for(int k=0; k<10; k++){
            retval = 

WaitForSingleObject(hReadEvent, INFINITE)

;     

// 읽기 완료를 기다림(Read이벤트 끝날때까지 대기)


            if(retval == WAIT_FAILED) break;

 

            // 공유 버퍼에 데이터 쓰기
             for(int i=0; i<BUFSIZE; i++)
                  buf[i] = 3;

           

SetEvent(hWriteEvent);

                                              

// 쓰기 완료를 알림(Write이벤트 신호 상태로 변경)


      }
      CloseHandle(hWriteEvent);                                            

// 이벤트 제거


      return 0;
}

 

DWORD WINAPI ReadThread(LPVOID arg)
{
      DWORD retval;
 
      while(1){
            retval = 

WaitForSingleObject(hWriteEvent, INFINITE)

;     

// 쓰기 완료를 기다림(Write이벤트 끝날때까지 대기)

            if(retval == WAIT_FAILED) break;


            

// 읽은 데이터를 출력


            printf("Thread %d:\t", GetCurrentThreadId());
            for(int i=0; i<BUFSIZE; i++)
                  printf("%d ", buf[i]);
            printf("\n");


            

// 버퍼를 0으로 초기화


            ZeroMemory(buf, sizeof(buf));                                   

// WriteThread가 실행 안되면 출력값은 '0' 이다.

            SetEvent(hReadEvent);                                            

 // 읽기 완료를 알림(Read이벤트 신호 상태로 변경)

      }
      return 0;
}

 

int main(void)
{
      

// 이벤트 생성      // Read와 Write 둘다 자동 리셋 이벤트로 생성      // 대기중인 스레드중 하나의 스레드만 이벤트 제어권을 넘겨받고 자동으로 비신호 상태가 된다.

      

hReadEvent = CreateEvent(NULL, FALSE, TRUE, NULL);


      if(hReadEvent == NULL) return -1;
      

hWriteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

      if(hWriteEvent == NULL) return -1;
 
      

// 세 개의 스레드 생성


      HANDLE hThread[3];
      DWORD ThreadId[3];
      hThread[0] = CreateThread(NULL, 0, WriteThread, NULL, 0, &ThreadId[0]);
      hThread[1] = CreateThread(NULL, 0, ReadThread, NULL, 0, &ThreadId[1]);
      hThread[2] = CreateThread(NULL, 0, ReadThread, NULL, 0, &ThreadId[2]);
 
      

// 스레드 종료 대기

      WaitForMultipleObjects(3, hThread, TRUE, INFINITE);


      

// 이벤트 제거

      CloseHandle(hReadEvent);
      printf("모든 작업을 완료했습니다.\n");
      return 0;
}

 

=> Write이벤트와  Read이벤트가 서로 동기화를 하고있고, 같은 Read이벤트를 제어하는 1번과 2번 스레드가 한번 더 동기화를 한다.

 

=> 

Read이벤트는 신호 상태

로 

Write이벤트는 비신호 상태

로 시작.

    Write이벤트가 비신호 상태로 시작하였기 때문에 Read스레드에서 Write이벤트가 끝나기 전까지 무한정 대기.

 

=> 

Write스레드의 루프가 한번 돌면 Write이벤트가 신호 상태가 되며 대기중이던 Read스레드의 코드가 실행.

    

Read이벤트는 자동 리셋 이벤트로 제어권이 1스레드에 넘어갔을때 자동으로 비신호 상태가 됨.

    

1번스레드→2번스레드 순서로 코드를 실행후 Read이벤트가 신호 상태가 되면서 다시 Write스레드의 루프가 돌아가는 방식

=> 

Write스레드에서 동기화를 시키지 않았다면

. Write이벤트는 비신호 상태로 시작하였으므로, 

Read스레드에서 무한정대기

.

 

=> 

Read스레드에서 동기화를 시키지 않았다면

. SetEvent로 신호 상태로 전환해 주지 않으면 1번스레드에 있는 제어권이

    2번스레드로 넘어가지 않으며, 또한 신호 상태가 되지 않으므로 Write스레드에서도 무한정 대기.

    → 

1번스레드에 있는 코드가 한번 실행되고 무한 대기 상태에 빠짐

 



출처: http://adolys.tistory.com/entry/스레드-이벤트Thread-Event []

 

 

=================================

=================================

=================================

 

 

반응형
그리드형


관련글 더보기

댓글 영역