Creazione di thread

La funzione CreateThread crea un nuovo thread per un processo. Il thread di creazione deve specificare l'indirizzo iniziale del codice che deve essere eseguito dal nuovo thread. In genere, l'indirizzo iniziale è il nome di una funzione definita nel codice del programma (per altre informazioni, vedere ThreadProc). Questa funzione accetta un singolo parametro e restituisce un valore DWORD . Un processo può avere più thread contemporaneamente che eseguono la stessa funzione.

Di seguito è riportato un semplice esempio che illustra come creare un nuovo thread che esegue la funzione definita localmente, MyThreadFunction.

Il thread chiamante usa la funzione WaitForMultipleObjects per rendere persistenti fino a quando tutti i thread di lavoro non vengono terminati. Il thread chiamante si blocca mentre è in attesa; per continuare l'elaborazione, un thread chiamante usa WaitForSingleObject e attende che ogni thread di lavoro segnali l'oggetto wait. Si noti che se si vuole chiudere l'handle a un thread di lavoro prima che venga terminato, questo non termina il thread di lavoro. Tuttavia, l'handle non sarà disponibile per l'uso nelle chiamate di funzione successive.

#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);
}

La MyThreadFunction funzione evita l'uso della libreria di runtime C (CRT), poiché molte delle relative funzioni non sono thread-safe, in particolare se non si usa la libreria CRT multithreading. Se si vuole usare CRT in una ThreadProc funzione, usare invece la funzione _beginthreadex .

È rischioso passare l'indirizzo di una variabile locale se il thread di creazione viene chiuso prima del nuovo thread, perché il puntatore diventa non valido. In alternativa, passare un puntatore alla memoria allocata dinamicamente o fare in modo che il thread di creazione attenda il termine del nuovo thread. I dati possono anche essere passati dal thread di creazione al nuovo thread usando variabili globali. Con le variabili globali, in genere è necessario sincronizzare l'accesso da più thread. Per altre informazioni sulla sincronizzazione, vedere Sincronizzazione dell'esecuzione di più thread.

Il thread di creazione può usare gli argomenti per CreateThread per specificare quanto segue:

  • Attributi di sicurezza per l'handle per il nuovo thread. Questi attributi di sicurezza includono un flag di ereditarietà che determina se l'handle può essere ereditato dai processi figlio. Gli attributi di sicurezza includono anche un descrittore di sicurezza, che il sistema usa per eseguire controlli di accesso su tutti gli usi successivi dell'handle del thread prima che venga concesso l'accesso.
  • Dimensioni dello stack iniziale del nuovo thread. Lo stack del thread viene allocato automaticamente nello spazio di memoria del processo; il sistema aumenta lo stack in base alle esigenze e lo libera quando termina il thread. Per altre informazioni, vedere Dimensioni dello stack di thread.
  • Flag di creazione che consente di creare il thread in uno stato sospeso. Quando viene sospeso, il thread non viene eseguito fino a quando non viene chiamata la funzione ResumeThread .

È anche possibile creare un thread chiamando la funzione CreateRemoteThread . Questa funzione viene usata dai processi del debugger per creare un thread che viene eseguito nello spazio indirizzi del processo sottoposto a debug.

Terminazione di un thread