秒杀多线程面试题系列
参考JustDoIT —— 大部分内容
对象 |
何时处于未触发状态 | 何时处于触发状态 | WaitForSingleObject副作用 | 内核对象 |
自动重置事件 | ResetEvent,PulseEvent或者等待成功 | SetEvent,PulseEvent | 重置通知 | √ |
手动重置事件 | ResetEvent,PulseEvent | SetEvent,PulseEvent | 没有 | √ |
自动重置可等待计时器 | CancelWaitableTimer或等待成功 | SetWaitableTimer | 重置计时器 | √ |
手动重置可等待计时器 | CancelWaitableTimer | SetWaitableTimer | 没有 | √ |
信号量 | 等待成功时 | 计数大于0的时候(ReleaseSemaphore) | 计数器减1 | √ |
互斥量 | 等待成功时 | 不为线程占用时(ReleaseMutex) | 把所有权交给线程 | √ |
关键段 | 等待成功时((Try)EnterCriticalSection) | 不为线程占用时(LeaveCriticalSection) | 把所有权交给线程 | × |
SRWLock | 等待成功时(AcquireSRWLock(Exclusive)) | 不为线程占用时(ReleaseSRWLock(Exclusive) | 把所有权交给线程 | × |
条件变量 | 等待成功(SleepConditionVariable*) | 被唤醒时(Wake(All)ConditionVariable) | 没有 | × |
同步与互斥的区别和联系
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
秒杀多线程第九篇 经典线程同步总结 关键段 事件 互斥量 信号量
当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。
(1).间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享CPU,共享I/O设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A在使用打印机时,其它线程都要等待。
(2).直接相互制约。这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。
间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步。
分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
注:因此,效率上,临界区比互斥量等要高。
用户模式下的方法有:原子操作、临界区、读写锁,条件变量
内核模式下的方法有:互斥量、信号量、事件(手动和自动),可等待计时器(手动和自动)
1、关于线程所有权属性:即某个线程获得该同步工具后,在他释放该工具前可以多次进入想访问的资源,一般来说具有所有权属性的工具不用于线程同步,只用于互斥,具体可以参考本文最前面的几篇博客
2、内核模式下的工具可以用于不同进程的线程之间的同步互斥,用户模式则只能用于相同进程的线程之间
3、只有互斥量在线程异常退出时,会释放对该工具的所有权,其他线程可以继续获取。其他的工具在线程异常退出时,其占有的工具不会释放,其他线程需要一直等待
4、事件分为自动和手动,如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。
百度百科
是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件。
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
一个典型的例子是:
线程A锁住了记录1并等待记录2,而线程B锁住了记录2并等待记录1,这样两个线程就发生了死锁现象.
死锁可以用LockCop来检测,其源码地址为https://github.com/lattesir/WindowsViaCPP/tree/master/09-LockCop
注:该工具使用了Windows Vista/ 7提供的WCT API,故需要在Windows Vista/ 7系统运行LockCop检测工具。
1) 不要在线程函数体内操作MFC控件,不要再线程里面调用UpdateData函数更新用户界面,而应该尽量采用发送消息的方式,在主线程的消息响应函数中操作控件;
注:Winform/WPF中在线程中更新界面会抛出异常,解决办法也是用委托。
2)不建议采用SendMessage往主线程发送消息,因为它是同步的,阻塞的,可以考虑采用PostMessage代替;
3)线程退出时,尽量不要使用TerminateThread函数,而尽可能的让线程自己退出;
注:在线程中有malloc/new等操作,强行终止,1会导致内存泄露,2是因为整个进程在分配和回收内存时,都要用同一把锁,如果TerminateThread之后再new很有可能会死锁。CloseHandle(),TerminateThread(),ExitThread()的区别
4) 当线程退出时,必须先等待工作者线程退出,主线程才退出,但是在主线程里面不要使用WaitForSingleObject或WaitForMultiObjects等待线程结束,因为它可能造成死锁,当主线程使用这两个函数时,主线程就挂起了,尤其在第 (1), (2) 种情况下,工作者线程还在调用主线程里面的资源,这样造成死锁;
5) 为了防止退出死锁的发生,尽量使用MsgWaitForMultipleObjects函数,因为该函数等待时,可以等待线程句柄 有信号,而且还可以等待消息,不会造成死锁;