GSI

MultiThread 예제 분석

C++ 2008. 12. 30. 14:56 |

/*  file Main.cpp
 *
 *  This program is an adaptation of the code Rex Jaeschke showed in
 *  Listing 4 of his Nov 2005 C/C++ User's Journal article entitled
 *  "C++/CLI Threading: Part II".  I changed it from C++/CLI (managed)
 *  code to standard C++.
 *
 *  One hassle is the fact that C++ must employ a free (C) function
 *  or a static class member function as the thread entry function.
 *
 *  This program must be compiled with a multi-threaded C run-time
 *  (/MT for LIBCMT.LIB or /MTd for LIBCMTD.LIB).
 *
 *                                      John Kopplin  7/2006
 */

#include <stdio.h>
#include <windows.h>          // for HANDLE
#include <process.h>          // for _beginthread()

static bool interlocked = false;    // change this to fix the problem

const int maxCount = 100000000;
static LONG value = 0;              // under Windows Server 2003 you
                                    // could use LONGLONG here

unsigned __stdcall TMain(void* arg)
{
    if ( interlocked )
    {
      for ( int i = 1; i <= maxCount; i++ )
      {
        InterlockedIncrement(&value); // under Windows Server 2003 you
                                      // could use InterlockedIncrement64() here
      }
    }
    else
    {
      for ( int i = 1; i <= maxCount; i++ )
      {
        ++value;
      }
    }

    return 3;  // thread exit code
}


