对使用CRITICAL_SECTION实现读写锁的质疑!!!

sealdh 2011-11-02 11:39:25
加精

最近找了很多在WINDOWS平台下实现读写锁的代码,基本上大同小异,最经典的是使用CRITICAL_SECTION来做。
因为CRITICAL_SECTION效率比内核对象要高,所以我也更倾向于尽量使用CRITICAL_SECTION,少使用内核对象。但这样实现我发现有很大的问题,先看下面的代码:



void RWLock::readLock()
{
EnterCriticalSection(&write_lock); //进入写锁临界区
EnterCriticalSection(&v_reader_count); //进入reader_count互斥临界区
reader_count ++;
if( reader_count == 1 )
EnterCriticalSection(&read_lock); //是第一个读者 进入读锁临界区

LeaveCriticalSection(&v_reader_count); //离开reader_count互斥临界区
LeaveCriticalSection(&write_lock); //离开写锁临界区
}

void RWLock::readUnLock()
{
EnterCriticalSection(&v_reader_count); //进入reader_count互斥临界区
reader_count --;
if( reader_count == 0 )
LeaveCriticalSection(&read_lock); //是最后一个读者 离开读锁临界区
LeaveCriticalSection(&v_reader_count); //离开reader_count互斥临界区
}

在这里,我们只关注:read_lock锁:
我们看到,在readLock函数中,如果是第一个读者(即:if( reader_count == 1 ))时,就执行:EnterCriticalSection(&read_lock);以便进入读锁临界区;而在readUnLock函数中,当没有读者时(即:if( reader_count == 0 ) )时,执行:LeaveCriticalSection(&read_lock);

这种实现表面上符合逻辑,没有问题。但却不符合MSDN相关规范。因为:对于EnterCriticalSection和LeaveCriticalSection,MSDN规范要求我们应该是“成对使用”,即:在一个线程中Enter,就应该在同一个线程中Leave。注意这里我们强调的是“同一个线程”。

但是,如果按照上述代码,Enter和Leave很有可能不在同一个线程中执行,
比如:
在线程1中先执行readLock,
然后线路2中执行readLock,
然后线程1结束,
最后线程2结束。

如果是这样的执行顺序,那么就是在线程1中执行EnterCriticalSection,而在线程2中执行LeaveCriticalSection。这显然和规范相悖。这种情况下后果是不可预知的,即:有可能正确,但不保证正确,甚至可能会造成死锁。


我想知道这么做是否保险,或者是否有足够的理论支撑。如果不能这么用,那所有类似的实现读写锁的操作都是错误的!


附:MSDN相关描述:
"If a thread calls LeaveCriticalSection when it does not have ownership of the specified critical section object, an error occurs that may cause another thread using EnterCriticalSection to wait indefinitely."




...全文
4363 129 打赏 收藏 转发到动态 举报
写回复
用AI写文章
129 条回复
切换为时间正序
请发表友善的回复…
发表回复
masm611masm 2013-12-05
  • 打赏
  • 举报
回复
楼主,这个问题解决了没有,我也觉得临界区有这个问题,但是Mutex就没有这个问题 即临界区 不合理地实现了读写者问题 mutex 合理地没有实现
LMXEQ5 2013-07-05
  • 打赏
  • 举报
回复
修改如下: //OwningThread为写者线程变量 //加读锁 void readLock() { if (OwningThread == GetCurrentThread() || WaitForSingleObject(read_lock, INFINITE) == WAIT_OBJECT_0) { if (InterlockedIncrement(&reader_count) == 1) EnterCriticalSection(&write_lock); } } //解读锁 void readUnLock() { if (InterlockedDecrement(&reader_count) == 0) LeaveCriticalSection(&write_lock); } //加写锁,写优先的 void writeLock() { EnterCriticalSection(&write_lock); OwningThread = GetCurrentThread(); ResetEvent(read_lock); //挂起读线程 } //解写锁 void writeUnLock() { SetEvent(read_lock); //激活读线程 LeaveCriticalSection(&write_lock); }
LMXEQ5 2013-07-05
  • 打赏
  • 举报
回复
昨天试了一下,最后一个在 writeLock readLock readUnLock writeUnLock 的执行序列下会死锁,加锁前最好加个是否当前线程的判断
LMXEQ5 2013-07-04
  • 打赏
  • 举报
回复
我只是想和大家交流一下,言语冒犯还请海涵 这样也可以提高读操作的效率,不知道有没有遗漏的问题,希望指正: //read_lock = CreateEvent(NULL, true, true, NULL); void RWLock::readLock() { if (WaitForSingleObject(read_lock, INFINITE) == WAIT_OBJECT_0) then { if (InterlockedIncrement(&reader_count) == 1) EnterCriticalSection(&write_lock); } } void RWLock::readUnLock() { if (InterlockedDecrement(&reader_count) == 0); LeaveCriticalSection(&write_lock); } void RWLock::writeLock() { ResetEvent(read_lock); EnterCriticalSection(&write_lock); } void RWLock::writeUnLock() { LeaveCriticalSection(&write_lock); SetEvent(read_lock); }
LMXEQ5 2013-07-03
  • 打赏
  • 举报
回复
下面的方法应该也可以,也更简单: void RWLock::readLock() { if(InterlockedIncrement(reader_count) == 1) EnterCriticalSection(&write_lock); } void RWLock::readUnLock() { if (InterlockedDecrement(reader_count) == 0); LeaveCriticalSection(&write_lock); } void RWLock::writeLock() { EnterCriticalSection(&write_lock); } void RWLock::writeUnLock() { LeaveCriticalSection(&write_lock); }
LMXEQ5 2013-07-03
  • 打赏
  • 举报
回复
这个读锁和解读锁操作加的互斥太多,读操作本来就多的情况下会影响读的性能,将读计数改成原子操作应该会好一点,不知道这样作会不会有潜在问题,欢迎拍砖 void RWLock::readLock() { EnterCriticalSection(&write_lock); if(InterlockedIncrement(reader_count) == 1) EnterCriticalSection(&read_lock); LeaveCriticalSection(&write_lock); } void RWLock::readUnLock() { if (InterlockedDecrement(reader_count) == 0); LeaveCriticalSection(&read_lock); }
dzz10 2013-07-03
  • 打赏
  • 举报
回复
临界区是保护代码段的!保证在进入和出去中间的代码在某一时刻只有一个线程在执行! 还有就是回帖的语气太。。。。让人不爽
LMXEQ5 2013-07-03
  • 打赏
  • 举报
回复
后一种不能作到写优先
wodetaiyang14 2013-01-31
  • 打赏
  • 举报
回复
我是看到楼主这样的傻逼二逼 特意来回帖的
baichuan 2012-03-19
  • 打赏
  • 举报
回复
我怎么无法编辑我的帖子???
上个帖子因为测试代码有误。修改了之后,测试了一个晚上,7个线程,每个线程占用一核,连续互斥几百亿次没发现CriticalSection有问题。
baichuan 2012-03-18
  • 打赏
  • 举报
回复
测试结论:
1,CriticalSection不适合多核CPU。或者说未找到多核环境下使用CriticalSection的方法
2,CriticalSection的效率基本上是Mutex(使用CreateMutex)的两倍

哪位高手知道问题1的答案,或者看过MSDN的明确说明,请不吝赐教
baichuan 2012-03-18
  • 打赏
  • 举报
回复
高手没多少。这个问题不需要高手。

互斥量就是为了多线程服务的,楼主这个担心,肯定是因为没有理解互斥这个概念。一旦一个互斥量被某个线程Lock,换言之,该线程进入了该互斥量保护的代码段,那么其他线程是不可能进入这个代码段的,直到进入者Unlock。所以Lock/UnLock肯定是被同一个线程成对调用的。

你们忽略了MSDN的另一条:同一个线程,多次Lock同一个互斥量,也只需要Unlock一次。换言之,一个线程Lock它已经Lock的互斥量,将被OS忽略。

我刚才写程序测试Mutex和CriticalSection的差别,后者效率高很多,但是似乎在多CPU环境下,不能完全互斥的现象。具体怎么得出这个猜疑,或者这个猜疑是否是正确的,还在研究。我打等会算用单核电脑试一下。

并不必须每个资源一个互斥量。但是,尤其在多核情况下,效率明显低于每个资源一个互斥量。可能是数量级上的差别。

yanyuchonglou 2012-02-22
  • 打赏
  • 举报
回复
[Quote=引用 99 楼 zwb0540822 的回复:]
首先,在一个多线程环境中,有多个可能会同时读写的变量,用这种readLock(),writeLock本身就是错误的。
建议先windows API 方式使用临界区。
最简单的方法,每一个多线程都读写的全局变量,都会有一个全局的临界区g_cs(CRITICAL_SECTION),当读写一个全局变量时,先进入指定的临界区,用完后离开离开指定的临界区。
自己写锁的话,就是每一个需要解\锁的公共资源……
[/Quote]

此猿才是真猿也!!
yanyuchonglou 2012-02-22
  • 打赏
  • 举报
回复
临界区是初级的IPC机制,哪来的效率高一说啊。

临界区保护的是资源,是同一个P下的多个T间用的,但却是在能用的范围里用起来最简单的一种。
rularys 2011-12-20
  • 打赏
  • 举报
回复
楼猪的意思大概明白一些,看得出LZ很现实。不过LZ可能没有意识到这一点:现在当前也许真的没问题,但不保证将来,新版本的系统也会没问题;这样的用法也算是另一种“未文档化”的意思。通常未文档化意味着不稳定,因为这没有像公开的服务那样的保证。但也有很多有名的软件在用着未文档化的服务,照样能很好的生存——有时候未文档化也意味着功能独特。只是利用它前提就是,必须要像LZ这样辛苦地求证其正确性,否则也不敢轻易使用。

扯远了,还是那句话:实践出真知。
safeqq2 2011-12-19
  • 打赏
  • 举报
回复
给建议不给代码:
设临界区保护、设自定义锁结构、设看门狗线程。
作用:
临界区用来获取全局控制权限,自定义结构用来控制访问状态。修改自定义结构读和写标志必须获取临界 区保护,其它执行根据锁结构判断。代码和实现原理自由控制。 看门狗线程访问产生线程死锁状态,一定时间片刻内,线程不退出临界区占用由看门狗复位临界区。

效率不会有太大的降低,减少对临界区函数反复调用,临界区函数基于原子锁,反复调用也需要进过OS处理,效率降低很明显。 使用临界区一般是为了保护重要的变量和函数或结构的单一访问,如果读写什么都依赖它,反正降低了效率。原子锁的获取是基于无限循环的,在一个原子执行片刻内,所以操作都必须等待。临界区用用就可以,用多了反正影响整体OS包括程序性能。
mfcdeclare 2011-12-19
  • 打赏
  • 举报
回复
kyee和kyotrue,还有什么要补充的吗?期待。
很土 2011-12-19
  • 打赏
  • 举报
回复
[Quote=引用 112 楼 mfcdeclare 的回复:]
kyee和kyotrue,还有什么要补充的吗?期待。
[/Quote]

古人云:“尽信书不如无书!”
小平说:“不管黑猫白猫,会抓老鼠的就是好猫!”

其实,临界区也好,Event也罢,能够稳定和高效运行就是硬道理。
临界区在一个线程中Enter,在另一个线程中Leave,理论上应该行得通,可以作为二个线程之间同步衔接时使用。具体是否能行,实践是检验真理的唯一标准,虽然实践不能完全覆盖理论。

倘若你觉得使用临界区实现的读写锁不放心,那就使用可靠的Event方式,不要拘泥于某种形式!我个人觉得应该使用可靠稳定的方式去写程序,可以使用新的方式尝试应用,若真的稳定了就变成可靠了,这就是知识的积累!
awperpvip 2011-12-17
  • 打赏
  • 举报
回复
mark
mfcdeclare 2011-12-15
  • 打赏
  • 举报
回复

此贴讨论的时间已经很长了,我初步打算本月底(2011年12月31日)结贴,在这里先感谢各位的热情参与。

如果对此问题还有未尽事宜,希望大家尽快回复,发表自己的观点,以正视听。




加载更多回复(102)

16,473

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