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

多线程无等待递增全局计数器及蒙特卡洛求π性能提升问题

解决多线程蒙特卡洛计算π的性能问题与线程安全计数器方案

首先得戳破你当前代码的核心问题:每个线程只生成1个随机点的开销,远不如创建/销毁线程的开销大——线程创建需要内核分配栈、调度上下文,这些操作的耗时是计算单个点的几百上千倍,多线程反而会拖慢整体速度。另外全局计数器的线程安全问题如果没处理好,要么结果错误,要么锁竞争会进一步吃掉并行收益。

下面给你一步步拆解解决方案:

一、先解决性能提升的核心:让每个线程干“足够多”的活

不要让每个线程只处理1个点,而是把总任务量拆分给每个线程,比如总共有1亿次尝试,分给4个线程的话,每个线程处理2500万次。这样线程创建/销毁的开销被分摊到大量计算上,并行的优势才能体现出来。

二、线程安全的计数器实现:减少锁竞争

直接对全局计数器做++操作会导致数据竞争(多个线程同时修改内存,结果不可预测),但如果每次递增都加锁,锁竞争又会让线程频繁等待。这里有两种高效的方案:

方案1:本地计数器 + 一次性全局累加(推荐)

每个线程先在本地统计自己命中圆内的点数,线程结束后再把本地值加到全局计数器——这样锁只需要用一次,几乎没有竞争:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>

pthread_mutex_t counter_mutex;
long long total_attempts;
int thread_count;
long long global_in_circle = 0;

// 传递给线程的任务参数
typedef struct {
    long long task_num;
} ThreadTask;

void* monte_carlo_worker(void* arg) {
    ThreadTask* task = (ThreadTask*)arg;
    long long local_in_circle = 0;
    // 每个线程用独立的随机种子,避免rand()的全局状态竞争
    unsigned int seed = time(NULL) ^ pthread_self();

    for (long long i = 0; i < task->task_num; i++) {
        double x = (double)rand_r(&seed) / RAND_MAX;
        double y = (double)rand_r(&seed) / RAND_MAX;
        if (x*x + y*y <= 1.0) {
            local_in_circle++;
        }
    }

    // 仅在最后把本地结果同步到全局,加锁保护
    pthread_mutex_lock(&counter_mutex);
    global_in_circle += local_in_circle;
    pthread_mutex_unlock(&counter_mutex);

    free(arg);
    return NULL;
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("使用方式: %s <总尝试次数> <线程数>\n", argv[0]);
        return 1;
    }

    total_attempts = atoll(argv[1]);
    thread_count = atoi(argv[2]);

    pthread_mutex_init(&counter_mutex, NULL);
    pthread_t* threads = malloc(thread_count * sizeof(pthread_t));
    if (!threads) { perror("malloc失败"); return 1; }

    long long base_task = total_attempts / thread_count;
    long long remaining_task = total_attempts % thread_count;

    // 创建线程分配任务
    for (int i = 0; i < thread_count; i++) {
        ThreadTask* task = malloc(sizeof(ThreadTask));
        task->task_num = base_task;
        // 最后一个线程处理剩余的任务
        if (i == thread_count - 1) task->task_num += remaining_task;
        
        if (pthread_create(&threads[i], NULL, monte_carlo_worker, task) != 0) {
            perror("创建线程失败");
            return 1;
        }
    }

    // 等待所有线程完成(这里的join是必须的,不然主线程会提前计算结果)
    for (int i = 0; i < thread_count; i++) {
        pthread_join(threads[i], NULL);
    }

    double pi_estimate = 4.0 * global_in_circle / total_attempts;
    printf("估算的π值: %.6lf\n", pi_estimate);

    // 清理资源
    pthread_mutex_destroy(&counter_mutex);
    free(threads);
    return 0;
}

方案2:用原子变量直接递增(更简洁)

如果你的编译器支持C11标准,可以用原子类型替代互斥锁,原子操作是硬件级别的同步,开销比锁更小:

#include <stdatomic.h>
// 把全局计数器改成原子类型
_Atomic long long global_in_circle = 0;

// 线程函数里不需要本地计数器,直接原子递增
void* monte_carlo_worker(void* arg) {
    ThreadTask* task = (ThreadTask*)arg;
    unsigned int seed = time(NULL) ^ pthread_self();

    for (long long i = 0; i < task->task_num; i++) {
        double x = (double)rand_r(&seed) / RAND_MAX;
        double y = (double)rand_r(&seed) / RAND_MAX;
        if (x*x + y*y <= 1.0) {
            atomic_fetch_add(&global_in_circle, 1); // 原子递增操作
        }
    }

    free(arg);
    return NULL;
}

三、测试时的注意事项

  1. 用足够大的总尝试次数:比如1e8甚至更大,这样计算时间远大于线程创建/销毁的开销,才能看到多线程的性能提升。
  2. 对比不同线程数:比如测试1、2、4、8线程的运行时间,当线程数接近CPU核心数时,性能提升最明显;超过核心数后,因为调度开销,提升会变缓甚至下降。

内容的提问来源于stack exchange,提问作者Razonixx

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:45:34