스레드 만들기

CreateThread 함수는 프로세스의 새 스레드를 만듭니다. 만드는 스레드는 새 스레드가 실행할 코드의 시작 주소를 지정해야 합니다. 일반적으로 시작 주소는 프로그램 코드에 정의된 함수의 이름입니다(자세한 내용은 ThreadProc 참조). 이 함수는 단일 매개 변수를 사용하고 DWORD 값을 반환합니다. 프로세스에는 동일한 함수를 동시에 실행하는 여러 스레드가 있을 수 있습니다.

다음은 로컬로 정의된 함수 MyThreadFunction를 실행하는 새 스레드를 만드는 방법을 보여 주는 간단한 예제입니다.

호출 스레드는 WaitForMultipleObjects 함수를 사용하여 모든 작업자 스레드가 종료될 때까지 유지됩니다. 대기하는 동안 호출 스레드가 차단됩니다. 처리를 계속하기 위해 호출 스레드는 WaitForSingleObject를 사용하고 각 작업자 스레드가 대기 개체에 신호를 보낼 때까지 기다립니다. 종료하기 전에 작업자 스레드에 대한 핸들을 닫는 경우에는 작업자 스레드가 종료되지 않습니다. 그러나 후속 함수 호출에서는 핸들을 사용할 수 없습니다.

#include <windows.h>
#include <tchar.h>
#include <strsafe.h>

#define MAX_THREADS 3
#define BUF_SIZE 255

DWORD WINAPI MyThreadFunction( LPVOID lpParam );
void ErrorHandler(LPCTSTR lpszFunction);

// Sample custom data structure for threads to use.
// This is passed by void pointer so it can be any data type
// that can be passed using a single void pointer (LPVOID).
typedef struct MyData {
    int val1;
    int val2;
} MYDATA, *PMYDATA;


int _tmain()
{
    PMYDATA pDataArray[MAX_THREADS];
    DWORD   dwThreadIdArray[MAX_THREADS];
    HANDLE  hThreadArray[MAX_THREADS]; 

    // Create MAX_THREADS worker threads.

    for( int i=0; i<MAX_THREADS; i++ )
    {
        // Allocate memory for thread data.

        pDataArray[i] = (PMYDATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                sizeof(MYDATA));

        if( pDataArray[i] == NULL )
        {
           // If the array allocation fails, the system is out of memory
           // so there is no point in trying to print an error message.
           // Just terminate execution.
            ExitProcess(2);
        }

        // Generate unique data for each thread to work with.

        pDataArray[i]->val1 = i;
        pDataArray[i]->val2 = i+100;

        // Create the thread to begin execution on its own.

        hThreadArray[i] = CreateThread( 
            NULL,                   // default security attributes
            0,                      // use default stack size  
            MyThreadFunction,       // thread function name
            pDataArray[i],          // argument to thread function 
            0,                      // use default creation flags 
            &dwThreadIdArray[i]);   // returns the thread identifier 


        // Check the return value for success.
        // If CreateThread fails, terminate execution. 
        // This will automatically clean up threads and memory. 

        if (hThreadArray[i] == NULL) 
        {
           ErrorHandler(TEXT("CreateThread"));
           ExitProcess(3);
        }
    } // End of main thread creation loop.

    // Wait until all threads have terminated.

    WaitForMultipleObjects(MAX_THREADS, hThreadArray, TRUE, INFINITE);

    // Close all thread handles and free memory allocations.

    for(int i=0; i<MAX_THREADS; i++)
    {
        CloseHandle(hThreadArray[i]);
        if(pDataArray[i] != NULL)
        {
            HeapFree(GetProcessHeap(), 0, pDataArray[i]);
            pDataArray[i] = NULL;    // Ensure address is not reused.
        }
    }

    return 0;
}


DWORD WINAPI MyThreadFunction( LPVOID lpParam ) 
{ 
    HANDLE hStdout;
    PMYDATA pDataArray;

    TCHAR msgBuf[BUF_SIZE];
    size_t cchStringSize;
    DWORD dwChars;

    // Make sure there is a console to receive output results. 

    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    if( hStdout == INVALID_HANDLE_VALUE )
        return 1;

    // Cast the parameter to the correct data type.
    // The pointer is known to be valid because 
    // it was checked for NULL before the thread was created.
 
    pDataArray = (PMYDATA)lpParam;

    // Print the parameter values using thread-safe functions.

    StringCchPrintf(msgBuf, BUF_SIZE, TEXT("Parameters = %d, %d\n"), 
        pDataArray->val1, pDataArray->val2); 
    StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
    WriteConsole(hStdout, msgBuf, (DWORD)cchStringSize, &dwChars, NULL);

    return 0; 
} 



void ErrorHandler(LPCTSTR lpszFunction) 
{ 
    // Retrieve the system error message for the last-error code.

    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    // Display the error message.

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
        (lstrlen((LPCTSTR) lpMsgBuf) + lstrlen((LPCTSTR) lpszFunction) + 40) * sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"), 
        lpszFunction, dw, lpMsgBuf); 
    MessageBox(NULL, (LPCTSTR) lpDisplayBuf, TEXT("Error"), MB_OK); 

    // Free error-handling buffer allocations.

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
}

MyThreadFunction 특히 다중 스레드 CRT를 사용하지 않는 경우 많은 함수가 스레드로부터 안전하지 않으므로 함수는 CRT(C 런타임 라이브러리)를 사용하지 않습니다. 함수에서 ThreadProc CRT를 사용하려면 대신 _beginthreadex 함수를 사용합니다.

포인터가 유효하지 않으므로 생성 스레드가 새 스레드 앞에서 종료되는 경우 로컬 변수의 주소를 전달하는 것은 위험합니다. 대신 동적으로 할당된 메모리에 포인터를 전달하거나 새 스레드가 종료되기를 기다리는 스레드를 만듭니다. 전역 변수를 사용하여 만드는 스레드에서 새 스레드로 데이터를 전달할 수도 있습니다. 전역 변수를 사용하면 일반적으로 여러 스레드로 액세스를 동기화해야 합니다. 동기화에 대한 자세한 내용은 여러 스레드 실행 동기화를 참조하세요.

만들기 스레드는 CreateThread 에 대한 인수를 사용하여 다음을 지정할 수 있습니다.

  • 새 스레드에 대한 핸들의 보안 특성입니다. 이러한 보안 특성에는 자식 프로세스에서 핸들을 상속할 수 있는지 여부를 결정하는 상속 플래그가 포함됩니다. 보안 특성에는 액세스 권한이 부여되기 전에 시스템에서 스레드 핸들의 모든 후속 사용에 대한 액세스 검사를 수행하는 데 사용하는 보안 설명자도 포함됩니다.
  • 새 스레드의 초기 스택 크기입니다. 스레드의 스택은 프로세스의 메모리 공간에 자동으로 할당됩니다. 시스템은 필요에 따라 스택을 증가시키고 스레드가 종료될 때 해제합니다. 자세한 내용은 스레드 스택 크기를 참조하세요.
  • 일시 중단된 상태에서 스레드를 만들 수 있는 만들기 플래그입니다. 일시 중단되면 ResumeThread 함수가 호출될 때까지 스레드가 실행되지 않습니다.

CreateRemoteThread 함수를 호출하여 스레드를 만들 수도 있습니다. 이 함수는 디버거 프로세스에서 디버깅 중인 프로세스의 주소 공간에서 실행되는 스레드를 만드는 데 사용됩니다.

스레드 종료