【问题标题】:pthread_mutex_lock issue in synchronization同步中的 pthread_mutex_lock 问题
【发布时间】:2020-12-01 14:36:26
【问题描述】:

我一直在编写在 C 中使用 pthread 库的代码。代码执行以下操作:

  1. 主要创建两个线程,每个线程都有自己的线程例程(函数)
  2. 一个线程(线程 1)生成一个随机数并将其分配给全局变量 x
  3. 然后另一个线程(线程2)基本上打印全局变量的值
  4. 这个生成并打印 x 的随机值的操作重复了 5 次

预期输出:

generator thread >> x = 5
printer thread >> x = 5
generator thread >> x = 9
printer thread >> x = 9
generator thread >> x = 7
printer thread >> x = 7
generator thread >> x = 3
printer thread >> x = 3
generator thread >> x = 2
printer thread >> x = 2

但是,输出以随机顺序出现,有时打印机首先执行,或者其中一个线程在另一个线程执行之前多次执行。

例如:

$ ./mutex.out 
generator >> x = 7
generator >> x = 10
generator >> x = 4
generator >> x = 7
generator >> x = 10
printer >> x = 7
printer >> x = 10
printer >> x = 10
printer >> x = 10
printer >> x = 10
$ ./mutex.out 
printer >> x = 0
printer >> x = 0
printer >> x = 0
printer >> x = 0
printer >> x = 0
generator >> x = 9
generator >> x = 2
generator >> x = 1
generator >> x = 3
generator >> x = 7

我为这个问题写的代码:

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


#define NUMTHRDS 2
pthread_t t [ NUMTHRDS];
pthread_mutex_t m1, m2; 
int x = 0; 

void *thread1(void *arg){
    for(int i = 0 ; i < 5 ; i++){
        pthread_mutex_lock(&m2); 
        pthread_mutex_lock(&m1); 
        x = (rand() % 10) + 1; // generates a random number between 1 and 10    
        printf("generator >> x = %d\n" , x);

        pthread_mutex_unlock(&m1); 
        pthread_mutex_unlock(&m2);
    }
}


void * thread2(void * arg){
    for(int i = 0 ; i < 5 ; i++){
        pthread_mutex_lock(&m1); 
        pthread_mutex_lock(&m2); 
        printf("printer >> x = %d\n" , x);
        pthread_mutex_unlock(&m2); 
        pthread_mutex_unlock(&m1);
    }
}

int main(void) 
{   
    srand(time(NULL));

    pthread_mutex_init(&m1, NULL);
    pthread_mutex_init(&m2, NULL);
    
    pthread_create(&t[1], NULL, thread1, NULL);
    pthread_create(&t[0], NULL, thread2, NULL);

    pthread_mutex_destroy(&m1);
    pthread_mutex_destroy(&m2);

    pthread_exit(NULL);

    return 0;

}

我的问题是如何正确使用互斥锁,以便在不使用其他任何东西的情况下保持这两个线程之间的顺序。

【问题讨论】:

  • 主线程(主程序)必须使用 pthread_join() 等待线程结束,否则整个程序可能在线程有机会做某事之前完成。所以调用 pthread_join() 在销毁互斥体之前等待线程结束。
  • 与互斥锁同步并不能保证顺序执行。您应该计划使用条件变量(即 pthread_cond_wait/signal())来唤醒线程
  • 这是由 pthread_exit(NULL) 完成的,我相信这会强制主线程等待程序中的所有线程完成
  • 是的,但是您之前销毁了互斥锁,因此线程可能会使用已销毁的互斥锁...
  • 你很幸运代码没有死锁。如果thread1锁定m2thread2已经有m1但没有锁定m2thread1thread2会死锁。

标签: c multithreading pthreads mutex


【解决方案1】:

乍一看有两个问题:

  1. 线程的序列化不适用于互斥锁
  2. 在等待线程结束之前调用 pthread_mutex_destroy() 使得后者可能使用已销毁的互斥锁

这是一个使用条件变量序列化线程并使用 pthread_join() 正确终止的建议:

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


#define NUMTHRDS 2
pthread_t t [ NUMTHRDS];
pthread_mutex_t m1, m2; 
int x = 0; 
pthread_cond_t cond1, cond2;
int gen, prt;


void *thread1(void *arg){
  for(int i = 0 ; i < 5 ; i++){
    pthread_mutex_lock(&m1);
    if (!gen) {
      pthread_cond_wait(&cond1, &m1);
    }

    x = (rand() % 10) + 1; // generates a random number between 1 and 10    
    printf("generator >> x = %d\n" , x);

    gen = 0;
    pthread_mutex_unlock(&m1);

    // Wake up printer
    pthread_mutex_lock(&m2);
    prt = 1;
    pthread_cond_signal(&cond2);
    pthread_mutex_unlock(&m2);

  }
}


void * thread2(void * arg){
  for(int i = 0 ; i < 5 ; i++){
    pthread_mutex_lock(&m2); 
    if (!prt) {
      pthread_cond_wait(&cond2, &m2);
    }
    printf("printer >> x = %d\n" , x);
    pthread_mutex_unlock(&m2); 

    prt = 0;
    pthread_mutex_unlock(&m2);

    // Wake up generator
    pthread_mutex_lock(&m1);
    gen = 1;
    pthread_cond_signal(&cond1);
    pthread_mutex_unlock(&m1);
  }
}

