浏览 2196 次
锁定老帖子 主题:分析《进程间通信》一书中的读/写锁策略
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2008-08-27
其中附件中的代码为自己重新封装后的代码和一个测试代码 编译环境如下 Thread model: posix gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5) -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- 正文: 读优先的读写锁策略 假设对于一个共享的数据区来说,读/写都需要,而写的并发又可能比较频繁一点,如果按照普通的mutex思路可能是以下结构: lock() Write()/Read() Unlock() 对于write来说,对于共享的形式是独占(shared-exclusive);但对于读锁来说对于共享的形式是共享锁(shared lock),即不需要mutex的互斥保护。那么以上的写法中,对于读的情况效率大大的打折。比如一个银行帐户,多个线程可以同时读出某个帐户的收支结余,但是一旦有一个线程需要更新某个给定收支结余,该线程就必须等待所有读出者完成该收支结余的读出,然后只允许该更新线程修改这个收支结余。那么在实现这个策略时,必须遵循以下三个条件 1、 保证并发的读取,不对读取做什么界限的限制,仅仅以一个计数器来表示当前的读取量 2、 有写入线程时,保证写入线程的独立执行 3、 在有写入线程发生,以这个写入线程时间点为界限,之前的等待读线程要执行完,之后的读线程处于等待状态 那么可以设置这么一个结构体` typedef struct { pthread_mutex_t rw_mutex; /* basic lock on this struct */ pthread_cond_t rw_condreaders; /* for reader threads waiting */ pthread_cond_t rw_condwriters; /* for writer threads waiting */ int rw_magic; /* for error checking */ int rw_nwaitreaders; /* the number waiting */ int rw_nwaitwriters; /* the number waiting */ int rw_refcount; } pthread_RWlock_t; /* *rw_nwaitreaders:当前有读的线程工作时,写的线程阻塞,并且该变量自增 *rw_refcount:表示锁的工作状态,如果是写用-1表示,如果当前无工作状态用0表示,读的 *状态为大于1,其数量代表了当前的读的并发数 */ 再设置两个获取读/写锁函数: int pthread_RWlock_rdlock (pthread_RWlock_t *pRw); int pthread_RWlock_wrlock (pthread_RWlock_t *pRw); 流程的判断变为如下的工作流程,这里我先把图给出,问题统一放在后面分析 获取读/写锁两个函数大体流程如下: pthread_RWlock_wrlock函数流程如下: pthread_RWlock_rdlock函数流程如下: 问题一:先说明一个技巧 这里用了phtread_cond_wait(), 防止一个不断的轮询过程,代码是这样的 while( pRw->rw_refcount != 0 ) { pRw->rw_nwaitwriters++; iResult = pthread_cond_wait(&pRw->rw_condwriters, &pRw->rw_mutex); pRw->rw_nwaitwriters--; if( iResult != 0 ) { break; } } 注意,这里用了个while.在多线程的情况下,有多个cond_wait进行等待,当rw_mutex的锁被释放时,可能被其他线程抢到(即不保证被本身的线程取到),并对rw_refcount进行修改。如果不对rw_refcount进行判断而直接执行,就可能造成数据的不准确。 问题二:获取读锁时对rw_refcount!= 0的判断 这要和读锁的rw_refcount <0和rw_nwaitwriters>0两个条件结合起来进行解释。首先假设当前有多个线程进行工作,这时有个写线程进来,发生阻塞,waitwriters自加。这个时候比这个写线程后来的读线程检测到rw_nwaitwriters>0而发生阻塞,在这之前的读线程仍在工作,这里我们还需要知道pthread_RWlock_unlock的部分工作代码 /* 对读锁的释放为:如果是读锁,refcount减1,信号通知 对写锁的释放为:如果是写锁,refconut直接置0,信号通知 */ if( pRw->rw_refcount > 0 ) { pRw->rw_refcount--; } else if( pRw->rw_refcount == -1 ) { pRw->rw_refcount = 0; } 看吧,把之前对读锁的获取时rw_refcount++和这里的rw_refcount—结合起来,可得知,当读线程都工作完毕时,rw_refcount必然会等于0,这也满足了我们开头提出的三个条件中的最后一条。那么剩下的pthread_RWlock_unlock工作代码如下: if( pRw->rw_nwaitwriters > 0 ) { /* 防止发送空的signal */ if( pRw->rw_refcount == 0 ) { iResult = pthread_cond_signal(&pRw->rw_condwriters); } } else if( pRw->rw_nwaitreaders > 0 ) { iResult = pthread_cond_broadcast(&pRw->rw_condreaders); } 首先要先判断当前是否有写的线程等待,这里并不是把优先权给写的线程,而是在有大量的并发读线程下,必须给写的线程点机会,否则它永远都执行不了。这里你可能对pRw->rw_refcount == 0有点疑问。作者的原意可能是这样的:一个写线程在工作中,另一个写线程刚好释放完,准备通知另一个正在写的线程,但这个时候>rw_refcount仍等于-1,通知是无意义的。其实我也是,我认为不会触发这个条件。因为写的线程是确保只有一个执行,而在释放锁的时候已经确保pRw->rw_refcount = 0,当然这会在我代码验证后给出。 好了,现在我们以文字的形式整理下所有的思路。 假设有3个读线程进行并发,那么它们一开始没有检测到rw_refcount <0和rw_nwaitwriters>0,于是他们很欢快的取的读的权限进行并发的读取,假设这个读取的时间很长。。。。这个时候一条写线程进入,它判断rw_refcount不等于0,于是进行阻塞。 在这个时间里,又有3个读线程发生,但是它们检测到了nwaitwriters大于0,也进行阻塞。。终于,最早的3个读线程工作完了,释放完后,通知正在等待的写线程。写线程工作完后,这时候没有其他的写线程进入,所以pthread_cond_broadcast发生了作用,它通知了最后进入的3个读线程。。。 一个大问题,线程被取消掉的死锁 假设有两个线程,读和写,首先保证thread1的先执行 void *thread1(void *) { int iResult; CRWLock.pthread_RWlock_rdlock(&rwlock); cout<<"thread 1 got a read lock"<<endl; sleep(3); /*让thread2阻塞在pthread_RWlock_wrlock()*/ pthread_cancel(tid2); sleep(3); iResult = CRWLock.pthread_RWlock_unlock(&rwlock); return 0; } void *thread2(void *) { cout<<"thread 2 trying to obtain a write lock"<<endl; CRWLock.pthread_RWlock_wrlock(&rwlock); cout<<"thread2 got a write lock"<<endl; sleep(1); CRWLock.pthread_RWlock_unlock(&rwlock); return 0; } 这段代码的工作模式如下:thread1先获取一个读锁,睡眠3秒,thread2阻塞在获取写锁上,thread1取消掉thread2,最后thread1调用unlock函数。这时候阻塞在了unlock里。 为什么?这得了解一个线程的知识,pthread_cond_wait函数发生时,会自动释放掉一个mutex,以运行其他线程进入等待。(如果没有释放这个mutex,那么就没有什么并发等待的意义了,大家都阻塞在最外面的lock),当thread1进行取消时,这个mutex没有释放(或者看成pthread_cond_wait之前的释放mutex没什么效果)。那么在unlock里,也需要对这个mutex进行访问,而造成了死锁。这也是许多多线程里死锁的隐患。具体的解决方案见代码,其实很简单,就是压入一个释放函数,如果线程发生意外直接返回则调用该函数。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |