多线程(C)程序线程无法终止

时间:2017-08-01 22:52:07

标签: c multithreading pthreads

我正在尝试完成一个程序,该程序使用多个线程(3)来分发4000美元的假设奖学金。每次线程处理时,它“锁定”“临界区”并阻止其他线程从总和中取出它们的块。每次访问时,该帖子都要占用“奖学金”余额的25%。输出是每个线程获得奖学金访问权所需的金额。

到目前为止,我的程序似乎正在处理正确的输出,但是当它到达结束时似乎有一个问题。每个进程/线程都到达一个不终止或退出的程序,程序就会停滞不前并且无法完成。我觉得线程正在处理,但没有达到终止条件(奖学金全部消失)。永远不会达到最后一个函数totalCalc()。有没有人看到我没有的东西,这有助于缓解这个问题或推动程序完成?

#include <stdio.h>
#include <pthread.h>
#include <math.h>

#define PERCENTAGE 0.25

pthread_mutex_t mutex; // protecting critical section
int scholarship = 4000,
                  total = 0;
void *A();
void *B();
void *C();
void *totalCalc();

int main(){ 

    pthread_t tid1,
              tid2,
              tid3;

    //pthread_setconcurrency(3); 

    pthread_create(&tid1, NULL, (void *(*)(void *))A, NULL );
    pthread_create(&tid2, NULL, (void *(*)(void *))B, NULL );
    pthread_create(&tid3, NULL, (void *(*)(void *))C, NULL );
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);

    totalCalc();


    return 0;

}

