在C中使用多线程时出现问题



我想要实现的目标在理论上非常简单,让我自己解释一下。

我有一个简单的功能,要求用户输入一个随机数,比方说200。

我有另一个函数,询问用户他想使用多少线程,如果随机数是偶数,他必须使用偶数的线程,这样每个线程都有相同数量的int来打印,代码看起来像这样:

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: %un", i, GetLastError);
}
}
WaitForMultipleObjects(nbThread, arrayThread, TRUE, INFINITE);

threadProc函数如下所示:

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

第一个问题是每个线程都在打印从0到199的数字,这是无用的,因为我希望每个线程都打印(200/nbThreads)数字。

第二个问题是线程不是很同步,事实上,只要所有线程都在运行,我就不在乎

第一个问题是每个线程都在打印从0到199的数字,这是无用的,因为我希望每个线程都打印(200/nbThreads)数字。

您需要将另一个整数传输到线程,以便线程知道从哪个整数开始(和停止)。有多种方法可以做到这一点。最常见的是创建一个带有两个整数的结构(例如称为WorkerInfo)。然后,您可以创建一个实例数组,其中每个单元都专用于特定线程。

也就是说,当前代码中存在一些问题:

  • 代码中使用的ThreadProc回调函数没有正确的签名:参数的类型为LPVOID,基本上是void*。事实证明,is可能在你的目标机器上工作,但它可能只是在其他机器上崩溃。int通常是4字节的值,而void*在64位处理器上是8字节的值(即,几乎所有现代处理器运行的窗口都是64位的)。操作系统将在堆栈中推送一个64位值,您的函数将弹出一个32位值,导致堆栈状态无效。如果在像x86-64这样的小端处理器上运行该值,则该值本身可能是有效的,但在大端处理器上则不正确。简而言之,当前代码不正确,但您在运行时很幸运
  • free的调用丢失

也有一些注意事项需要考虑:

  • 最好在stderr输出中打印错误
  • Win32线程仅适用于Windows,因此不能在所有计算机上移植。C11以更标准、更便携的方式支持线程。如果您真的想使用Win32功能,那么在有VirtualAlloc的情况下,将C函数/类型与Win32函数/类型(如intDWORDmallocCreateThread)混合使用肯定不是一个好主意

下面是一个(未经测试的)示例:

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: %un", 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 : %dn", i);
}
return 0;
}

第二个问题是线程不是很同步,事实上,只要所有线程都在运行,我就不在乎了

除非添加非常昂贵的同步,否则您无法控制这一点。核心有点像人:他们互动越多,就越不能工作。正常并行工作(至少在最初共享工作时)需要交互,但过多的交互会使事情变得缓慢,可能比一个人单独完成所有工作更慢。

由于物理原因,内核的同步性很弱:事物不能比光更快地移动,因此从一个内核到另一个内核的信息传输需要一些时间,更不用说指令是流水线式的,并且在现代处理器上并行执行,因此同步通常需要内核做大量工作才能完成。在现代x86-64处理器上,任何同步方法通常都需要至少几十个周期。

此外,请注意,线程不会同时运行。运行线程是异步完成的,OS调度器可能需要大量时间来实际调度线程。在和中,一个线程的所有printf都可能在下一个线程真正开始之前完成。更不用说线程的执行顺序没有保证:这是操作系统调度器的工作。

printf这样的IO操作受到保护,通常使用关键部分。关键部分阻止任何并行执行,因此printf不是并行执行的。当一个线程进入一个已经被另一个线程锁定的关键部分时,它正在休眠,并等待操作系统调度程序在另一个锁定关键部分的线程离开它时唤醒它。这会导致上下文切换缓慢,并且线程的调度顺序未定义:操作系统调度器可以自由执行它想要的操作。通常,当前线程有更高的机会再次锁定关键部分,因此在另一个线程真正开始其循环之前,循环的所有迭代都可以在一行中完成。简而言之:funThread不能真正并行运行,只能并发运行

最新更新