从锁的角度理解线程
2015-03-26 23:42
211 查看
1.锁的本质
线程中,同步与互斥无疑是核心,看完了APUE的线程部分,有一些小小的理解,有必要记录下来。锁的引入是为了保证同一时间片,只能有一个线程操作“被锁”的变量。而实际常用的锁(mutex,互斥量)则是通过阻塞函数实现的。
但是事实上,锁并不是如上面描述的那样,直接加在变量上,使得变量不会被改变。(这种实现,需要锁住内存吧),我们用的mutex其实是提供一个阻塞的变量,一切加锁、解锁的操作,都是针对这个“锁变量”,而不是我们想要保护的变量的。
这样看来,锁就很形象了,在变量外面加一个盒子,盒子可以被锁上、被打开!
因此,使用锁时,实际上是按照一定的规则,来使用变量:如果我不将变量放在盒子里,那么即使盒子锁上了,变量还是没有加锁的效果。就是这么简单。
所以锁的使用的前提是,按照规则来定义锁后的行为。只有“变量装在盒子里”这种行为才是能够给变量赋予锁属性的
2.令人迷惑的读写锁
第11章有一节讲的是读写锁,这个读写的描述真的是有些误导了。开始读的时候,一直觉得加上rdlock后就不能被读了(因为我一度错误的认为锁有禁止内存被访问的神奇功能),但实际上,后来我想通了之后却发现:
这完全和读写没关系啊!
读写锁,其实就是两个锁共用一个unlock函数。假设你不遵守规则,在全部的rdlock后进行写操作、在全部的wr操作后进行读操作,那么这时候读锁起得是写锁的功能!写锁实际上起得的读锁的功能!
锁的实际意义仅仅和锁后的行为有关,读写锁这个概念实际上已经约定了:读锁后必须不是更改数据的操作,写锁后应该是更改数据的操作。
还是上面的理解让这一切变得简单,锁需要按照规则来访问变量,锁本身才有意义!
基于这个理解,读写锁就是一个二元锁,完全可以根据需要定义一个N元锁。我用C++实现了一个这样的class。
3.pthread核心函数:create和join
pthread,线程,在使用的时候,往往都是create,然后在某处join回收它。pthread_join是阻塞的,这一点让人很自然的联想到锁的实现!
完全可以这样理解:
pthread_create在函数的开始,创建了一个匿名锁,然后将其锁住;
而pthread_join,与create共享这个匿名锁,在调用join的时候,调用pthread_mutex_lock来获得这个锁;
直到create传入的函数指针执行完毕,返回,create才会进行unlock操作,
这时候,主线程中被阻塞的join函数终于获取了锁的控制,然后进行一系列线程销毁操作,至此,这个线程的流程完毕。
使用join很直观,并且还可以获得返回值,但是有一个缺点,那就是因为阻塞的特性,导致必须按顺序获得各个线程的匿名锁。从时间上来看,如果某个线程需要很长的时间,但是这个线程的join却排在第一个,那么后面的全部线程,都被堵住了。我们浪费了在这个线程阻塞期间的时间片。barrier就是一个批量的join策略
4.屏障barrier
barrier和锁又有什么关系呢?
使用barrier,首先要定义一个全局的(必须保证在线程函数中可以访问)pthread_barrier_t的变量。然后调用初始化函数init,接下来在线程函数中都调用barrier的wait函数。
这个过程,是不是就像一个多元锁呢?
我们首先在init给出锁的数量,pthread_barrier_init(&pthread_barrier_t,NULL,3)就是定义了一个3元锁,并且将其中全部的锁都锁上;
除了这三个子锁之外,barrier内部还有一个额外的锁,暂且叫做总锁吧。它初始时是锁住的,只有在所有的子锁都解开时,才会解开。
然后,每个线程末尾的pthread_barrier_wait函数,实际进行的就是解锁操作:
[b] 1 - 在wait被执行时,首先从子锁池里拿出一个子锁,然后把它解开,之后尝试继续执行,但是因为总锁还是锁着的,因此被阻塞
[/b]
[b] 2 - 在所有的子锁被解开后,总锁被解锁,所有的子锁通道这时候都畅通了,该销毁的销毁该收尾的收尾。一切重新继续。
[/b]
有了这个认识,我们完全可以脱离barrier的范围,针对各种不同功能的子线程,在不同的位置设置同步节点。
这比join还需要获得进程ID的方式方便的多,用几把锁,create这些简单的函数,就能实现线程的同步!
5.锁,如何实现?
锁的特性是阻塞,这个问题就变成了,如何实现一个阻塞函数?[b] 关键词:时间片调度。
[/b]
这里涉及内核知识我不是很明白,不敢妄言。但是在此给出用程序实现阻塞函数的思路.
1: while循环
[b] 很简单的思路,一个while循环反复轮转,检查需要关注的变量是否符合条件。比如:
[/b]
[b][b] [b] while(mutex==1){}; mutex =1;[/b][/b][/b]
[b][b][b][b] ......
[/b][/b][/b][/b]
[b][b][b][b][b] mutex = 0;
[/b][/b][/b][/b][/b]
[b][b][b][b] 这就完成了对这个mutex的获取(while中不断检查),然后加锁操作(再次=1),在用完锁之后,将锁释放.
[/b][/b][/b][/b]
[b][b][b][/b][/b][/b]
[b][b][b][b][b] 2: 上面的while循环是有缺陷的,因为不断在检查,所以不断在占用CPU时间片。(monitor中,while循环时CPU占用会达到100%)
[/b][/b][/b][/b][/b]
[b][b][b][b][b] 所以,也可以usleep()一段时间,这样会略微减少消耗的资源。
[/b][/b][/b][/b][/b]
[b][b][b][/b][/b][/b]
[b][b][b][b][b] 更高级的实现可不只这么简单!目前还不懂。
[/b][/b][/b][/b][/b]
相关文章推荐
- 转载:从进程-线程的角度,理解Activity,Service,Broadcast
- http://flyvenus.net/ 深入android http://www.eoeandroid.com/thread-67739-1-1.html 从进程-线程的角度,理解Activity,Service,Broadcast - 4.7更
- 从JVM角度理解线程
- 聊聊JVM(五)从JVM角度理解线程
- 从源码角度理解Android线程
- 进程、线程和协程的理解
- 从服务端架构设计角度,深入理解大型APP架构升级
- 我对java线程同步的理解
- 深入理解线程 以及线程并发的线程安全问题及处理方法
- 数据库三大范式另一角度的理解
- 理解线程3 c语言示例线程基本操作
- jQuery源码分析以及从jQuery对象创建的角度理解extend方法的原理
- 从JDK源码角度看线程的阻塞和唤醒
- 阿里云上从ASP.NET线程角度对“黑色30秒”问题的全新分析
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- EM算法的两种理解角度
- 将Spring推下神坛(仿造一个中国式Spring ,教大家一步一步从代码的角度理解 Ioc)
- 我理解的异步与线程
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- handler(7) Android异步消息处理机制完全解析,带你从源码的角度彻底理解