Нарушение прав доступа при прохождении по массиву и многопоточности

0

Я играл с Threading в Windows и составил этот пример, он должен добавить все позиции массива 128 Мб. Я создаю x потоков для вычисления суммы, поэтому я делю массив в x-гранях и каждый поток вычисляет одно из этих элементов. Все работает хорошо, пока я не попытаюсь создать более 64 потоков. Например, если я создаю 65 потоков, я получаю нарушение прав доступа в моей функции добавления. Я предполагаю, что это массив из связанного, я просто не могу понять, почему после 64 потоков я получаю эту ошибку.

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

#define MAX_ARRAY_SIZE 128 * 1024 * 1024
#define MAX_THREADS_BUFFER 512

DWORD dwSampleData[MAX_ARRAY_SIZE];
DWORD dwTotal;

DWORD WINAPI DoWork(LPVOID lpParam);
void ErrorHandler(LPTSTR lpszFunction);
VOID InitializedwSampleData();
VOID CreateThreadsAndDoWork(DWORD MaxThreads, DWORD dwPrintIntermediateResults);

typedef struct _THREAD_ARGS{
    DWORD * pdwSampleData;
    DWORD dwOffset;
    DWORD dwSize;
    DWORD dwPrintIntermediateResults;
}THREAD_ARGS, *PTHREAD_ARGS;

DWORD _tmain()
{
    InitializedwSampleData();
    CreateThreadsAndDoWork(1, FALSE);
    CreateThreadsAndDoWork(2, FALSE);
    CreateThreadsAndDoWork(4, FALSE);
    CreateThreadsAndDoWork(8, FALSE);
    CreateThreadsAndDoWork(16, FALSE);
    CreateThreadsAndDoWork(32, FALSE);
    CreateThreadsAndDoWork(64, FALSE);
    CreateThreadsAndDoWork(128, FALSE); // <----------- More than 64 threads

    printf("Press any key to finish");
    getchar();
    return 0;
}

VOID InitializedwSampleData(){
    DWORD i;

    for (i = 0; i < MAX_ARRAY_SIZE; i++){
        dwSampleData[i] = 1;
    }
}

VOID CreateThreadsAndDoWork(DWORD MaxThreads, DWORD dwPrintIntermediateResults){
    PTHREAD_ARGS pDataArray[MAX_THREADS_BUFFER];
    DWORD   dwThreadIdArray[MAX_THREADS_BUFFER];
    HANDLE  hThreadArray[MAX_THREADS_BUFFER];
    DWORD BeginTickCount;

    // Reset dwTotal;
    dwTotal = 0;

    // Get Initial Tick Count
    BeginTickCount = GetTickCount();

    // Create MAX_THREADS worker threads.
    for (DWORD i = 0; i < MaxThreads; i++)
    {
        // Allocate memory for thread data.
        pDataArray[i] = (PTHREAD_ARGS)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
            sizeof(THREAD_ARGS));

        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 data for each thread to work with.
        pDataArray[i]->dwOffset = i * (MAX_ARRAY_SIZE / MaxThreads);
        pDataArray[i]->dwSize = MAX_ARRAY_SIZE / MaxThreads;
        pDataArray[i]->pdwSampleData = dwSampleData;
        pDataArray[i]->dwPrintIntermediateResults = dwPrintIntermediateResults;

        // Create the thread to begin execution on its own.
        hThreadArray[i] = CreateThread(
            NULL,                   // default security attributes
            0,                      // use default stack size  
            DoWork,                 // 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(MaxThreads, hThreadArray, TRUE, INFINITE);

    // Close all thread handles and free memory allocations.
    for (DWORD i = 0; i < MaxThreads; i++)
    {
        CloseHandle(hThreadArray[i]);
        if (pDataArray[i] != NULL)
        {
            HeapFree(GetProcessHeap(), 0, pDataArray[i]);
            pDataArray[i] = NULL;    // Ensure address is not reused.
        }
    }
    // Print Results
    _tprintf(TEXT("Computation task with %d thread(s): Added to %d in %d mills\n"), MaxThreads, dwTotal, GetTickCount() - BeginTickCount);
}

DWORD WINAPI DoWork(LPVOID lpParam)
{
    DWORD i;
    DWORD sum = 0;

    for (i = ((PTHREAD_ARGS)lpParam)->dwOffset; i < ((PTHREAD_ARGS)lpParam)->dwSize + ((PTHREAD_ARGS)lpParam)->dwOffset; i++){
        sum += ((PTHREAD_ARGS)lpParam)->pdwSampleData[i]; // <------------ ACCESS VIOLATION ERROR
    }

    dwTotal += sum;
    if (((PTHREAD_ARGS)lpParam)->dwPrintIntermediateResults){
        _tprintf(TEXT("\nSUM = %d\n"), sum);
    }

    return 0;
}

void ErrorHandler(LPTSTR 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);
}
  • 1
    +1 Это очень хорошо заданный вопрос, и я не могу понять, почему вы получили так много отрицательных голосов.
  • 0
    У вас также есть гонка данных: несколько потоков одновременно модифицируют dwTotal в DoWork . Самым простым dwTotal было бы сделать dwTotal std::atomic<DWORD> .
Показать ещё 1 комментарий
Теги:
multithreading
winapi

1 ответ

4
Лучший ответ

WaitForMultipleObjects имеет ограничение на количество потоков, на которые он может ждать. Этот предел равен MAXIMUM_WAIT_OBJECTS который имеет значение 64.

Это означает, что когда вы вызываете WaitForMultipleObjects передавая 128 дескрипторов, он немедленно возвращается с ошибкой, которую вы просто игнорируете. Чтобы быть полностью понятным, WaitForMultipleObjects возвращается до завершения потоков. Затем вы освобождаете память кучи, вследствие чего потоки, которые все еще работают, терпят неудачу при попытке получить доступ к уже освобожденной памяти.

Основной урок для изучения - всегда проверять возвращаемые значения функций Win32 API. Если бы вы проверили значение, возвращаемое WaitForMultipleObjects вы бы обнаружили проблему.

Чтобы обойти это, вам нужно будет повторно вызвать WaitForMultipleObjects в цикле. Подождите первый, MAXIMUM_WAIT_OBJECTS потоков. Затем следующие потоки WaitForMultipleObjects. И так до тех пор, пока нечего ждать.

  • 0
    Я не могу поверить, что я пропустил это, когда я прочитал документацию. Спасибо :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню