• 【线程】多线程同步互斥-条件变量与信号量,生产者与消费者问题


    条件变量

      条件变量用来阻塞线程等待某个事件的发生,并且当等待的事件发生时,阻塞线程会被通知。

      互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。

      而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。

      使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。

      一般说来,条件变量被用来进行线程的同步。

      条件变量的结构为pthread_cond_t

      最简单的使用例子:

    pthread_cond_t cond;
    pthread_cond_init(&cond, NULL);
    pthread_cond_wait(&cond, &mutex);
        ...
    pthread_cond_signal(&cond);

      

      调用pthread_cond_wait的线程会按调用顺序进入等待队列,当有一个信号产生时,先进入阻塞队列的线程先得到唤醒。条件变量在某些方面看起来差不多。

      正如上面所说的,条件变量弥补了互斥锁的不足。

      pthread_cond_wait() 用于阻塞当前线程,等待别的线程使用pthread_cond_signal()pthread_cond_broadcast来唤醒它 
      pthread_cond_wait() 必须与pthread_mutex 配套使用。
     
      pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread_cond_signal()或pthread_cond_broadcast,把该线程唤醒,使pthread_cond_wait()通过(返回)时,该线程又自动获得该mutex。
     
      pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
     
      使用pthread_cond_signal一般不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。
     
      但是pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程. 
      另外,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.

      接着前一章列举的生产者、消费者例子中,我们这里增加消费者(并假设缓冲区可以放任意多条信息),比如有3个消费者线程,如果都使用互斥锁,那么三个线程都要不断的去查看缓冲区是否有消息,有就取走。这无疑是资源的极大浪费。如果用条件变量,三个消费者线程都可以放心的“睡觉”,缓冲区有消息,消费者会收到通知,那时起来收消息就好了。

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <pthread.h>
     4 #include <sys/time.h>
     5 
     6 static char buff[50];
     7 pthread_mutex_t mutex;//互斥锁
     8 pthread_cond_t cond;//条件变量
     9 pthread_mutex_t cond_mutex;
    10 
    11 void consumeItem(char *buff)
    12 {
    13     printf("consumer item
    ");
    14 }
    15 
    16 void produceItem(char *buff)
    17 {
    18     printf("produce item
    ");
    19 }
    20 
    21 void *consumer(void *param)//消费者进程回调函数
    22 {
    23     int t = *(int *)param;
    24     while (1)
    25     {
    26         pthread_cond_wait(&cond, &cond_mutex);//加入到等待队列中去
    27         pthread_mutex_lock(&mutex);
    28         printf("%d: ", t);
    29         consumeItem(buff);
    30         pthread_mutex_unlock(&mutex);
    31     }
    32     return NULL;
    33 }
    34 
    35 void *producer(void *param)//生产者进程回调函数
    36 {
    37     while (1)
    38     {
    39         pthread_mutex_lock(&mutex);
    40         produceItem(buff);        
    41         pthread_mutex_unlock(&mutex);
    42         pthread_cond_signal(&cond);//唤醒等待队列中的进程
    43         sleep(1);
    44     }
    45     return NULL;
    46 }
    47 
    48 int main()
    49 {
    50     pthread_t tid_c, tid_p, tid_c2, tid_c3;
    51     void *retval;
    52     
    53     pthread_mutex_init(&mutex, NULL);//初始化互斥锁
    54     pthread_mutex_init(&cond_mutex, NULL);
    55     
    56     pthread_cond_t cond;
    57     pthread_cond_init(&cond, NULL);//初始化条件变量
    58     
    59     
    60     int p[3] = {1, 2, 3}; //用来区分是哪个线程
    61     
    62     pthread_create(&tid_p, NULL, producer, NULL);
    63     pthread_create(&tid_c, NULL, consumer, &p[0]);
    64     pthread_create(&tid_c2, NULL, consumer, &p[1]);
    65     pthread_create(&tid_c3, NULL, consumer, &p[2]);
    66 
    67     
    68     pthread_join(tid_p, &retval);
    69     pthread_join(tid_c, &retval);
    70     pthread_join(tid_c2, &retval);
    71     pthread_join(tid_c3, &retval);
    72         
    73     return 0;
    74 }
    View Code

      要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如缓冲区到底有多少条信息等。

      与条件变量有关的其它函数有:

     1 /* 销毁条件变量cond */
     2 int pthread_cond_destroy (pthread_cond_t *cond);  
     3 
     4 /* 唤醒所有等待条件变量cond的线程 */
     5 int pthread_cond_broadcast (pthread_cond_t *cond);
     6 
     7 /* 等待条件变量直到超时 */
     8 int pthread_cond_timedwait (pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);
     9 
    10 /* 初始化条件变量属性 attr*/
    11 int pthread_condattr_init (pthread_condattr_t *attr);
    12 
    13 /* 销毁条件变量属性 */
    14 int pthread_condattr_destroy (pthread_condattr_t *attr)
    15 
    16 /* 读取条件变量属性attr的进程共享标志 */
    17 int pthread_condattr_getpshared (const pthread_condattr_t *attr,int *pshared);
    18 
    19 /* 更新条件变量属性attr的进程共享标志 */
    20 int pthread_condattr_setpshared (pthread_condattr_t *attr,int pshared);
    View Code

      

      一般来说,条件变量被用来进行线程间的同步。


     

    信号量

      信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。

      当公共资源增加时,调用函数sem_post()增加信号量。

      只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。

      和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。
      信号量的数据类型为结构sem_t,它本质上是一个长整型的数。

      函数sem_init()用来初始化一个信号量。它的原型为:

    extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
    1. sem为指向信号量结构的一个指针;
    2. pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;
    3. value给出了信号量的初始值。

      函数sem_post( sem_t *sem )用来增加信号量的值。

      当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。

      函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。

      函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。

      函数sem_destroy(sem_t *sem)用来释放信号量sem。

      在生产者、消费者例子中,假设缓冲区最多能放10条消息。用信号量实现

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <pthread.h>
     4 #include <semaphore.h>
     5 
     6 static char buff[50];
     7 pthread_mutex_t mutex;//互斥锁
     8 pthread_mutex_t cond_mutex;
     9 pthread_cond_t cond;//条件变量
    10 
    11 sem_t msg_cnt;      //缓冲区消息数
    12 sem_t space_cnt;   //缓冲区空闲数
    13 
    14 void consumeItem(char *buff)
    15 {
    16     printf("consumer item
    ");
    17 }
    18 
    19 void produceItem(char *buff)
    20 {
    21     printf("produce item
    ");
    22 }
    23 
    24 void *consumer(void *param)//消费者进程回调函数
    25 {
    26     while (1)
    27     {
    28         sem_wait(&msg_cnt);//等待一个消息
    29         pthread_mutex_lock(&mutex);
    30         consumeItem(buff);
    31         pthread_mutex_unlock(&mutex);
    32         sem_post(&space_cnt);//增加一个空位
    33     }
    34     return NULL;
    35 }
    36 
    37 void *producer(void *param)//生产者进程回调函数
    38 {
    39     while (1)
    40     {
    41         sem_wait(&space_cnt);//减少一个空位
    42         pthread_mutex_lock(&mutex);
    43         produceItem(buff);        
    44         pthread_mutex_unlock(&mutex);
    45         sem_post(&msg_cnt);//增加一个消息
    46         
    47     }
    48     return NULL;
    49 }
    50 
    51 int main()
    52 {
    53     pthread_t tid_c, tid_p;
    54     void *retval;
    55     
    56     pthread_mutex_init(&mutex, NULL);//以默认属性初始化
    57     pthread_mutex_init(&cond_mutex, NULL);
    58     
    59     pthread_cond_t cond;
    60     pthread_cond_init(&cond, NULL);
    61 
    62     sem_init(&msg_cnt, 0, 0);  //信号量初始化,初始缓冲区没有消息
    63     sem_init(&space_cnt, 0, 10);  //初始缓冲区能放10条消息
    64     
    65     pthread_create(&tid_p, NULL, producer, NULL);
    66     pthread_create(&tid_c, NULL, consumer, NULL);
    67     
    68         
    69     pthread_join(tid_p, &retval);
    70     pthread_join(tid_c, &retval);
    71             
    72     return 0;
    73 }
    View Code
  • 相关阅读:
    移动端touch事件获取事件坐标
    详解webpack中的hash、chunkhash、contenthash区别
    textarea placeholder 设置主动换行
    js-xlsx的使用
    关于Blob对象的介绍与使用
    spring boot zuul集成kubernetes等第三方登录
    Spring Boot 获取yaml配置文件信息
    spring boot @Value源码解析
    java.lang.StackOverflowError解决
    Jpa 重写方言dialect 使用oracle / mysql 数据库自定义函数
  • 原文地址:https://www.cnblogs.com/lcw/p/3236602.html
Copyright © 2020-2023  润新知