void *A(){
    float result;
    while(scholarship > 0){
        sleep(2);
        pthread_mutex_lock(&mutex);
        result = scholarship * PERCENTAGE;
        result = ceil(result);
        total = total + result;
        scholarship = scholarship - result;
        if( result >= 1){
            printf("A = ");
            printf("%.2f",result);
            printf("\n");
        }
        if( scholarship < 1){
            pthread_exit(0);
            printf("Thread A exited\n");
            return;
        }
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(0);

}

void *B(){
    float result;
    while(scholarship > 0){
        sleep(1);
        pthread_mutex_lock(&mutex);
        result = scholarship * PERCENTAGE;
        result = ceil(result);
        total = total + result;
        scholarship = scholarship - result;
        if( result >= 1){
            printf("B = ");
            printf("%.2f",result);
            printf("\n");
        }
        if( scholarship < 1){
            pthread_exit(0);
            printf("Thread B exited\n");
            return;
        }           
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(0);
}

void *C(){
    float result;
    while(scholarship > 0){
        sleep(1);
        pthread_mutex_lock(&mutex);
        result = scholarship * PERCENTAGE;
        result = ceil(result);
        total = total + result;
        scholarship = scholarship - result;
        if( result >= 1){
            printf("C = ");
            printf("%.2f",result);
            printf("\n");
        }
        if( scholarship < 1){
            pthread_exit(0);
            printf("Thread C exited\n");
            return;
        }           
        pthread_mutex_unlock(&mutex);       
    }

    pthread_exit(0);
}

void *totalCalc(){
    printf("Total given out: ");
    printf("%d", total);
    printf("\n");
}

输出:

B = 1000.00
C = 750.00
A = 563.00
B = 422.00
C = 317.00
B = 237.00
C = 178.00
A = 134.00
B = 100.00
C = 75.00
B = 56.00
C = 42.00
A = 32.00
B = 24.00
C = 18.00
B = 13.00
C = 10.00
A = 8.00
B = 6.00
C = 4.00
B = 3.00
C = 2.00
A = 2.00
B = 1.00
C = 1.00
B = 1.00
C = 1.00
^C

3 个答案:

答案 0 :(得分:4)

你不应该写出相同的函数3次 - 你可以将一个参数传递给线程函数,给它做不同的事情。

  • 您应该初始化互斥锁。
  • 您应该使用一个printf()语句而不是连续三个。
  • 您应该在退出线程函数之前解锁互斥锁。
  • 您应该在退出该功能之前打印状态。
  • 编写return时,应该从线程函数返回一个值。
  • totalCalc()功能合并为一个printf()后,PERCENTAGE功能就没有多少美德了。
  • return这个词用词不当;这是一个分数,而不是一个百分比。

我选择使用pthread_exit()而不是调用#include <math.h> #include <pthread.h> #include <stdio.h> #include <unistd.h> #define FRACTION 0.25 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static int scholarship = 4000; static int total = 0; static void *calculate(void *data); struct Data { const char *name; int doze; }; int main(void) { pthread_t tid1; pthread_t tid2; pthread_t tid3; struct Data a = { "A", 2 }; struct Data b = { "B", 1 }; struct Data c = { "C", 1 }; pthread_create(&tid1, NULL, calculate, &a); pthread_create(&tid2, NULL, calculate, &b); pthread_create(&tid3, NULL, calculate, &c); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); printf("Total given out: %d\n", total); return 0; } static void *calculate(void *arg) { struct Data *data = arg; float result; while (scholarship > 0) { sleep(data->doze); pthread_mutex_lock(&mutex); result = scholarship * FRACTION; result = ceil(result); total = total + result; scholarship = scholarship - result; if (result >= 1) { printf("%s = %.2f\n", data->name, result); } if (scholarship < 1) { printf("Thread %s exited\n", data->name); pthread_mutex_unlock(&mutex); return 0; } pthread_mutex_unlock(&mutex); } return 0; } ;差异并不重要。

第一组清理

以下是对您的代码的修订。

B = 1000.00
C = 750.00
A = 563.00
B = 422.00
C = 317.00
B = 237.00
C = 178.00
A = 134.00
B = 100.00
C = 75.00
B = 56.00
C = 42.00
A = 32.00
C = 24.00
B = 18.00
C = 13.00
B = 10.00
A = 8.00
C = 6.00
B = 4.00
B = 3.00
C = 2.00
A = 2.00
B = 1.00
C = 1.00
B = 1.00
C = 1.00
Thread C exited
Thread A exited
Thread B exited
Total given out: 4000

示例输出(在Mac上运行macOS Sierra 10.12.6,使用GCC 7.1.0):

calculate()

第一阶段改进

请记住:通常可以改进工作代码。这是static void *calculate(void *arg) { struct Data *data = arg; while (scholarship > 0) { sleep(data->doze); pthread_mutex_lock(&mutex); float result = ceil(scholarship * FRACTION); total += result; scholarship -= result; if (result >= 1) printf("%s = %.2f\n", data->name, result); pthread_mutex_unlock(&mutex); } printf("Thread %s exited\n", data->name); return 0; } 函数的另一个修订版,它可以更清晰地处理终止条件。

main()

它仍然使用混合模式算法(浮点和整数)。进一步的改进包括修改calculate()函数以使用数组而不是线程ID和控制结构的单独变量。然后你可以很容易地拥有2-26个线程。您也可以使用亚秒级睡眠。您可能有不同的线程与赠款的剩余部分不同 - 而不是固定的部分,您可以在不同的线程中使用不同的分数。

全唱,全舞蹈版

以前的版本都有一个问题(正如user3629249中的comment所指出的那样 - 尽管我已经有了一个初步版本的代码并且有必要的修复;它只是'还有SO)。 scholarship函数中的代码访问共享变量pthread_*()而不保留互斥锁。这不应该真的做到。这是一个处理它的版本。它还会错误地检查对stderr.h函数的调用,报告错误并在出现问题时退出。这是戏剧性但足以用于测试代码。可以在https://github.com/jleffler/soq/tree/master/src/libsoq中找到stderr.c标头和支持源代码printf()。错误处理在某种程度上掩盖了代码的操作,但它与之前显示的非常相似。主要的变化是互斥锁在进入循环之前被锁定,在退出循环之后解锁,在睡眠之前解锁并在醒来后重新锁定。

此代码还使用随机分数而不是一个固定分数,以及随机亚秒级睡眠时间,它有五个线程而不是三个线程。它使用控制结构数组,根据需要对其进行初始化。打印种子(当前时间)是一个非常好的;它将允许您重现在程序升级到处理命令行参数时使用的随机序列。 (线程调度问题仍然存在不确定性。)

请注意,与原始版本中的三次调用相比,对printf()的单次调用可改善输出的外观。原始代码可以(并且确实)交错来自不同线程的部分行。每个/* SO 4544-8840 Multithreaded C program - threads not terminating */ #include "stderr.h" // https://github.com/jleffler/soq/tree/master/src/libsoq #include <errno.h> #include <math.h> #include <pthread.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static int scholarship = 4000; static int total = 0; static void *calculate(void *data); enum { MAX_THREADS = 5 }; enum { MIN_PERCENT = 10, MAX_PERCENT = 25 }; struct Data { char name[2]; struct timespec doze; double fraction; }; static inline double random_fraction(void) { return (double)rand() / RAND_MAX; } static inline _Noreturn void err_ptherror(int rc, const char *fmt, ...) { errno = rc; va_list args; va_start(args, fmt); err_print(ERR_SYSERR, ERR_STAT, fmt, args); va_end(args); exit(EXIT_FAILURE); } int main(int argc, char **argv) { err_setarg0(argv[argc-argc]); pthread_t tids[MAX_THREADS]; struct Data ctrl[MAX_THREADS]; unsigned seed = time(0); printf("Seed: %u\n", seed); srand(seed); int rc; for (int i = 0; i < MAX_THREADS; i++) { ctrl[i].name[0] = 'A' + i; ctrl[i].name[1] = '\0'; ctrl[i].doze.tv_sec = 0; ctrl[i].doze.tv_nsec = 100000000 + 250000000 * random_fraction(); ctrl[i].fraction = (MIN_PERCENT + (MAX_PERCENT - MIN_PERCENT) * random_fraction()) / 100; if ((rc = pthread_create(&tids[i], NULL, calculate, &ctrl[i])) != 0) err_ptherror(rc, "Failed to create thread %d\n", i); } for (int i = 0; i < MAX_THREADS; i++) { if ((rc = pthread_join(tids[i], NULL)) != 0) err_ptherror(rc, "Failed to join thread %d\n", i); } printf("Total given out: %d\n", total); return 0; } static void *calculate(void *arg) { struct Data *data = arg; printf("Thread %s: doze = 0.%03lds, fraction = %.3f\n", data->name, data->doze.tv_nsec / 1000000, data->fraction); int rc; if ((rc = pthread_mutex_lock(&mutex)) != 0) err_ptherror(rc, "Failed to lock mutex (1) in %s()\n", __func__); while (scholarship > 0) { if ((rc = pthread_mutex_unlock(&mutex)) != 0) err_ptherror(rc, "Failed to unlock mutex (1) in %s()\n", __func__); nanosleep(&data->doze, NULL); if ((rc = pthread_mutex_lock(&mutex)) != 0) err_ptherror(rc, "Failed to lock mutex (2) in %s()\n", __func__); double result = ceil(scholarship * data->fraction); total += result; scholarship -= result; if (result >= 1) printf("%s = %.2f\n", data->name, result); } if ((rc = pthread_mutex_unlock(&mutex)) != 0) err_ptherror(rc, "Failed to unlock mutex (2) in %s()\n", __func__); printf("Thread %s exited\n", data->name); return 0; } 产生一整行,这不再是问题。您可以查看flockfile()及其朋友以了解发生了什么 - 规范中有一个涵盖其余I / O库函数的详尽声明。

Seed: 1501727930
Thread A: doze = 0.119s, fraction = 0.146
Thread B: doze = 0.199s, fraction = 0.131
Thread C: doze = 0.252s, fraction = 0.196
Thread D: doze = 0.131s, fraction = 0.102
Thread E: doze = 0.198s, fraction = 0.221
A = 584.00
D = 349.00
E = 678.00
B = 314.00
A = 303.00
C = 348.00
D = 146.00
A = 187.00
D = 112.00
E = 217.00
B = 100.00
A = 97.00
C = 111.00
D = 47.00
E = 90.00
A = 47.00
B = 36.00
D = 24.00
A = 31.00
C = 36.00
D = 15.00
E = 29.00
B = 13.00
A = 13.00
D = 8.00
A = 10.00
E = 13.00
B = 6.00
C = 8.00
D = 3.00
A = 4.00
D = 3.00
E = 4.00
B = 2.00
A = 2.00
C = 2.00
D = 1.00
A = 2.00
E = 2.00
B = 1.00
A = 1.00
D = 1.00
Thread D exited
Thread C exited
Thread A exited
Thread E exited
Thread B exited
Total given out: 4000

您仍然可以对代码进行修改,以便在睡眠后检查奖学金金额,从而在循环体中打破无限循环。这些变化留给读者作为一个小练习。

运行示例

{{1}}

答案 1 :(得分:2)

你应该在返回之前解锁互斥锁。

if( scholarship < 1){
    pthread_exit(0);
    printf("Thread A exited\n");
    return;
}
pthread_mutex_unlock(&mutex);

那些互斥锁永远不会被解锁。放一个

pthread_mutex_unlock(&mutex);

return;

答案 2 :(得分:1)

正如所建议的那样,函数的编写方式不正确,导致线程无法正常退出并且函数没有返回。为了减轻程序在完成之前挂起,我将线程exit和return语句放在锁定的互斥锁之外,这允许程序执行完成。

void *A(){
    float result;
    while(scholarship > 0){
        sleep(2);
        pthread_mutex_lock(&mutex);
        result = scholarship * PERCENTAGE;
        result = ceil(result);
        total = total + result;
        scholarship = scholarship - result;
        if( result >= 1){
            printf("A = ");
            printf("%.2f",result);
            printf("\n");
        }
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(0);
    return;

}

void *B(){
    float result;
    while(scholarship > 0){
        sleep(1);
        pthread_mutex_lock(&mutex);
        result = scholarship * PERCENTAGE;
        result = ceil(result);
        total = total + result;
        scholarship = scholarship - result;
        if( result >= 1){
            printf("B = ");
            printf("%.2f",result);
            printf("\n");
        }           
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(0);
    return;
}

void *C(){
    float result;
    while(scholarship > 0){
        sleep(1);
        pthread_mutex_lock(&mutex);
        result = scholarship * PERCENTAGE;
        result = ceil(result);
        total = total + result;
        scholarship = scholarship - result;
        if( result >= 1){
            printf("C = ");
            printf("%.2f",result);
            printf("\n");
        }           
        pthread_mutex_unlock(&mutex);       
    }

    pthread_exit(0);
    return;
}