如何在C语言多线程中高效实现任务袋(Bag of Tasks)模式?
问题描述
- 计算需求:对x从2到N(N范围100~2000)调用
ComplicatedFunction(x),单次计算复杂度为O((N-x)^3),x越大计算耗时越短。 - 多线程方案:计划采用**任务袋(Bag of Tasks)**模式适配任务耗时差异,但当前实现为每个x创建独立线程;因
WaitForMultipleObjects最多支持等待64个线程,改用SynchronizationBarrier等待所有计算完成。 - 性能异常:小实例多线程版本耗时反而高于单线程(单线程约2.5分钟,多线程超3分钟);大实例虽有提速(单线程约8.5分钟,多线程约6.5分钟),但提升幅度未达预期,怀疑是线程开销过大或硬件资源受限。
- 语法疑问:
EnterSynchronizationBarrier(&barrier, SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY)与EnterSynchronizationBarrier(&barrier, 0)是否等价?
优化建议
1. 替换大量线程为线程池
Windows下创建700个线程会带来极高的上下文切换开销和内存占用(每个线程默认栈大小为1MB,700个线程仅栈内存就超过700MB)。改用Windows线程池API(CreateThreadpoolWork/SubmitThreadpoolWork)或简易版的QueueUserWorkItem,让系统自动管理线程数量(通常等于CPU核心/超线程数),避免线程泛滥。
2. 正确实现任务袋模式
用线程安全队列存储待计算的x值,线程池中的线程循环从队列取任务执行,直到队列空:
- 用
CRITICAL_SECTION或SRWLOCK保护队列的入队/出队操作; - 初始化时将2到N的所有x值加入队列;
- 线程工作函数:循环从队列取x,调用
ComplicatedFunction(x),直到队列为空后退出。
3. 优化共享变量同步
- 用原子操作替代互斥量:因为仅需对整数
total_sum做加法,直接使用InterlockedAdd原子操作,完全省去互斥量的开销:InterlockedAdd(&total_sum, x); - 若必须用互斥量,确保锁仅包裹
total_sum更新的代码,不要在计算过程中持有锁。
4. 优化等待逻辑
当前用SynchronizationBarrier的方式存在误用(后续语法部分说明),改用更高效的等待方式:
- 若用线程池,使用
WaitForThreadpoolWorkCallbacks等待所有任务完成; - 手动计数:用
InterlockedDecrement配合事件对象(CreateEvent),任务完成时计数减1,计数为0时触发事件,主线程等待该事件即可。
5. 适配硬件资源
笔记本CPU核心数有限(通常4核8线程),创建远超核心数的线程只会增加上下文切换开销,线程池会自动适配核心数量,让每个核心保持高效运行,避免无意义的线程调度。
语法疑问解答
这两个调用不等价,核心区别在于是否参与屏障的到达计数:
EnterSynchronizationBarrier(&barrier, 0):调用线程会被计入屏障的到达计数,当到达的线程数达到InitializeSynchronizationBarrier指定的lTotalThreads时,所有等待的线程会被同时释放。EnterSynchronizationBarrier(&barrier, SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY):调用线程仅等待屏障触发,不会被计入到达计数,也就是说它不算在需要到达屏障的线程总数里。
你的代码中存在误用:初始化屏障时指定了THREAD_COUNT + 1,但所有线程(工作线程+主线程)都用SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY调用,导致没有线程参与计数,屏障永远不会触发,程序会一直阻塞。正确的做法是工作线程用0参数调用,主线程也用0参数调用,这样THREAD_COUNT个工作线程+1个主线程,刚好满足THREAD_COUNT +1的计数要求,屏障会正常触发。
修正后的极简示例代码
#include <windows.h> #include <stdio.h> #define N 2000 #define MAX_WORKERS 8 // 适配CPU核心数 int total_sum = 0; CRITICAL_SECTION queue_lock; HANDLE task_done_event; int remaining_tasks = 0; DWORD WINAPI WorkerThread(LPVOID param) { while (1) { int x = -1; // 线程安全取任务 EnterCriticalSection(&queue_lock); if (remaining_tasks > 0) { x = N - remaining_tasks + 2; // 模拟从2到N的任务队列 remaining_tasks--; } LeaveCriticalSection(&queue_lock); if (x == -1) break; // 任务为空,退出 printf("Processing x = %d\n", x); // 执行复杂计算 // ComplicatedFunction(x); // 原子更新总和 InterlockedAdd(&total_sum, x); } // 任务完成,计数减1,若为0则触发事件 if (InterlockedDecrement(&remaining_tasks) == 0) { SetEvent(task_done_event); } return 0; } int main() { InitializeCriticalSection(&queue_lock); task_done_event = CreateEvent(NULL, TRUE, FALSE, NULL); remaining_tasks = N - 1; // x从2到N,共N-1个任务 HANDLE worker_threads[MAX_WORKERS]; for (int i = 0; i < MAX_WORKERS; i++) { worker_threads[i] = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL); } // 等待所有任务完成 WaitForSingleObject(task_done_event, INFINITE); // 清理资源 for (int i = 0; i < MAX_WORKERS; i++) { CloseHandle(worker_threads[i]); } CloseHandle(task_done_event); DeleteCriticalSection(&queue_lock); printf("Final sum: %d\n", total_sum); return 0; }
内容的提问来源于stack exchange,提问作者Pavol
相关产品推荐
相关产品推荐

