您的位置:首页 > 其它

从锁的角度理解线程

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]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: