Home > Back-end >  Issue when using MultiThreading in C
Issue when using MultiThreading in C

Time:11-20

What I want to achieve is pretty simple in theory, let me explain myself.

I have a simple function that ask to the user to enter a random number, let's say 200.

I have another function that ask the user how much thread he want to use, if the random number is EVEN he must use an even number of threads so each thread have the same number of int to print, the code look like this :

HANDLE* arrayThread;
arrayThread = (int*)malloc(nbThread * sizeof(int));
DWORD* ThreadId;
ThreadId = malloc(sizeof(int) * nbThread);

for (int i = 0; i < nbThread; i  )
{
    arrayThread[i] = CreateThread(NULL, 0, threadProc, 200, 0, &ThreadId[i]);
    if (arrayThread[i] == NULL)
    {
        printf("Create Thread %d get failed. Error no: %u\n", i, GetLastError);
    }
}

WaitForMultipleObjects(nbThread, arrayThread, TRUE, INFINITE);

And the threadProc function look like this :

DWORD WINAPI funThread(int nb)
{
    for (int i = 0; i < nb; i  )
    {   
        printf("Hello : %d\n", i);
    }
    return 0;
}

The first problem is that each thread is printing numbers from 0 to 199, which is useless as I want each thread to print (200/nbThreads) numbers each.

Second problem is that the threads are not very synchronized, which in fact i don't really care as long as all the threads are running

CodePudding user response:

The first problem is that each thread is printing numbers from 0 to 199, which is useless as I want each thread to print (200/nbThreads) numbers each.

You need to transmit another integer to the thread so they can know from which integer to start (and stop). There are multiple ways to do that. The most frequent one is to create a structure for that (eg. called WorkerInfo) with the two integer. Then you can create an array of instance where each cell is dedicated to a specific thread.

That being said there are some issues in the current code:

  • The ThreadProc callback function used in your code does not have a correct signature : the parameter is of type LPVOID, that is basically void*. It turns out is may work on your target machine but is can simply crash on other ones. int is generally a 4-byte value while void* is a 8-byte value on 64-bit processors (ie. nearly all modern processor running windows are 64-bit ones). The OS will push a 64-bit value in the stack and your function will pop a 32-bit value resulting in an invalid stack state. The value itself may be valid if your run it on a little endian processor like a x86-64 one but it will be incorrect on a big endian processor. Put it shortly, the current code is not correct but you are lucky at runtime.
  • The calls to free are missing.

There are few note to consider too:

  • It is better to print errors in the stderr output.
  • Win32 threads are only for Windows so they are not portable on all machines. C11 supports threads in a more standard and portable way. If you really want to use Win32 features, then it is certainly not a good idea to mix C function/types with Win32 ones like int and DWORD or malloc with CreateThread while there is VirtualAlloc.

Here is an (untested) example:

struct WorkerInfo
{
    int start;
    int stop;
};
HANDLE* arrayThread = (HANDLE*)malloc(nbThread * sizeof(HANDLE));
DWORD* threadId = (DWORD*)malloc(nbThread * sizeof(DWORD));
struct WorkerInfo* threadParam = (struct WorkerInfo*)malloc(nbThread * sizeof(struct WorkerInfo));

for (int i = 0; i < nbThread; i  )
{
    // load-balance the work, feel free to change that regarding your needs
    threadParam[i].start = 200*i/nbThread;
    threadParam[i].stop = 200*(i 1)/nbThread;

    arrayThread[i] = CreateThread(NULL, 0, threadProc, &threadParam[i], 0, &threadId[i]);
    if (arrayThread[i] == NULL)
    {
        fprintf(stderr, "Create Thread %d get failed. Error no: %u\n", i, GetLastError);
    }
}

WaitForMultipleObjects(nbThread, arrayThread, TRUE, INFINITE);

free(threadParam);
free(ThreadId);
free(arrayThread);
DWORD WINAPI funThread(LPVOID param)
{
    struct WorkerInfo info = *(struct WorkerInfo*)param;

    for (int i = info.start; i < info.stop; i  )
    {
        printf("Hello : %d\n", i);
    }

    return 0;
}

Second problem is that the threads are not very synchronized, which in fact i don't really care as long as all the threads are running

You cannot control that unless you add synchronizations which will be very expensive. Cores are a bit like people: the more they interact, the less they can work. Interaction is required for properly working in parallel (at least to initially share the work), but too many interaction make things slow, potentially slower than one person doing all the work alone.

Cores are weakly synchronized because of physics: things cannot move faster than light so transmitting information from one core to another take some time not to mention instructions are pipelined and executed in parallel on modern processor so synchronizations often require a lot of work for cores to complete them. Any synchronization methods typically requires at least several dozens of cycles on modern x86-64 processors.

Moreover, note that threads does not run at the same time. Running a thread is done asynchronously and the OS scheduler can take a lot of time to actually schedule the thread. In the and, all the printf of a thread may be completed before the next one could actually start. Not to mention there is not guarantee on the order of the execution of the thread: this is the job of the OS scheduler.

IO operations like printf are protected, typically using a critical section. Critical section prevent any parallel execution so the printf are not done in parallel. When a thread enter a critical section that is already locked by another thread, it is sleeping and wait for the OS scheduler to awake it when another thread locking the critical section leave it. This causes slow context switches and the order in which the threads are scheduled is undefined: the OS scheduler is free to do what it want. Generally, the current thread has a higher chance to lock the critical section again so all the iterations of a loop may be completed in a row before another thread can actually start its loop. Put it shortly: funThread cannot run truly in parallel but only concurrently.

  • Related