【问题标题】:Multithreaded (C) program threads not terminating多线程 (C) 程序线程未终止
【发布时间】:2017-08-01 22:52:06
【问题描述】:

我正在尝试完成一个程序,该程序使用多个线程 (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

【问题讨论】:

  • 初始化互斥体pthread_mutex_t mutex; --> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • 这些:void *A(); void *B(); void *C(); 不是有效的线程函数签名;它们应该是: void *A( void *pData );无效*B(无效*pData);无效 *C( 无效 *pData );`
  • 没有警告或错误。
  • 这种行:pthread_exit(0);一旦被执行,后面的代码就不会被执行,所以每个线程都有一些无法访问的代码。顺便说一句:那个参数应该是:NULL,而不是0
  • 贴出的代码存在逻辑问题。线程在解锁互斥锁之前调用pthread_exit()

标签: c multithreading pthreads


【解决方案1】:

你不应该把同一个函数写出 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;
}

以及示例输出(在运行 macOS Sierra 10.12.6 的 Mac 上,使用 GCC 7.1.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

第一阶段改进

记住:工作代码通常可以改进。这是 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() 函数以使用数组而不是线程 ID 和控制结构的单独变量。然后你可以很容易地拥有 2-26 个线程。您也可以使用亚秒级睡眠。您可能有不同的线程对剩余的赠款资金给予不同的慷慨——您可以在不同的线程中使用不同的分数,而不是固定的分数。


全唱全舞版

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

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

请注意,与原始中的三次调用相比,对 printf() 的单次调用改进了输出的外观。原始代码可以(并且确实)将来自不同线程的部分行交错。每个printf() 生产一整行,这不再是问题。您可以查看flockfile() 和它的朋友,看看发生了什么——规范中有一个全面的声明,涵盖了其余的 I/O 库函数。

/* 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;
}

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

示例运行

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

【讨论】:

  • 这比我写的清楚多了!感谢您的帮助
  • 检查公共变量scholarship 是否大于0,当互斥锁未锁定时,会导致竞争条件。因此,根据确切的执行顺序,代码可能会导致花费超过scholarship 的总数
  • @user3629249:是的,你是对的。我有一个经过修改但未完全检查的代码版本,其中包含该修复程序以及其他一些更改。我已经更全面地检查了代码并将其包含在答案中,并简要讨论了原因。该版本的代码也有各种其他变化。
【解决方案2】:

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

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

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

pthread_mutex_unlock(&mutex);

之前

return;

【讨论】:

  • 互斥锁应该在pthread_exit() 调用之前解锁(永远不会返回)。 printf()return 永远不会在此 if 语句中执行。
  • 谢谢!这允许程序运行完成。
  • @hedgar2017: pthreads 不必使用[thread_exit() 来终止——你可以简单地从线程函数返回。但是,无论您如何终止线程,它都需要清理任何必要的资源(例如被持有的互斥锁)。
【解决方案3】:

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

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;
}

【讨论】:

  • 朝着正确方向迈出了一大步。通常,在 C 语言中编程时,请根据函数的 man 页面编写函数,而不是尝试“捷径”那些您的代码未使用的细节。
  • 这些代码行` pthread_exit(0);返回;`是错误的。 return 语句永远不会被执行,因为它是无法访问的代码。对pthread_exit() 的调用退出线程,因此(如前一条评论中所述)这两行需要减少为pthread_exit( NULL );
  • 如前所述,线程函数的原型是:void * function( void *pData ) 最好不要使用隐式转换,也不要使用带有空参数列表的函数的副作用
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-22
  • 2014-01-24
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多