遍历数组和线程时访问冲突

Posted

技术标签:

【中文标题】遍历数组和线程时访问冲突【英文标题】:Access Violation While looping through array and threading 【发布时间】:2014-06-10 14:39:44 【问题描述】:

我在 windows 中使用 Threading 并编写了这个示例,它应该添加 128Mb 数组的所有位置。我创建了 x 个线程来计算总和,因此我将数组划分为 x 个和平,并让每个线程计算其中一个和平。在我尝试创建超过 64 个线程之前,这一切都很好。例如,如果我创建 65 个线程,我的 add 函数中会出现访问冲突。我的猜测是它是一个超出范围的数组,我只是不明白为什么在 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 这是一个很好的问题,我不明白为什么你会收到这么多反对票。 你也有数据竞争:多个线程同时修改dwTotal 中的DoWork。最简单的解决方法是将dwTotal 设为std::atomic&lt;DWORD&gt; 谢谢凯西,我也会调查一下。 【参考方案1】:

WaitForMultipleObjects 对它可以等待的线程数有限制。该限制为 MAXIMUM_WAIT_OBJECTS,其值为 64。

这意味着,当您调用 WaitForMultipleObjects 传递 128 个句柄时,它会立即返回并显示错误,您只需忽略该错误。要完全清楚,WaitForMultipleObjects 在线程完成之前返回。然后释放堆内存,结果是仍在运行的线程在尝试访问现在释放的内存时失败。

要学习的主要课程始终是检查 Win32 API 函数的返回值。如果您检查了WaitForMultipleObjects 返回的值,您就会发现问题所在。

要解决此问题,您需要在循环中重复调用WaitForMultipleObjects。等待第一个 MAXIMUM_WAIT_OBJECTS 线程。然后是下一个WaitForMultipleObjects 线程。以此类推,直到没有更多的等待。

【讨论】:

我不敢相信我在阅读文档时错过了这一点。谢谢你:)

以上是关于遍历数组和线程时访问冲突的主要内容,如果未能解决你的问题,请参考以下文章

java多线程访问同一个数组,存在并发问题吗,每个线程访问的是数组的不同部分,不存在冲突

遍历 mongoDB (mongoose) 数组时访问“当前元素”

为什么在尝试将指向数组的指针作为函数的参数时出现访问冲突错误?

jquery 如何遍历循环数组

怎么遍历只有id和pid 的数组排序

CopyOnWriteArrayList