You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:05:56