在 C 中使用互斥锁同步 pthread

Synchronizing pthreads using mutex in C

我必须编写一个程序来计算公式 (x^i)/ 给出的前 10 个项的系列(对不起我的语言,这是我第一次用英语谈论数学)一世!。所以,基本上这是微不足道的。但是,有一些特殊要求。每个单独的术语都必须由单独的线程计算,每个线程并发工作。然后他们都必须将结果保存到名为 result 的公共变量中。之后它们必须由主线程添加,这将显示最终结果。所有这些都使用 pthreads 和 mutexes。

这就是我遇到问题的地方。我正在考虑使用 table 来存储结果,但老师告诉我,这不是正确的解决方案,因为那样我就不必使用互斥锁了。任何想法做什么以及如何同步它?我对 pthread 和 mutex 完全陌生。

这是我到目前为止得到的。我仍在研究它,所以它目前不起作用,它只是一个程序方案,我想在其中添加互斥锁。我希望这不是错的。 ;p

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

int number = 0;
float result = 0;
pthread_mutex_t term_lock;
pthread_mutex_t main_lock;
int save = 0; //condition variable

int factorial(int x) { 
        if(x==0 || x==1)
                return 1;

        return factorial(x-1)*x;
}

void  *term(void *value) {  
        int x = *(int *)value;
        float w;
        if(save == 0) {
            pthread_mutex_lock(&term_lock);
            w = pow(x, number)/factorial(number);
            result = w;
            printf("%d term of series with x: %d  is: %f\n", number, x, w);
            number++;
            save = 1;
            pthread_mutex_unlock(&term_lock);
        }
        return NULL;
}

int main(void) {

        int x, i, err = 0;
        float final = 0;
        pthread_t threads[10];

        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

        printf("Get X: \n");
        scanf("%d", &x);
        for(i=0; i<10; i++)
        {
                err = pthread_create(&threads[i], &attr, (void *)term, &x);
                if(err) {
                        printf("Error creating threads.\n");
                        exit(-1);
                }
        }
        i = 0;
        while (number <= 10) {
            //printf("While Result: %f, final %f\n", result, final); - shows that it's infinite loop
            if(save) {
                pthread_mutex_lock(&main_lock); 
                final = final + result;
                save = 0;
                pthread_mutex_unlock(&main_lock);   
                printf("If Result: %f, final %f\n", result, final); //final == last result
            }
        }
        return 0;
}

编辑:如果不清楚 - 我需要帮助解决如何将所有线程的结果存储在公共变量中并同步它。

EDIT2:可能的解决方案 - 全局变量 result 由所有线程共享。返回到主线程,它会被添加到一些局部变量,这样我就可以用另一个线程的结果覆盖它的值。当然它需要一些同步,所以在我将它添加到主线程之前另一个线程不会覆盖它。你怎么看?

EDIT3:我已经用我现在拥有的更新了代码。输出给我 8-9 项的值(术语中的 printf),然后程序仍在运行,什么也没显示。评论 printf 告诉我,while 循环是无限的。局部变量 final 也只有最后一个结果值。我做错了什么?

number 在所有线程之间共享,因此您需要使用互斥锁来保护它(这可能是您的老师希望看到的)

pthread_mutex_t number_mutex;
pthread_mutex_t result_mutex;
int number = 0;
int result = 0;

void  *term(int x) {
  float w;

  // Critical zone, make sure only one thread updates `number`
  pthread_mutex_lock(&number_mutex); 
  int mynumber = number++;
  pthread_mutex_unlock(&number_mutex);
  // end of critical zone 

  w = pow(x, mynumber)/factorial(mynumber);
  printf("%d term of series with x: %d  is: %f\n", mynumber, x, w);

  // Critical zone, make sure only one thread updates `result`
  pthread_mutex_lock(&result_mutex); 
  result += w;
  pthread_mutex_unlock(&result_mutex);
  // end of critical zone 

  return (void *)0;
}

您还应该删除 DETACHED 状态并在打印出结果之前在主程序末尾进行线程连接

主线程应该是添加项的线程,这是相当做作的,但各个线程必须将它们的结果全部写入同一个变量。我通常希望每个线程将自己的术语添加到结果中(这确实需要互斥锁),或者可能将其结果放入数组中(如您所建议的),或者将其添加到共享队列中(这需要互斥锁) ,甚至将其写入管道。不过,照老师的方法做也可以。

要解决的关键问题之一是您必须同步需要同步的截然不同的操作:

  • 各个计算线程对共享结果变量的写入
  • 主线程读取结果变量

您不能只使用一个同步结构,因为那样您无法区分计算线程和主线程。解决这个问题的一种方法是根据需要通过互斥锁同步计算线程的写入,并同步那些 。主线程通过信号量或条件变量进行读取。您也可以使用一个或多个额外的互斥锁来完成它,但不是很干净。

补充说明:

  • 您的线程存放它们的术语的结果变量必须是全局变量。线程无权访问启动它们的函数的局部变量。
  • 您的 term() 函数的签名对于线程启动函数不正确。参数必须是 void *.
  • 类型
  • 线程启动函数与其他函数没有什么不同,因为它们的局部变量只能在函数执行期间访问。特别是,return指向局部变量的指针不能做任何有用的事情,因为以后任何取消引用此类指针的尝试都会产生未定义的行为。

我不会为你写作业,但这里有一个可行的方法:

  1. 主线程初始化一个互斥量和两个信号量,后者的初始值为零。
  2. 主线程启动所有计算线程。虽然它很丑陋,但您可以通过将它们转换为 void *,然后将它们转换回 term() 函数来为它们提供数字参数(因为它的参数应该是 void *)。
  3. 然后主线程循环。在每次迭代中,它
    1. 等待信号量 1 (sem_wait())
    2. 将全局 result 变量的值添加到 运行 总计
    3. 发布到信号量 2 (sem_post())
    4. 如果执行的迭代次数与线程数一样多,则从循环中中断

同时,每个计算线程都这样做:

  1. 计算相应项的值
  2. 锁定互斥体
  3. 将术语值存储在全局 result 变量中
  4. 发布到信号量 1
  5. 等待信号量 2
  6. 解锁互斥体

更新:

要为这项工作使用条件变量,必须确定哪些共享状态受到这些条件变量的保护,因为必须始终防止因等待条件变量而意外唤醒。

在这种情况下,所讨论的共享状态似乎很自然地涉及全局 result 变量,其中计算线程 return 它们的结果。该变量实际上有两个一般的、互斥的状态:

  1. 准备好从计算线程接收值,并且
  2. 准备好让主线程读取。

计算线程需要等待第一个状态,主线程需要(反复)等待第二个状态。由于线程需要等待两种不同的条件,因此您需要两个条件变量。这是使用这些想法的替代方法:

  1. 主线程初始化一个互斥量和两个条件变量,并将result设置为-1
  2. 主线程启动所有计算线程。虽然它很丑陋,但您可以通过将它们转换为 void *,然后将它们转换回 term() 函数来为它们提供数字参数(因为它的参数应该是 void *)。
  3. 主线程锁定互斥量
  4. 然后主线程循环。在每次迭代中,它
    1. 测试 result 是否为非负数。如果是这样,它
      1. result 变量的值添加到 运行 总计
      2. 如果添加了与线程一样多的术语,则从循环中断
      3. result 设置为 -1
      4. 信号条件变量 1
    2. 等待条件变量 2
  5. 从循环中断后,主线程解锁互斥量

同时,每个计算线程都这样做:

  1. 计算其项
  2. 锁定互斥体
  3. 循环:
    1. 检查 result 的值。如果它小于零则从循环中断
    2. 等待条件变量 1
  4. 退出循环后,将 result 设置为计算项
  5. 信号条件变量 2
  6. 解锁互斥体

这是我对你的问题的解决方案:

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

int number=0;
float result[10];
pthread_mutex_t lock;

int factorial(int x) {


    if(x==0 || x==1)
                return 1;

        return factorial(x-1)*x;
}

void  *term(void *value) {
        int x = *(int *)value;
        float w;
        pthread_mutex_lock(&lock);
        w = pow(x, number)/factorial(number);
        printf("%d term of series with x: %d  is: %f\n", number, x, w);
        result[number] = w;
        number++;
        pthread_mutex_unlock(&lock);
        return NULL;
}

int main(void) {

        int x, i, err;
        pthread_t threads[10];

        printf("Get X: \n"); 
        scanf("%d", &x);

        for(i=0; i<=9; i++)
        {
                err = pthread_create(&threads[i], NULL, term, &x);
                if(err) {
                        printf("Error creating threads.\n");
                        exit(-1);
                }
        }

        for(i = 0; i < 10; i++)
        {
                pthread_join(threads[i], NULL);
        }

        i = 0;

        for(i=0; i<=9; i++)
        {
                printf("%f\n", result[i]);
        }
        return 0;
}

这段代码创建了一个全局互斥锁 pthread_mutex_t lock,它(在这种情况下)确保同一代码不会同时被任何人执行:基本上当一个线程执行时 pthread_mutex_lock(&lock),它禁止任何其他 thread 从执行那部分代码直到 "original" 线程执行 pthread_mutex_unlock(&lock)

另一个重要部分是pthread_join:这样做是强制主线程等待创建的所有其他线程的执行;这样,float result[10] 是在主线程实际处理之前写入的(在本例中,是最后一条打印指令)。

除此之外,我修复了其他用户指出的您代码中的几个错误。

如果结果是单个变量,那么一个解决方案是使用一个包含 20 个互斥锁的数组:aMutex[20];。 Main 锁定所有 20 个互斥锁,然后启动 pthread。每个 pthread[i] 计算一个本地项,等待 aMutex[i],将它的值存储到结果中,然后解锁 aMutex[10+i]。在 main() for(i = 0; i < 20; i++){ 解锁 aMutex[i] 以允许 pthread[i] 将其值存储到结果中,然后等待 aMutex[10+i] 知道结果已更新,然后将结果加到总和中。 }