线程的同步机制(二)

前言

在线程的同步机制中我们提到互斥锁能够解决资源的互斥访问,但某些情况,互斥并不能解决,线程经常遇到这种情况:要想继续执行,可能要依赖某种条件,如果条件不满足,它能做的事情就是等待,等到条件满足为止,通常条件的达成,很可能取决于另一个线程,例如系统有全局i和全局j,线程A和线程B都要访问,线程A需要互斥的执行i++;j–操作,线程B需要互斥的在i等于j时执行do_work()函数.情况如下
在这里插入图片描述
如果只使用互斥锁,可能导致do_work()函数永远不会执行,分析如下
1.线程A抢到互斥锁,执行后i=4,j=6,然后释放锁
2.线程A和线程B都可能抢到锁,如果线程B抢到锁条件不成立释放锁,如果线程A抢到锁执行后i=5,j=5,释放锁
3.同理,线程A和线程B都可能抢到锁,如果线程B抢到锁条件成立则do_work()后释放锁,如果线程A抢到锁后执行i=6,j=4,此后i=j的情况再也不会发生,就像爱情一样错过了就没有,作为一个稳定的系统我们绝不允许这种存在偶合性情况的发生,也不允许美好爱情的错过,我们就要引入互斥锁结合条件变量来解决问题,下图为改进后的模型,需要强调的是条件变量不能单独使用,必须配合互斥锁一起实现对资源的互斥访问
在这里插入图片描述

调用接口介绍


1.初始化,摧毁条件变量
和互斥锁一样,条件变量在使用之前也要进行初始化,互斥锁有静态初始化和动态初始化,条件变量也一样,简单地把PTHREAD_COND_INITIALIZER赋值给pthread_cond_t类型的变量就可以变量的静态初始化:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

动态分配条件变量可以或者对条件变量的属性有所定制,都需要用pthread_cond_init进行初始化,成功返回0:

int pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *attr);
//如果attr为NULL,则该条件变量使用默认属性,

函数pthread_cond_destroy()用来销毁条件变量,成功返回0,函数声明如下

int pthread_cond_destroy(pthread_cond_t *cond,const pthread_condattr_t *attr);

注意事项

1.永远不要用一个条件变量对另一个条件变量赋值,即pthread_cond_t cond_b = cond_a不合法
2.使用PTHREAD_COND_INITIALIZER初始化的静态变量不需要被销毁
3.调用pthread_cond_destroy()销毁的条件变量可以调用pthread_cond_init重新进行初始化
4.不要引用已销毁的条件变量

2.条件变量的通知
POSIX提供了如下的两个接口来通知等待的线程,两个函数调用成功都返回0

int pthread_cond_signal(pthread_cond_t *cond);负责唤醒等待在条件变量上的第一个线程
int pthread_cond_broadcast(pthread_cond_t *cond);广播唤醒等待在条件变量上的所有线程

显然,在此函数被调用后,被唤醒的进程又有了互斥量
3.条件变量的等待
pthread_cond_wait用来阻塞等待某个条件变量,函数声明如下

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
第一个参数cond是指向要等待的条件变量的指针
第二个参数mutex是指向与条件变量cond关联的互斥锁指针
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime)
第一个参数cond是指向要等待的条件变量的指针
第二个参数mutex是指向与条件变量cond关联的互斥锁指针
第三个参数abstime是等待过期时的绝对时间

从接口上看条件等待总是和互斥量绑定在一起,这是因为条件不会无缘无故变得满足了,必然会牵扯到共享数据的变化.所以一定要有互斥锁来保护,没有互斥锁,就无法安全地获取和修改共享数据了,所以以上两个函数都包含一个互斥锁,如果某线程因等待条件变量进入等待状态时,将隐含释放申请的互斥锁,就算是需要互斥锁为什么互斥锁和等待变量不能分开呢,比如以下设计

pthread_mutex_lock(&m)
while(condition_is_flase)
{
    pthread_mutex_unlock(&m);
    cond_wait(&cv);
    pthread_mutex_lock(&m);
}

原因在于上面的解锁和等待不是原子操作,在解锁以后,调用cond_wait之前如果已经有其他线程获取到互斥量,并且满足了条件,那么cond_wait将错过这个信号,可能会导致线程永远处于阻塞状态,所以解锁加等待必须原子操作,以确保已经注册到事件的等待队列之前,不会有其他线程可以获得信号,那么先注册等待事件后释放锁不行嘛?要注意的是条件等待是个阻塞型的接口,不单单是注册在等待队列上,线程也会阻塞于此,所以导致互斥量无法释放,造成死锁

例子:

sice@sice:~$ cat test.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int x = 9;
int y = 11;
pthread_cond_t  cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void* fun1(void* arg)
{ 
  printf("fun1() start\n");
  pthread_mutex_lock(&lock);
  while(x<y)//while效率更高
  {
    pthread_cond_wait(&cond,&lock);
  }
  pthread_mutex_unlock(&lock);
  sleep(3);
  printf("the x is %d,the y is %d\n",x,y);
  printf("fun1() end\n");
  return 0;
}
void* fun2(void* arg)
{
    printf("fun2() start\n");
    pthread_mutex_lock(&lock);
    x = 100;
    y = 2;
    //pthread_mutex_unlock(&lock);
    if(x>y)
    {
      pthread_cond_signal(&cond);
    }
    pthread_mutex_unlock(&lock);
    printf("fun2() end\n");
    return 0;
}
int main(void)
{
   pthread_t tid[2];
   int flag;
   flag = pthread_create(&tid[0],NULL,fun1,NULL);
   if(flag)
   {
       printf("pthread 1 create error\n");
       return flag;
   }
   sleep(3);
   flag = pthread_create(&tid[1],NULL,fun2,NULL);
   if(flag)
   {
       printf("pthread 2 create error\n");
       return flag;
   }
   sleep(6);
   return 0;
}

结果:

sice@sice:~$ ./test 
fun1() start
fun2() start
the x is 100,the y is 2
fun1() end
fun2() end
https://blog.csdn.net/shichao1470/article/details/89856443?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3
该文章值得大家学习
发布了83 篇原创文章 · 获赞 3 · 访问量 1246

猜你喜欢

转载自blog.csdn.net/qq_41936794/article/details/105301877