关于同步机制的一些问题探究与总结(下)

前言

上篇介绍了一部分同步机制的机理,这篇接着来看其他的部分。


三、再探究条件变量的使用

这里主要是想结合陈硕大佬的【1】篇博客,简单谈谈条件变量的使用。

正儿八经的条件变量应该是向下面这样使用:

wait端

lock(mutex);
//注意condition 是条件,cond才是条件变量
while(condition ==false ){
    
    
	wait(&cond,&mutex);//
}

unlock();

signal端

lock()
consdition = true;
/* other operation*/
signal();
unlock();

为什么条件变量的使用需要带有一个mutex呢?
我在上篇中也说了,是为了防止“信号丢失”

为什么会信号的丢失呢?下面详细的介绍一下我的理解

对于条件变量来说,signal 是边缘触发,而不是水平触发,这就是说如果在signal端发送了信号,wait 队列上没有等待的线程,那么信号就是丢失了。

如果有一种假想的条件变量cond1,可以在wait端写成下面的形式:

假想的wait端(条件变量不带有mutex)

while(condition == false){
    
       
	wait(&cond);		//这里不使用mutex,wait的作用是把当前现成阻塞在cond队列上
}

这种情况下,如果wait 线程首先检测condition 为false, 准备wait阻塞在cond队列上(还没有加入到cond队列),在这个时候,signal端释放了信号,因为队列上还没有线程(wait线程将要加入但是还未加入),这个信号也就丢失了。

这只是信号丢失的一种情况,我觉得也是比较迷惑的情况。 【1】中还说了一些其他的情况,可以好好看看。

按照我的目前的理解, condition 本身内部实现的对阻塞队列的同步并不是使用我们传进去的mutex(按照【3】中的说法,它内部使用的信号量和互斥锁), 之所以要把mutex传进去,是为了能让wait() 在把线程阻塞到工作队列时释放mutex(让其他线程signal线程能够改变条件condition,从而唤醒阻塞的线程),然后被唤醒线程需要重新竞争这个mutex, 然后才能从wait()返回到调用者。 在这个过程中的前半部分,保证判断condition条件(condition == false)和加入阻塞工作队列(wait()内部实现)是原子性的。

按照这个说法,【1】中提到的思考题:
在这里插入图片描述
你猜会是神马?


四、中断和锁

在这里插入图片描述

enen…问你一个问题:在单核CPU上,防止竞态条件,有哪几种方式?

没戳,上面讨论的几种方式都可以,锁啦,信号量,互斥锁,条件变量等。

还有一种比较重要的,就是中断,确切的说是关中断。因为在单核CPU上任务的调度主要的最原始的驱动力就是中断(这里暂且不考虑主动schedule的情况),关闭了中断并发的异步条件就没了,所以可以防止并发。但是一般在实际过程中很少,因为关闭中断可以带来很大的性能损耗(没法并发执行任务了),只有在某些条件下,关中断和开中断之间间隔很短的情况下,比如说允许内核抢占的中断处理程序中,为了防止spin_lock 的死锁,一般来说会关闭中断。

话说,我原来自己实现的小OS-HelloOs就是通过关中断来实现原子性操作的,现在回想起来,也有些不妥的地方^ _ ^|||。

四、如何理解在进程上下文中获取锁和中断上下文中获取锁的区别?

要回答这个问题,首先要搞清楚进程上下文和中断上下文的区别。

下面几张图片是我从【5】中截取来的,比较清晰的比较了进程上下文和中断上下文。

进程上下文:
在这里插入图片描述

在这里插入图片描述

中断上下文:

在这里插入图片描述

总结起来一句话,进程上下文中可以阻塞睡眠,中断上下文中内核不能睡眠。 这样如果在进程上下文中加锁,基本上可以使用上述讨论到的所有锁,但是如果在中断上下文中需要使用临界区,那么只能用自旋锁(因为其他的同步手段都可能会阻塞)。


五、如何理解【5】中提到的给数据加锁而不是给代码加锁?

在《Linux 内核设计与实现》一节中有这样一个说法:

在这里插入图片描述
书上还有一段有关的说法:
在这里插入图片描述

说实话,我不是很懂。 网上也没有特别好的解释(我都没搜到相关的内容)。但我隐隐觉得应该是有道理的,另外我还观察到一个现象:很多优秀的代码都把锁嵌入到要保护的数据结构中,比如说我正在研究学习的muduo代码:

在这里插入图片描述
是不是告诉我们,数据是一种互斥资源,代码从某种程度上来说不是,我们在写程序的时候,更多的关注度应该放在互斥的数据上?

谁知道呢? 有小伙伴了解的,可以告诉我。(* ^ ▽ ^ *)


六、如何理解【6】中说的“semaphore has no notion of ownership” ?

与semaphore相对应的,mutex被当做是have a ownership 的一个互斥工具。

回答这个问题, 要涉及到semaphore和mutex的区别了。按照我的理解,信号量的设计之出就是为了同步之用,一个线程在wait,另一个线程在signal (但究竟是哪一个是不确定的)。

而mutex不同,一个线程在获取lock之时,如果没有获得,那么它是可以知道当前的lock是被哪个线程owned (因为一个lock只能被其拥有的线程释放),当然也就可以了解哪个线程正在临界区中执行。

这大概就叫mutex has a notion of ownership, while semaphore has not。


这里在深入一点,按照我的猜测,从使用层面上说,二值信号量和mutex都是类似的,都可以用做互斥之用(只是,一般来说信号量不被用做这个用途),本应该也可以has a ontion of ownership的,但是因为它所属在信号量这一个大的框架上(必须要符号信号量这个同步的作用),所以其底层的实现无法拥有owner这个概念。

以下是linux上semaphore和mutex的结构体:
sem_t:
在这里插入图片描述

mutex:
在这里插入图片描述
在这里插入图片描述

顺便提一点:【6】中建议Don’t use a semaphore where a mutex would suffice, 一是因为semaphore -based的同步机制会有优先级翻转的概念;二是因为没有ownership的概念不利用追踪调试。 这里就不详述了,具体的可以参考原文。


七、 总结

在这里插入图片描述

好吧,最后来做一个小总结。

并发程序设计中,同步机制是一个比较基础的部分。 在一般的设计中,前人给我们总结了一些宝贵经验,如【6】中的和【9】中的:

在这里插入图片描述



最后,配个温暖的图吧…
在这里插入图片描述


参考

【1】、用条件变量实现事件等待器的正确与错误做法
【2】、Condition Variable总结
【3】、用条件变量实现事件等待器的正确与错误做法
【4】、Linux 中断、抢占、锁之间的关系
【5】、《Linux 内核设计与实现》
【6】、《Real-World Concurrency》
【7】、【读书笔记】"Real-world Concurrency"论文笔记
【8】、Semaphore 和 Mutex
【9】、《Linux多线程服务端编程》

猜你喜欢

转载自blog.csdn.net/plm199513100/article/details/113805915
今日推荐