int main()
{
    // In this program we create 3 threads and request that their
    // entry-point-function be the TMain() function which is a
    // free (C) function and hence causes no problems for
    // _beginthreadex()

   
    HANDLE   hth1;
    unsigned  uiThread1ID;

CreateThread() 함수를 사용했을 때 스레드에서 Win32 함수만을 사용한다면 문제가 없지만 C 런타임 라이브러리 호출을 하려면 _beginthread()나 _beginthreadex() 를 사용해야 한다.

(원형) unsigned long _beginthread( void (*lpThreadEntryPoint)(void* lpArgList), unsigned uStackSize, void* lpArgList );

lpThreadEntryPoint 는 스레드가 시작된 함수의 주소인데, 이 함수에는 CreateThread()와 같이 한 개의 32비트 인자인 lpArgList가 있으며 이 값을 _beginthread()에 전달해야 한다. 이 함수는 void 타입으로 정의되어 CreateThread()와 조금 차이가 있다. 이 함수는 종료시까지 실행되는데 값을 반환하지는 않는다. uStackSize는 CreateThread()에서와 동일한 의미를 가지는데 스레드 스택의 베이스에서 커멧될 바이트 수이며, 이 값을 0으로 하면 윈도우는 부모 스레드에서 커멧된 것과 동일한 양의 메모리를 커멧힌다.

_beginthreadex() 는 CreateThread()와 완전히 같고 SECURITY_ATTRIBUTES 포인터와 시작 플래그(0이나 CREATE_SUSPENDED), 스레드 ID를 받는 포인터를 인자로 받는다. 일시 정지된 상태에서 스레드를 시작하거나 PostThreadMessage()에 스레드 ID를 사용하려면 _beginthreadex()를 사용해야 한다. _beginthread()
로 시작한 스레드만이 종료 코드를 설정할 수 있으므로 이때에도 필요하다.

    hth1 = (HANDLE)_beginthreadex( NULL,         // security
                                   0,            // stack size
                                   TMain,        // entry-point-function
                                   NULL,         // arg list
                                   CREATE_SUSPENDED,  // so we can later call ResumeThread()
                                   &uiThread1ID );

    if ( hth1 == 0 )
        printf("Failed to create thread 1\n");

    DWORD   dwExitCode;

 스레드가 수행되는 동안에 GetExitCodeThread()를 호출하면 종료 코드 STILL_ACTIVE를 반환한다.
 스레드에서 return 문이 등장하면 윈도우는 ExitThread()를 대신 호출해서 return 문에 전달된 값을 전달해 주는데, 이 때문에 입구 함수는 DWORD의 반환값을 갖는다.
 스레드를 시작하는 방법이 여러가지 있고 각각의 종료 함수도 다르므로 스레드에서는 단순히 return문을 사용하는 것이 좋다.

    GetExitCodeThread( hth1, &dwExitCode );  // should be STILL_ACTIVE = 0x00000103 = 259
    printf( "initial thread 1 exit code = %u\n", dwExitCode );

    HANDLE   hth2;
    unsigned  uiThread2ID;

    hth2 = (HANDLE)_beginthreadex( NULL,         // security
                                   0,            // stack size
                                   TMain,        // entry-point-function
                                   NULL,         // arg list
                                   CREATE_SUSPENDED,  // so we can later call ResumeThread()
                                   &uiThread2ID );

    if ( hth2 == 0 )
        printf("Failed to create thread 2\n");

    GetExitCodeThread( hth2, &dwExitCode );  // should be STILL_ACTIVE = 0x00000103 = 259
    printf( "initial thread 2 exit code = %u\n", dwExitCode );

    HANDLE   hth3;
    unsigned  uiThread3ID;

    hth3 = (HANDLE)_beginthreadex( NULL,         // security
                                   0,            // stack size
                                   TMain,        // entry-point-function
                                   NULL,         // arg list
                                   CREATE_SUSPENDED,  // so we can later call ResumeThread()
                                   &uiThread3ID );

    if ( hth3 == 0 )
        printf("Failed to create thread 3\n");

    GetExitCodeThread( hth3, &dwExitCode );  // should be STILL_ACTIVE = 0x00000103 = 259
    printf( "initial thread 3 exit code = %u\n", dwExitCode );

    // If we hadn't specified CREATE_SUSPENDED in the call to _beginthreadex()
    // we wouldn't now need to call ResumeThread().

인자 dwFlags는 스레드의 초기 스케쥴링을 결정하는데 0의 값으로 설정하면 윈도우는 스레드가 바로 수행되도록 스케쥴 하고, 0이 아닌 CREATE_SUSPENDED의 값은 핸들을 ResumeThread()에 전달할 때까지 스레드가 실행되지 않도록 한다.

    ResumeThread( hth1 );   // Jaeschke's   // t1->Start();
    ResumeThread( hth2 );   // Jaeschke's   // t2->Start();
    ResumeThread( hth3 );   // Jaeschke's   // t3->Start();

    // In C++ the process terminates when the primary thread exits
    // and when the process terminates all its threads are then terminated.
    // Hence if you comment out the following waits, the non-primary
    // threads will never get a chance to run.

    WaitForSingleObject( hth1, INFINITE );  // Jaeschke's t1->Join()
    WaitForSingleObject( hth2, INFINITE );  // Jaeschke's t2->Join()
    WaitForSingleObject( hth3, INFINITE );  // Jaeschke's t3->Join()

    GetExitCodeThread( hth1, &dwExitCode );
    printf( "thread 1 exited with code %u\n", dwExitCode );

    GetExitCodeThread( hth2, &dwExitCode );
    printf( "thread 2 exited with code %u\n", dwExitCode );

    GetExitCodeThread( hth3, &dwExitCode );
    printf( "thread 3 exited with code %u\n", dwExitCode );

    printf( "After %d operations, value = %d\n", 3 * maxCount, value );
                                         // under Windows Server 2003 you
                                         // could use %I64d

    // The handle returned by _beginthreadex() has to be closed
    // by the caller of _beginthreadex().

대부분의 스레드 관리 함수는 스레드 핸들을 필요로 하는데 그 중 몇몇만이 스레드 ID를 필요로 하고 가장 필요로 하는 함수는 PostThreadMessage()이다. 모든 Win32 핸들과 같이 스레드 핸들의 사용이 끝나면 CloseHandle() 함수를 사용하여 스레드 핸들을 닫아야 한다. 이것은 스레드 자체에는 아무런 영향을 미치지 않는다. 스레드 핸들을 닫는 것은 스레드를 끝내는(Kill) 것과는 다르며, 이렇게 하지 않을 경우 프로그램이 종료할 때까지 시스템 자원이 할당된 채로 남게 된다.

    CloseHandle( hth1 );
    CloseHandle( hth2 );
    CloseHandle( hth3 );

    printf("Primary thread terminating.\n");
}

Posted by gsi
: