`
kanwoerzi
  • 浏览: 1644669 次
文章分类
社区版块
存档分类
最新评论

可递归锁与非递归锁

 
阅读更多
可递归锁与非递归锁<wbr style="line-height:25px"><br style="line-height:25px"> 转载自《线程同步之利器(1)——可递归锁与非递归锁》<br style="line-height:25px"><a target="_blank" rel="nofollow" href="http://blog.csdn.net/zouxinfox/archive/2010/08/25/5838861.aspx" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">http://blog.csdn.net/zouxinfox/archive/2010/08/25/5838861.aspx</a><br style="line-height:25px"><span style="color:#000080; line-height:25px"><wbr style="line-height:25px">最常见的进程/线程的同步方法有</wbr></span><span style="color:#993300; line-height:25px">互斥锁</span><span style="color:#000080; line-height:25px">(或称</span><span style="color:#99cc00; line-height:25px">互斥量Mutex)</span><span style="color:#000080; line-height:25px">,</span><span style="color:#808000; line-height:25px">读写锁(rdlock)</span><span style="color:#000080; line-height:25px">,</span><span style="color:#339966; line-height:25px">条件变量(cond)</span><span style="color:#000080; line-height:25px">,</span><span style="color:#008000; line-height:25px">信号量(Semophore)</span><span style="color:#000080; line-height:25px">等<wbr style="line-height:25px">。</wbr></span><br style="line-height:25px"> 在Windows系统中,临界区(CriticalSection)和事件对象(Event)也是常用的同步方法。<br style="line-height:25px"> 简单的说,互斥锁保护了一个临界区,在这个临界区中,一次最多只能进入一个线程。<br style="line-height:25px"> 如果有多个进程在同一个临界区内活动,就有可能产生竞态条件(racecondition)导致错误。<br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#800000; line-height:25px">读写锁</span><wbr style="line-height:25px">从广义的逻辑上讲,也可以认为是一种共享版的<wbr style="line-height:25px"><span style="color:#ff6600; line-height:25px">互斥锁</span><wbr style="line-height:25px">。如果对一个临界区大部分是读操作而只有少量的写操作,<br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#800000; line-height:25px">读写锁</span><wbr style="line-height:25px">在一定程度上能够降低线程互斥产生的代价。<br style="line-height:25px"> 条件变量允许线程以一种无竞争的方式等待某个条件的发生。当该条件没有发生时,线程会一直处于休眠状态。<br style="line-height:25px"> 当被其它线程通知条件已经发生时,线程才会被唤醒从而继续向下执行。条件变量是比较底层的同步原语,直接使用的情况不多,<br style="line-height:25px"> 往往用于实现高层之间的线程同步。使用<wbr style="line-height:25px"><span style="color:#993300; line-height:25px">条件变<wbr style="line-height:25px">量</wbr></span>的一个经典的例子就是线程池(ThreadPool)了。<br style="line-height:25px"> 在学习操作系统的进程同步原理时,讲的最多的就是<span style="line-height:25px"><wbr style="line-height:25px">信号量</wbr></span><wbr style="line-height:25px">了。通过精心设计信号量的PV操作,<br style="line-height:25px"> 可以实现很复杂的进程同步情况(例如经典的哲学家就餐问题和理发店问题)。<br style="line-height:25px"> 而现实的程序设计中,却极少有人使用信号量。能用信号量解决的问题似乎总能用其它更清晰更简洁的<wbr style="line-height:25px"><span style="color:#000080; line-height:25px">设计手段去代替信号量</span><wbr style="line-height:25px">。<br style="line-height:25px"> 本系列文章的目的并不是为了讲解这些同步方法应该如何使用(AUPE的书已经足够清楚了)。更多的是讲解很容易被人忽略的一些关于锁的概念,<br style="line-height:25px"> 以及比较经典的使用与设计方法。文章会涉及到递归锁与非递归锁(recursivemutex和non-recursivemutex),<br style="line-height:25px"><span style="color:#99cc00; line-height:25px">区域锁(ScopedLock)</span>,<span style="color:#808000; line-height:25px">策略锁(StrategizedLocking)</span>,<span style="color:#339966; line-height:25px">读写锁与条件变量</span>,<span style="color:#008000; line-height:25px">双重检测锁(DCL)</span>,<span style="color:#800080; line-height:25px">锁无关的数据结构(Lockingfree)</span>,<br style="line-height:25px"><span style="color:#ff99cc; line-height:25px">自旋锁</span>等等内容,希望能够抛砖引玉。<br style="line-height:25px"> 那么我们就先从递归锁与非递归锁说开去吧:)<br style="line-height:25px"> 1<span style="line-height:25px"><wbr style="line-height:25px">可递归锁与非递归锁</wbr></span><wbr style="line-height:25px"><br style="line-height:25px"> 1.1概念<br style="line-height:25px"> 在所有的线程同步方法中,恐怕互斥锁(mutex)的出场率远远高于其它方法。互斥锁的理解和基本使用方法都很容易,这里不做更多介绍了。<br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#993300; line-height:25px">Mutex</span><span style="color:#000080; line-height:25px">可以分为</span><span style="color:#ff9900; line-height:25px">递归锁(recursivemutex)</span><span style="color:#000080; line-height:25px">和</span><span style="color:#ff9900; line-height:25px">非递归锁(non-recursivemutex)</span><span style="color:#000080; line-height:25px">。可递归锁也可称为可重入锁(reentrantmutex)</span><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">,</span><br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#ff9900; line-height:25px">非递归锁</span><span style="color:#000080; line-height:25px">又叫不可重入锁<wbr style="line-height:25px">(non-reentrantmutex)。</wbr></span><br style="line-height:25px"> 二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。<br style="line-height:25px"> Windows下的Mutex和CriticalSection是可递归的。Linux下的pthread_mutex_t锁默认是非递归的。<br style="line-height:25px"> 可以显示的设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t设为递归锁。<br style="line-height:25px"> 在大部分介绍如何使用互斥量的文章和书中,这两个概念常常被忽略或者轻描淡写,造成很多人压根就不知道这个概念。<br style="line-height:25px"> 但是如果将这<wbr style="line-height:25px"><span style="color:#000080; line-height:25px">两种锁误用</span><wbr style="line-height:25px">,很可能会造成程序的<span style="color:#000080; line-height:25px"><wbr style="line-height:25px">死锁</wbr></span><wbr style="line-height:25px">。请看下面的程序。<br style="line-height:25px"> MutexLockmutex;<br style="line-height:25px"> voidfoo()<br style="line-height:25px"> {<br style="line-height:25px"> mutex.lock();<br style="line-height:25px"> //dosomething<br style="line-height:25px"> mutex.unlock();<br style="line-height:25px"> }<br style="line-height:25px"> voidbar()<br style="line-height:25px"> {<br style="line-height:25px"> mutex.lock();<br style="line-height:25px"> //dosomething<br style="line-height:25px"> foo();<br style="line-height:25px"> mutex.unlock();<br style="line-height:25px"> }<br style="line-height:25px"> foo函数和bar函数都获取了同一个锁,而bar函数又会调用foo函数。如果MutexLock锁是个非递归锁,则这个程序会立即死锁。<br style="line-height:25px"> 因此在为一段程序加锁时要格外小心,否则很容易因为这种调用关系而造成死锁。<br style="line-height:25px"> 不要存在侥幸心理,觉得这种情况是很少出现的。当代码复杂到一定程度,被多个人维护,调用关系错综复杂时,<br style="line-height:25px"> 程序中很容易犯这样的错误。庆幸的是,这种原因造成的死锁很容易被排除。<br style="line-height:25px"> 但是这并不意味着应该用<span style="color:#ff9900; line-height:25px">递归锁</span>去代替非递归锁。<span style="color:#ff9900; line-height:25px">递归锁</span>用起来固然简单,但往往会隐藏某些代码问题。<br style="line-height:25px"> 比如调用函数和被调用函数以为自己拿到了锁,都在修改同一个对象,这时就很容易出现问题。因此在能使用<span style="color:#ff9900; line-height:25px">非递归锁</span>的情况下,<br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">应该尽量使用</span><span style="color:#ff6600; line-height:25px">非递归锁</span><wbr style="line-height:25px">,因为<span style="color:#000080; line-height:25px"><wbr style="line-height:25px">死锁相对来</wbr></span><wbr style="line-height:25px">说,更容易通过<wbr style="line-height:25px"><span style="color:#000080; line-height:25px">调试</span><span style="line-height:25px">发</span><wbr style="line-height:25px">现。程序设计如果有问题,应该暴露的越早越好。<br style="line-height:25px"> 1.2<span style="line-height:25px"><wbr style="line-height:25px">如何避免</wbr></span><wbr style="line-height:25px"><br style="line-height:25px"> 为了避免上述情况造成的死锁,AUPEv2一书在第12章提出了一种设计方法。即如果一个函数<span style="line-height:25px"><wbr style="line-height:25px">既有可能在已加锁的情况下使用,<br style="line-height:25px"> 也有可能在未加锁的情况下使用,往往将这个函数拆成两个版本---加锁版本和不加锁版本(添加nolock后缀)</wbr></span><wbr style="line-height:25px">。<br style="line-height:25px"> 例如将foo()函数拆成两个函数。<br style="line-height:25px"> //不加锁版本<br style="line-height:25px"> voidfoo_nolock()<br style="line-height:25px"> {<br style="line-height:25px"> //dosomething<br style="line-height:25px"> }<br style="line-height:25px"> //加锁版本<br style="line-height:25px"> voidfun()<br style="line-height:25px"> {<br style="line-height:25px"> mutex.lock();<br style="line-height:25px"> foo_nolock();<br style="line-height:25px"> mutex.unlock();<br style="line-height:25px"> }<br style="line-height:25px"> 为了接口的将来的扩展性,可以将bar()函数用同样方法拆成bar_withou_lock()函数和bar()函数。<br style="line-height:25px"> 在DouglasC.Schmidt(ACE框架的主要编写者)的“StrategizedLocking,Thread-safeInterface,andScopedLocking”论文中,<br style="line-height:25px"> 提出了一个基于C++的线程安全接口模式(Thread-safeinterfacepattern),与AUPE的方法有异曲同工之妙。<br style="line-height:25px"> 即在设计接口的时候,每个函数也被拆成两个函数,<wbr style="line-height:25px"><span style="color:#000080; line-height:25px">没有使用锁的函数是private或者protected类型</span><wbr style="line-height:25px">,<br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">使用锁的的函数是public类型</span><wbr style="line-height:25px">。接口如下:<br style="line-height:25px"> classT<br style="line-height:25px"> {<br style="line-height:25px"> public:<br style="line-height:25px"> foo();//加锁<br style="line-height:25px"> bar();//加锁<br style="line-height:25px"> private:<br style="line-height:25px"> foo_nolock();<br style="line-height:25px"> bar_nolock();<br style="line-height:25px"> }<br style="line-height:25px"> 作为对外接口的public函数只能调用无锁的私有变量函数,而不能互相调用。在函数具体实现上,这两种方法基本是一样的。<br style="line-height:25px"> 上面讲的两种方法在通常情况下是没问题的,可以有效的避免死锁。但是有些复杂的回调情况下,则必须使用递归锁。<br style="line-height:25px"> 比如foo函数调用了外部库的函数,而外部库的函数又回调了bar()函数,此时必须使用递归锁,否则仍然会死锁。<br style="line-height:25px"> AUPE一书在第十二章就举了一个必须使用递归锁的程序例子。<br style="line-height:25px"> 1.3<span style="line-height:25px"><wbr style="line-height:25px">读写锁的递归性</wbr></span><wbr style="line-height:25px"><br style="line-height:25px"> 读写锁(例如Linux中的pthread_rwlock_t)提供了一个比互斥锁更高级别的并发访问。<br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">读写锁的实现往往是比互斥锁要复杂的,因此开销通常也大于互斥锁</span><wbr style="line-height:25px">。<br style="line-height:25px"> 在我的Linux机器上实验发现,单纯的写锁的时间开销差不多是<wbr style="line-height:25px"><span style="color:#000080; line-height:25px">互斥锁十倍左右</span><wbr style="line-height:25px">。<br style="line-height:25px"> 在系统不支持读写锁时,有时需要自己来实现,通常是用条件变量加读写计数器实现的。有时可以根据实际情况,<br style="line-height:25px"> 实现读者优先或者写者优先的读写锁。<br style="line-height:25px"><span style="color:#800000; line-height:25px">读写锁</span>的优势往往展现在读操作很频繁,而写操作较少的情况下。如果写操作的次数多于读操作,并且写操作的时间都很短,<br style="line-height:25px"> 则程序很大部分的开销都花在了读写锁上,这时反而用互斥锁效率会更高些。<br style="line-height:25px"> 相信很多同学学习了读写锁的基本使用方法后,都写过下面这样的程序(Linux下实现)。 <div style="line-height:25px">程序1<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">#include&lt;pthread.h&gt;<br style="line-height:25px"> int</span><span style="color:#ff6600; line-height:25px">main</span><span style="color:#3366ff; line-height:25px">()<br style="line-height:25px"> {<br style="line-height:25px"> pthread_rwlock_trwl;<br style="line-height:25px"> pthread_rwlock_rdlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_wrlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_unlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_unlock(&amp;rwl);<br style="line-height:25px"> return-1;<br style="line-height:25px"> }</span><br style="line-height:25px"> 程序2<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">#include&lt;pthread.h&gt;<br style="line-height:25px"> intmain()<br style="line-height:25px"> {<br style="line-height:25px"> pthread_rwlock_trwl;<br style="line-height:25px"> pthread_rwlock_wrlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_rdlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_unlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_unlock(&amp;rwl);<br style="line-height:25px"> return-1;<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">你会很疑惑的发现,程序1先加读锁,后加写锁,按理来说应该阻塞,但程序却能顺利执行。而程序2却发生了阻塞</wbr></span><wbr style="line-height:25px">。<br style="line-height:25px"> 更近一步,你能说出执行下面的程序3和程序4会发生什么吗?<br style="line-height:25px"> /*程序3*/<br style="line-height:25px"> #include&lt;pthread.h&gt;<br style="line-height:25px"> intmain()<br style="line-height:25px"> {<br style="line-height:25px"> pthread_rwlock_trwl;<br style="line-height:25px"> pthread_rwlock_rdlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_rdlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_unlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_unlock(&amp;rwl);<br style="line-height:25px"> return-1;<br style="line-height:25px"> }<br style="line-height:25px"> /*程序4*/<br style="line-height:25px"> #include&lt;pthread.h&gt;<br style="line-height:25px"> intmain()<br style="line-height:25px"> {<br style="line-height:25px"> pthread_rwlock_trwl;<br style="line-height:25px"> pthread_rwlock_wrlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_wrlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_unlock(&amp;rwl);<br style="line-height:25px"> pthread_rwlock_unlock(&amp;rwl);<br style="line-height:25px"> return-1;<br style="line-height:25px"> }<br style="line-height:25px"> 在POSIX标准中,<wbr style="line-height:25px"><span style="color:#000080; line-height:25px">如果一个线程先获得写锁,又获得读锁,则结果是无法预测的。这就是为什么程序1的运行出人所料</span><wbr style="line-height:25px">。<br style="line-height:25px"> 需要注意的是,<wbr style="line-height:25px"><span style="color:#000080; line-height:25px">读锁是递归锁(即可重入)<wbr style="line-height:25px">,<wbr style="line-height:25px">写锁是非递归锁(即不可重入)</wbr></wbr></span><wbr style="line-height:25px">。因此程序3不会死锁,而程序4会一直阻塞。<br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#000080; line-height:25px">读写锁是否可以递归会可能随着平台的不同而不同</span><wbr style="line-height:25px">,因此为了避免混淆,<br style="line-height:25px"> 建议在不清楚的情况下尽量避免在同一个线程下混用读锁和写锁。<br style="line-height:25px"> 在系统不支持递归锁,而又必须要使用时,就需要自己构造一个递归锁。通常,<span style="color:#000080; line-height:25px">递<wbr style="line-height:25px">归锁是在非递归互斥锁加引用计数器来实现的</wbr></span><wbr style="line-height:25px">。<br style="line-height:25px"> 简单的说,<span style="color:#003366; line-height:25px">在加锁前,先判断上一个加锁的线程和当前加锁的线程是否为同一个。如果是同一个线程,则仅仅引用计数器加1。<br style="line-height:25px"> 如果不是的话,则引用计数器设为1,则记录当前线程号,并加锁</span>。关于此的一个实现请参照《<strong><a title="阅读全文" target="_blank" href="http://hubingforever.blog.163.com/blog/static/171040579201071225542246/" style="color:rgb(207,121,28); line-height:25px; text-decoration:none">多线程中递归锁的实现</a></strong>》</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr> </div> <div style="line-height:25px">需要注意的是,如果自己想写一个递归锁作为公用库使用,就需要考虑更多的异常情况和错误处理,让代码更健壮一些。</div> </wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
分享到:
评论

相关推荐

    锁链 非递归做法C++.zip

    但是你能否找到一个非递归算法呢? 【输入输出】 输入:环的总数n。 输出:为尽量体现程序输出结果的层次,要求按照从n、n-1、n-2、……、1的顺序,将移除掉n号环的全部过程作为一个段落输出,然后将移除n-1号环的...

    Java并发教程.md

    * [可重入锁(递归锁)](#可重入锁递归锁) * [偏向锁](#偏向锁) * [轻量级锁](#轻量级锁) * [自旋锁](#自旋锁) * [自适应自旋锁](#自适应自旋锁) * [锁消除](#锁消除) * [锁粗化](#锁粗化) * [死锁](#死锁) *...

    常见的Java笔试题-JVM-JUC-Core:JUCJVM核心知识点

    可重入锁/递归锁 锁的配对 自旋锁 读写锁/独占/共享锁 Synchronized和Lock的区别 CountDownLatch/CyclicBarrier/Semaphore CountDownLatch 枚举类的使用 CyclicBarrier Semaphore 阻塞队列 SynchronousQueue ...

    debug_mutex:C / C ++调试互斥库-开源

    您可能做错了:-死锁-无效的互斥锁离开顺序-线程关闭,同时仍保持互斥锁-销毁锁定的互斥锁-(Un)锁定销毁的互斥锁-解锁未锁定的互斥锁-解锁由另一个线程锁定的互斥锁-锁定非递归,锁定的互斥锁-在应用程序终止之前...

    Java并发编程实战

    15.3.2 性能比较:锁与原子变量267 15.4 非阻塞算法270 15.4.1 非阻塞的栈270 15.4.2 非阻塞的链表272 15.4.3 原子的域更新器274 15.4.4 ABA问题275 第16章 Java内存模型277 16.1 什么是内存模型,为什么...

    JAVA并发编程实践_中文版(1-16章全)_1/4

    真正的Addison-Wesley 出品的Java Concurrency in Practice 中文版 目录回到顶部↑ 代码清单 序 第1章 介绍 ...第15章 原子变量与非阻塞同步机制 第16章 java存储模型 附录a 同步annotation 参考文献 索引

    Java并发编程part2

    中文完整版的Java并发编程实践PDF电子书 作者:Brian Gogetz Tim Peierls Joshua Bloch Joseph Bowbeer David Holmes Doug Lea 译者:韩锴 方秒 ...第15章 原子变量与非阻塞同步机制 第16章 java存储模型

    Java并发编程实践part1

    中文完整版的Java并发编程实践PDF电子书 作者:Brian Gogetz Tim Peierls Joshua Bloch Joseph Bowbeer David Holmes Doug Lea 译者:韩锴 方秒 ...第15章 原子变量与非阻塞同步机制 第16章 java存储模型

    数学建模案例讲解 MATLAB数学建模培训PPT课件 数学建模中的常见算法 共111页.pptx

    95B 天车与冶炼炉的作业调度: 非线性规划、动态 规划、层次分析法、PETRI方法、图论方法、排队论方法 96A 最优捕鱼策略:微分方程、积分、非线性规划 96B 节水洗衣机:非线性规划 97A 零件参数设计:微积分、非线性...

    数学建模基础知识培训 数学建模培训资料 数学建模中的常用数学建模算法 共110页.ppt

    95B 天车与冶炼炉的作业调度: 非线性规划、动态规划、层次分析法、PETRI方法、图论方法、排队论方法 96A 最优捕鱼策略:微分方程、积分、非线性规划 96B 节水洗衣机:非线性规划 97A 零件参数设计:微积分、非线性...

    Java并发编程实践 PDF 高清版

    随着多核处理器的普及,使用并发成为构建高性能应用程序的关键。Java 5以及6在开发并发程序取得了显著的进步,提高了Java...第15章 原子变量与非阻塞同步机制 第16章 Java存储模型 附录A 同步Annotation 参考文献 索引

    Java并发编程(学习笔记).xmind

    性能与可伸缩性 概念 运行速度(服务时间、延时) 处理能力(吞吐量、计算容量) 可伸缩性:当增加计算资源时,程序的处理能力变强 如何提升可伸缩性 Java并发程序中的串行,主要来自独占的...

    Java学习题答案

    (15分) 主要相同点: Lock能完成synchronized所实现的所有功能.(其它不重要) 主要不同点: Lock有比synchronized更精确的线程语义和更好的性能(在相同点中回答此点也行) synchronized会自动释放锁....

    2号店 商城 系统

    ORA-00020: 超出最大进程数 () ORA-00021: 会话附属于其它某些进程;无法转换会话 ORA-00022: 无效的会话 ID;访问被拒绝 ORA-00023: 会话引用进程私用...ORA-00058: DB_BLOCK_SIZE 必须为才可安装此数据库 (非 )

    AI学习知识点.xmind

    递归函数及递归优化 常用内置函数/高级函数 项目案例: 约瑟夫环问题 常用库 时间库 并发库 科学计算库 Matplotlib可视化会图库 锁和线程 多线程变成 3. 机器学习 机器学习 理论概述 督导学习 逻辑...

    python入门到高级全栈工程师培训 第3期 附课件代码

    05 递归锁 06 同步对象event 07 信号量 08 线程队列 09 生产者消费者模型 10 多进程的调用 第35章 01 进程通信 02 进程池 03 协程 04 事件驱动模型 05 IO模型前戏 06 阻塞IO与非阻塞IO 07 select及触发方式 08 ...

    nova-extension-utils:用于开发Nova.app扩展的共享实用程序

    特征installWrappedDependencies 此功能提供并发安全,可重现,非全局污染的机制,用于安装由扩展程序执行的外部nodejs依赖项,而无需将其捆绑在扩展程序构件中并增加扩展程序的大小。 这在开发人员中特别有用,因为...

    数据库系统原理A.pdf

    递归实体 B.复合实体 C.弱实体 D.超类实体 6.关系规范化中的插入异常是指( ) A.不该插入的数据被插入 B.应该插入的数据没插入 C.插入了没有用的数据 D.插入了错误的数据 7.两个函数依赖集F和G等价的充分必要条件是...

    常见的Windows驱动程序开发可靠性问题

    • 既未缓冲也非直接的 I/O (METHOD_NEITHER) • 设备状态验证 • 清除和关闭例程 • 设备控制例程 • 同步 • 共享访问 • 锁和禁用 APC • 处理验证 • 请求创建和打开文件和设备 • 在设备命名...

Global site tag (gtag.js) - Google Analytics