Я играл с 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);
}
WaitForMultipleObjects
имеет ограничение на количество потоков, на которые он может ждать. Этот предел равен MAXIMUM_WAIT_OBJECTS
который имеет значение 64.
Это означает, что когда вы вызываете WaitForMultipleObjects
передавая 128 дескрипторов, он немедленно возвращается с ошибкой, которую вы просто игнорируете. Чтобы быть полностью понятным, WaitForMultipleObjects
возвращается до завершения потоков. Затем вы освобождаете память кучи, вследствие чего потоки, которые все еще работают, терпят неудачу при попытке получить доступ к уже освобожденной памяти.
Основной урок для изучения - всегда проверять возвращаемые значения функций Win32 API. Если бы вы проверили значение, возвращаемое WaitForMultipleObjects
вы бы обнаружили проблему.
Чтобы обойти это, вам нужно будет повторно вызвать WaitForMultipleObjects
в цикле. Подождите первый, MAXIMUM_WAIT_OBJECTS
потоков. Затем следующие потоки WaitForMultipleObjects
. И так до тех пор, пока нечего ждать.
dwTotal
вDoWork
. Самым простымdwTotal
было бы сделатьdwTotal
std::atomic<DWORD>
.