Linux线程安全之---同步

目录

一、同步

1.有了互斥还为什么要有同步?

条件变量

1.条件变量的使用原理

2.条件变量的原理

3.条件变量接口

3.1初始化接口:

3.2等待接口:

3.3唤醒接口:

二、代码展示

三、条件变量夺命追问

1.条件变量的等待接口第二个参数为什么会有互斥锁?

2.pthread_cond_wait的内部是针对互斥锁做了上什么操作?先释放互斥锁还是先将线程放入到PCB等待队列?

 3.线程被唤醒之后会执行什么代码?需要再获取互斥锁吗?


一、同步

1.有了互斥还为什么要有同步?

多个线程保证了互斥, 也就是保证了线程能够合理的访问临界资源了。但并不是说, 各个线程在访问临界资源的时候都是合理的。同步是为了保证多个线程对临界资源的访问的合理性。这个合理性建立在多个线程保证互斥的情况下

如果只保证互斥,有可能出现“线程饥饿”问题,那么多线程不久变成了单线程吗?

比如现实生活种的问题,去面馆吃面

我们在吃面的生活,不可能我们边吃碗里的面,厨师边往碗里做面,防止这样的事就是互斥的原理,但是还有一个问题,就是厨师不能疯狂的一直往碗里方面,碗是盛不下的,或者我们一直吃碗里的面,甚至把碗给吃了。这就是同步要去解决的问题

所以同步就是:在保证互斥的前提下,保证多个线程对临界资源访问的合理性

条件变量

  • 线程在加锁之后,判断下临界资源是否可用(这里就像拿到碗之后,可不可以做面,就是当前碗里有没有面,如果没有面,那就可以做面,如果有面就不可以做面,将互斥锁放开然后进行等待,吃面也同理):
  • 如果可用:则直接访问临界资源
  • 如果不可用:则调用等待接口,让该线程进行等待

1.条件变量的使用原理

  • 线程在加锁之后,判断下临界资源是否可用(这里就像拿到碗之后,可不可以做面,就是当前碗里有没有面,如果没有面,那就可以做面,如果有面就不可以做面,将互斥锁放开然后进行等待,吃面也同理):
  • 如果可用:则直接访问临界资源
  • 如果不可用:则调用等待接口,让该线程进行等待
  • 2.条件变量的原理

  • 本质上是:,PCB等待队列 (存放在等待的线程的PCB)

  • 3.条件变量接口

  • 3.1初始化接口:

    • 1.动态初始化

 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

  • 参数:
  • pthread_cond_t:
    • 条件变量类型
  • cond:
    • 接收一个条件变量的指针||接收一个条件变量的地址
  • attr:
    • 表示条件变量的属性信息,传递NULL,采用默认属性
  • 2.销毁条件变量:
    • int pthread_cond_destroy(pthread_cond_t *cond);
  • 3.销毁条件变量:
    • int pthread_cond_destroy(pthread_cond_t *cond);

  • 3.2等待接口

int pthread_cond_wait(pthread_cond_t    *restrict cond,pthread_mutex_t     *restrict mutex);

作用:谁调用把谁放在PCB等待队列当中

  • 参数:
  • cond:
    • 条件变量
  • mutex:
    • 互斥锁
  • 3.3唤醒接口:

  • 作用:通知PCB等待队列,唤醒在队列中的线程
  • int pthread_cond_broadcast (pthread_cond_t *cond);//唤醒PCB等待队列中所有的线程
  • int pthread_cond_signal (pthread_cond_t *cond);//唤醒PCB等待队列当中至少一个线程(有可能唤醒两个或者三个或者全部都唤醒)

二、代码展示

这个代码的效果是一会为正一会为负数,这显然是没有对碗这个资源进行合理的使用的

我们再写一个代码,在不用到条件变量的情况下,用if语句来进行

 我们运行一下来观察结果

 我们可以看到这样确实可以做到吃面和做面是正确着的,那既然这样就可以了为什么还要用到条件变量呢?

缺陷:CPU的利用率很低

假设一个线程拿到的时间片是200ms,在这个时间片范围内,他会先判断,面人如果当前碗里没有面那么他就continue退出这一次循环,但是这时候还没200ms,所以他就会一直在这里重复这个判断和continue,也可能他在刚加完锁时间片就到了,这时候他退出了,然后做面人来了,但是因为锁是加着的,所以做面人也访问不到碗,那么在这个时间片范围内就是在做无用功,时间白白给浪费掉了。

 我们这时候加上条件变量再看看看

我们这时候来看看调用堆栈,看看是不是和我们分析的是一样的

我们可以看到,吃面人是在pthread_cond_wait中,做面人是在sleep中。

但是上面代码中写了两个sleep,而且按我们之前的分析,做面人是加不了锁的,那怎么能走到下面的sleep呢?

为了区分到底是在上面还是下面,我们在两个sleep中间打印一句话,如果能打印出来就是下面的sleep中

我们发现了这句话,证明做面线程是在下面的sleep中 ,而且做面人加锁成功了

我们这时候就要知道在pthread_cond_wait中,会进行解锁逻辑

我们之前的现在写的代码已经能够做到吃一碗面做一碗面了,但如果要是有多个吃面人和做面人会怎样呢?

 我们这时候在来运行一下看看会怎么样

 我们来分析一下为什么会产生这种错误的现象

 那这个代码应该怎么改去解决问题呢?

之前那个判断逻辑有问题,有可能做面人被唤醒之后资源还是不可用的,所以要再次进行判断,将if改为while

 这时候我们再来运行一下,观察现象

 这时候确实是做一碗面吃一碗面,但现在这个程序不结束了,而且也不再进行吃面或者做面了

 我们这时候再来看一下这几个线程在干什么呢?

 我们神奇的发现,这四个线程都在pthread_cond_wait

我们来分析一下这个现象又是如何产生的

 那么怎么解决这个问题呢?

我们可以多加一个条件变量

 我们来写代码实现这个逻辑

 这时候我们再来运行一下:

 我们发现,这时候已经能够顺利的实现功能了,这才算真正支持了多线程

三、条件变量夺命追问

1.条件变量的等待接口第二个参数为什么会有互斥锁?

在函数内部需要解锁操作,如果不解锁就阻塞等待,则其他线程一定拿不到锁

2.pthread_cond_wait的内部是针对互斥锁做了上什么操作?先释放互斥锁还是先将线程放入到PCB等待队列?

解锁,   先放到PCB等待队列再进行解锁

 3.线程被唤醒之后会执行什么代码?需要再获取互斥锁吗?

1.从PCB等待队列当中移除

2.抢锁

猜你喜欢

转载自blog.csdn.net/flyingcloud6/article/details/128401006