int main(void) 
{   
  srand(time(NULL));

  pthread_mutex_init(&m1, NULL);
  pthread_mutex_init(&m2, NULL);

  pthread_cond_init(&cond1, NULL);
  pthread_cond_init(&cond2, NULL);

  pthread_create(&t[1], NULL, thread1, NULL);
  pthread_create(&t[0], NULL, thread2, NULL);

  // Wake up generator
  printf("Waking up generator...\n");
  pthread_mutex_lock(&m1);
  gen = 1;
  pthread_cond_signal(&cond1);
  pthread_mutex_unlock(&m1);

  pthread_join(t[0], NULL);
  pthread_join(t[1], NULL);

  pthread_mutex_destroy(&m1);
  pthread_mutex_destroy(&m2);

  pthread_cond_destroy(&cond1);
  pthread_cond_destroy(&cond2);

  return 0;

}

【讨论】:

    【解决方案2】:

    不是一个完整的答案,但我只是想明确地说出其他答案中隐含的内容。

    ...如何正确使用互斥锁以保持这两个线程之间的顺序...

    这不是互斥锁的目的。互斥锁是错误的工具。

    当您希望一个线程等待另一个线程执行某事时可以使用的最低级别工具称为condition variable。在 posix 线程库 (pthreads) 中,您使用一个 pthread_cond_t 变量,以及对其进行操作的各种 pthread_cond_...(...) 函数:

    https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread.h.html


    但还要注意:人们通常使用一些成熟的、更高层次的模式来解决您的问题类型。例如,Google 的“阻塞队列”。

    【讨论】:

      【解决方案3】:

      根据这个问题的大多数答案和 cmets,我认为互斥锁不是维持两个线程之间顺序的正确工具。然而,在我的导师的帮助下,他向我展示了一个可以解决此类问题的技巧,诀窍是首先锁定一个关键部分(由线程 2 执行的关键部分),然后在另一个线程中解锁该关键部分(线程 1)

      这就是新代码的样子

      #include <pthread.h> 
      #include <stdio.h> 
      #include <stdlib.h> 
      #include <time.h>
      
      
      #define NUMTHRDS 2
      pthread_t t [ NUMTHRDS];
      pthread_mutex_t m1, m2; 
      int x = 0; 
      
      void *thread1(void *arg){
              pthread_mutex_lock(&m1);
              x = (rand() % 10) + 1; // generates a random number between 1 and 10    
              printf("generator >> x = %d\n" , x);
              // 2- unlock the critical section of the printing thread
              pthread_mutex_unlock(&m2);
      }
      
      
      void * thread2(void * arg){
              pthread_mutex_lock(&m2);
              printf("printer >> x = %d\n" , x);
              pthread_mutex_unlock(&m1);
      }
      
      int main(void) 
      {   
          srand(time(NULL));
      
          pthread_mutex_init(&m1, NULL);
          pthread_mutex_init(&m2, NULL);
          // 1- locking the critical section of the printing thread
          pthread_mutex_lock(&m2);
          for(int i = 0 ; i < 5 ; i++) {
              pthread_create(&t[1], NULL, thread1, NULL);
              pthread_create(&t[0], NULL, thread2, NULL);
          }
      
          pthread_exit(NULL);
      
          pthread_mutex_destroy(&m1);
          pthread_mutex_destroy(&m2);
      
          return 0;
      
      }
      
      

      【讨论】:

      • 我不确定这是否可以被视为一个答案或是否有资格作为一个值得记住的技巧。您能否就 pthread mutexes 的以下文档向您的讲师咨询? “如果一个线程试图解锁一个它尚未锁定的互斥锁或一个未锁定的互斥锁,则会导致未定义的行为。” linux.die.net/man/3/pthread_mutex_unlock
      • 请注意,“未定义的行为结果”意味着您不能依赖它来工作。但是,您可以或多或少地以这种方式使用 semaphores
      • PTHREAD_MUTEX_NORMAL 类型在 linux.die.net/man/3/pthread_mutex_unlock 中的含义是什么,因为我使用的是 pthread_mutex_t。我不确定它们是否相同,因为在我的代码中我使用的是 pthread_mutex_t 而文档中提到了 PTHREAD_MUTEX_NORMAL 类型。尽管我同意在这种情况下使用互斥锁是不可靠的。但是,我必须按照我的导师这样使用它,有趣的是它以某种方式工作:)。
      • 我也对你的问题很感兴趣;您必须向您的讲师提出这些问题,或者可能是研究堆栈溢出以寻求答案;如果这两种方法都不能满足您对答案的要求,请发布一个新问题,以便其他人能够回答您的问题。最后,在计算机科学或工程中,一个有效的程序/解决方案永远不能作为任何问题的答案,但它是按时交付任务的有用方法。一个工作计划是在寻找持久计划方面向前迈出的一小步。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-29
      • 1970-01-01
      • 2019-09-25
      • 2010-11-20
      相关资源
      最近更新 更多