C多线程编程- 近似求解π

本程序使用蒙特卡洛方法估算圆周率(π)。它首先创建了指定数量的线程,每个线程生成一个随机点并检查该点是否在单位圆内。基于这些线程的结果,程序计算在单位圆内的点的比例,并乘以4来估算π的值。为了对比,程序还直接在主线程中(没有并发)进行了相同的π估算过程(由于每次都是生成随机数,所以这个基准也没啥意义hh~)。最后,程序打印出两种方法得到的π值。

#include 
#include 
#include 

struct arg_t {
    float x;
    float y;
};

void *start(void *arg) {
    struct arg_t *ll = (struct arg_t *)arg;
    float x1 = ll->x;
    float y1 = ll->y;
    long M = 0;
    if (x1*x1 + y1*y1 <= 1.0) {
        M ++;
    }
    pthread_exit((void *)M);
}

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "Please provide a number as an argument.\n");
        exit(1);
    }
    long N1 = atoll(argv[1]);
    // printf("%ld\n", N1);
    pthread_t tids[N1];

    // pai(concurrency)
    for (long i = 0; i < N1; ++ i) {
        struct arg_t *arg = malloc(sizeof(struct arg_t));
        int x = rand();
        int y = rand();
        arg->x = 1.0*x / RAND_MAX;
        arg->y = 1.0*y / RAND_MAX;
        pthread_create(&tids[i], 0, start, arg);
    }

    void *res = 0;
    long M = 0;
    for (long i = 0; i < N1; ++ i) {
        pthread_join(tids[i], &res);
        M += (long)res;
    }
    printf("pai = %f\n", 4.0*M/N1);  // concurrency

    // pai(oracle)
    M = 0;
    for (long i = 0; i < N1; ++ i) {
        int x = rand();
        int y = rand();
        float x1 = 1.0*x / RAND_MAX;
        float y1 = 1.0*y / RAND_MAX;
        if (x1*x1 + y1*y1 <= 1.0) {
            M ++;
        }
    }
    printf("pai = %f\n", 4.0*M/N1);  // oracle
    pthread_exit(0);
}

测试一下上述程序:

majn@tiger:~$ ./pai 10000
pai = 3.171200
pai = 3.142000
majn@tiger:~$ ./pai 100000
Segmentation fault (core dumped)

分析Segmentation fault的原因:

在上面的程序中,为每个线程都动态分配了 arg_t 结构的内存,但在线程执行完毕后,这些内存并没有被释放。虽然这不是立即的问题,但长期这样会导致内存泄露。应该在线程函数中或 pthread_join 之后释放这些内存。

改进方案:

一种方法是在线程函数 start 的结尾释放它。但由于在主函数中可能还需要访问这些结构,更安全的方法是在 pthread_join 之后释放这些动态分配的内存。

修改上述代码:

#include 
#include 
#include 

struct arg_t {
    float x;
    float y;
};

void *start(void *arg) {
    struct arg_t *ll = (struct arg_t *)arg;
    float x1 = ll->x;
    float y1 = ll->y;
    long M = 0;
    if (x1*x1 + y1*y1 <= 1.0) {
        M ++;
    }
    pthread_exit((void *)M);
}

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "Please provide a number as an argument.\n");
        exit(1);
    }
    long N1 = atoll(argv[1]);

    struct arg_t *args[N1];
    // printf("%ld\n", N1);
    pthread_t tids[N1];

    // pai(concurrency)
    for (long i = 0; i < N1; ++ i) {
        args[i] = malloc(sizeof(*args[i]));
        // struct arg_t *arg = malloc(sizeof(*arg));
        int x = rand();
        int y = rand();
        args[i]->x = 1.0*x / RAND_MAX;
        args[i]->y = 1.0*y / RAND_MAX;
        pthread_create(&tids[i], 0, start, args[i]);
    }

    void *res = 0;
    long M = 0;
    for (long i = 0; i < N1; ++ i) {
        pthread_join(tids[i], &res);
        M += (long)res;
        free(args[i]);
    }
    printf("pai = %f\n", 4.0*M/N1);  // concurrency

    // pai(oracle)
    M = 0;
    for (long i = 0; i < N1; ++ i) {
        int x = rand();
        int y = rand();
        float x1 = 1.0*x / RAND_MAX;
        float y1 = 1.0*y / RAND_MAX;
        if (x1*x1 + y1*y1 <= 1.0) {
            M ++;
        }
    }
    printf("pai = %f\n", 4.0*M/N1);  // oracle
    pthread_exit(0);
}

这样,每次线程执行完毕并被主线程收回后,对应的动态分配的内存都会被释放。

测试一下修改后的程序:

majn@tiger:~$ ./pai 100000
pai = 3.721760
pai = 3.137640
majn@tiger:~$ ./pai 1000000
Segmentation fault (core dumped)

好好好,这样玩儿是吧。

分析Segmentation fault的原因:

仔细观察上面的程序,我使用了一个固定大小的线程数组:pthread_t tids[N1];。对于大的 N1 值,这可能会导致栈溢出。在大多数系统上,默认的栈大小可能不足以容纳大量的 pthread_t 变量。

改进方案:

考虑动态分配线程ID数组的空间。

#include 
#include 
#include 

struct arg_t {
    float x;
    float y;
};

void *start(void *arg) {
    struct arg_t *ll = (struct arg_t *)arg;
    float x1 = ll->x;
    float y1 = ll->y;
    long M = 0;
    if (x1*x1 + y1*y1 <= 1.0) {
        M ++;
    }
    pthread_exit((void *)M);
}

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "Please provide a number as an argument.\n");
        exit(1);
    }
    long N1 = atoll(argv[1]);

    struct arg_t *args[N1];
    // printf("%ld\n", N1);
    pthread_t *tids = malloc(N1 * sizeof(pthread_t));

    // pai(concurrency)
    for (long i = 0; i < N1; ++ i) {
        args[i] = malloc(sizeof(*args[i]));
        // struct arg_t *arg = malloc(sizeof(*arg));
        int x = rand();
        int y = rand();
        args[i]->x = 1.0*x / RAND_MAX;
        args[i]->y = 1.0*y / RAND_MAX;
        pthread_create(&tids[i], 0, start, args[i]);
    }

    void *res = 0;
    long M = 0;
    for (long i = 0; i < N1; ++ i) {
        pthread_join(tids[i], &res);
        M += (long)res;
        free(args[i]);
    }
    printf("pai = %f\n", 4.0*M/N1);  // concurrency

    // pai(oracle)
    M = 0;
    for (long i = 0; i < N1; ++ i) {
        int x = rand();
        int y = rand();
        float x1 = 1.0*x / RAND_MAX;
        float y1 = 1.0*y / RAND_MAX;
        if (x1*x1 + y1*y1 <= 1.0) {
            M ++;
        }
    }
    printf("pai = %f\n", 4.0*M/N1);  // oracle
    pthread_exit(0);
}
majn@tiger:~$ ./pai 1000000
pai = 3.972176
pai = 3.142136
majn@tiger:~$ ./pai 10000000
Segmentation fault (core dumped)

???不玩儿了!!! 到此为止,我们把输入数据的规模N1从一开始最大接受10000扩大到现在最大接受1000000


注: 蒙特卡洛方法(Monte Carlo method)是一种通过随机抽样来获得数值解的统计方法。这个方法得名于摩纳哥的蒙特卡洛赌场,因为它大量使用随机性和概率。蒙特卡洛方法在物理学、工程学、经济学和许多其他领域都有广泛的应用。

关键概念和特点:

  1. 随机抽样:这是蒙特卡洛方法的核心。为了得到一个问题的数值解,这个方法使用随机数或更通常地说,使用伪随机数。

  2. 统计结果:通过对大量的随机样本进行统计分析,得到的是一个近似解,而不是确切的解。

  3. 精度与样本数量:通常,随着样本数量的增加,估算的精度也会提高。但是,为了使误差减少到原来的一半,样本数通常需要增加四倍。

  4. 应用:蒙特卡洛方法在多种应用中都非常有用,尤其是在问题的解析解很难得到或者不存在时。例如,它被用于估算复杂积分、求解难以解析的统计物理问题、进行金融市场模拟等。

  5. 示例 - 估算π:一个经典的应用是使用蒙特卡洛方法估算π的值。方法是这样的:随机投掷点到单位正方形内,统计落在单位圆内的点的数量。落在圆内的点数与总点数的比例,乘以4,就给出了π的近似值。

简而言之,蒙特卡洛方法是一种利用随机性来求解问题的技术,通过对大量样本的统计分析来获得结果。

你可能感兴趣的:(工程化C,c语言)