====================================================================================================================
GetExitCodeThread() 用来检查某个线程的状态,以及如果线程退出了那么他的return返回值是多少。
如果子线程不多,那么主线程可以通过反复调用这个函数来观察线程状态(不能控制,这个函数只能观察),但是如果子线程
比较多,那么主线程在观察监视这个行为上就会花费大量CPU,这个时候最好是子线程自己主动通知主线程自己的状态。
有几种实现方式:
1)观察者模式,主线程将某个函数作为回调函数注册给子线程,子线程退出之前调用以通知主线程;
2)linux下有atexit系列函数,需要确认是否有线程版本,以及windows下是否可用;
3)WaitForSingleObject(HANDLE h,DWORD time),如果h结束了则此函数跳出等待,如果超时时间time到达了,则此函数也会跳出等待。
====================================================================================================================
如果一个程序启动以后会让cpu到达100%且持续很长时间,则说明程序中可能有busy loop
====================================================================================================================
WaitForSingleObject的超时参数可以设置为0,表示当前动作只一次检查,如果HANDLE准备好了,那么返回WAIT_OBJECT_0,否则返回WAIT_TIMEOUT
注意:
这里WaitForSingleObject用来观察线程是否结束,这个函数的第一个参数可以是任意windows下的HANDLE,而且这些HANDLE对应的
触发事件也不一样,需要具体分类罗列。也就是说HANDLE的类别不同,这个函数的返回判定也不同。这里统一把让函数能返回的事件叫做 “HANDLE对象被激发”
HANDLE对象类别 什么情况下是 “被激发” 备注
线程 线程结束时 CreateThread/CreateRemoteThread 创建线程获得HANDLE
进程 进程结束时 CreateProcess/OpenProcess 创建/attach进程获得HANDLE
Change Notification 特定磁盘子目录中发生特别变化
Console Input 控制台输入缓冲区中有数据可用时 CreateFile/GetStdFile 可获得控制台输入的HANDLE
Event 使用SetEvent/PulseEvent/ResetEvent时 CreateEvent/OpentEvent 可获得Event的HANDLE
Mutex Mutex可用时,即Mutex未被任何线程占有时 CreateMutex/OpenMutex 可用来获得其HANDLE
Semaphore Semaphore计数为0时被激发 CreateSemaphore/OpentSemaphore 可以获得 其 HANDLE
注:Change Notification的 “变化” 有如下情况:
代码 意义
FILE_NOTIFY_CHANGE_FILE_NAME 产生/删除/重命名一个文件
FILE_NOTIFY_CHANGE_DIR_NAME 产生/删除一个子目录
FILE_NOTIFY_CHANGE_ATTRIBUTES 目录/子目录中的任何属性改变
FILE_NOTIFY_CHANGE_SIZE 目录及子目录的任何文件大小改变
FILE_NOTIFY_CHANGE_LAST_WRITE 目录及子目录中的任何文件的最后写入时间改变
FILE_NOTIFY_CHANGE_SECURITY 目录及子目录的任何安全属性改变
WaitForSingleObject的不足
假设有N个线程的 “被激发” 状态需要被监视,那么单线程场景下必须对WaitForSingleObject设置超时时间,然后轮训监控,这和不
使用select来监控N各socket是否可读是一样的,没有进行 “多路复用” ,这个时候需要使用WaitForMutipleObjects监控多个线程的
HADNLE,以此完成 “多路复用”。
上面提到了 “不同HANDLE的被激发条件不一样” , WaitForMutipleObjects 不要求其监控的HADNLE是同样类型。
函数原型:
WaitForMutipleObjects(
DWORD nCount, //数组中元素的个数,最大值为MAXIMUM_WAIT_OBJECTS
CONST HANDLE* lpHandles, //数组指针
BOOL bWaitAll, //true --- 非超时场景下,所有HANDLE都 “被激发”才返回;false --- 非超时场景下,任意一个HANDLE “被激发” 都会返回
DWORD dwMilliseconds //超时时间
)
可以使用 bWaitAll 来模拟linux下的barrier功能。
使用WaitForMutipleObjects可以实现简单的线程池,每当有线程退出时,主线程的WaitForMutipleObjects都会跳出等待转而去创建新的线程。
====================================================================================================================
Windows下的所有 GUI 线程都有自己的消息队列,这个队列中存放所有发生在当前GUI线程上的 “事件”,比如鼠标、按键、窗口事件等等,
这些事件会被排入消息队列中等待被 GUI 线程取出处理。
GUI线程作为消息队列的消费者,它的消费模式不是 busy loop,而是向下面这个样子:
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
这里的 GetMessage 的行为比较像 WaitForSingleObject,不同的地方是,WaitForSingleObject等待的核心对象,入参是HANDLE,而
GetMessage 等待的是 消息,入参是消息类型。
注:其实底层数据结构真的就是一个消息队列。
Win16的合作性多任务的核心理念就是使用这个消息系统,比如进程间通讯,也是通过这种发消息的方式。
PS:Qt中的EventLoop和 app.exec()这些的底层都是一个消息队列在等待有事件发生。Qt和Windows GUI在界面软件的架构设计上有异曲同工之处
如果主线程使用WaitForSingleObject或WaitForMutipleObjects等待核心对象被激发,那么他势必无法回到 “消息循环” 中去消费消息队列。
这个问题的最直观表现就是 GUI 线程管理的主线程无响应,比如界面不会重绘,鼠标点击没反应,因为这些事件都在消息队列中排队,而
他们的消费者正在等待核心对象而无法消费消息队列。
针对上面的问题,Windows提供了 MsgWaitForMutipleObjects 来同时等待消息 和 核心对象,这样就可以在等待核心对象的时候同时处理
到达的消息了。
Windows GUI程序依赖 Message 机制,因此 MsgWaitForMutipleObjects 很有必要,具体的代码框架见 书本的87页。
一些思考:Qt中有事件环,应该也是同样的机制,那么如果主线程在阻塞等待核心对象,那么事件环应该也没法被消费,这个时候界面上
的表现就是主界面卡死,因此Qt的事件模块中是否也有 MsgWaitForMutipleObjects 这样的函数能用来同时等待核心对象和事件????
MsgWaitForMutipleObjects 和 WaitForMutipleObjects 不允许事件数组的结构松散,即如果移除了某个需要被监控的核心对象,那么必须
重新组织一个新的数组作为参数传递。
====================================================================================================================
Windows:
PostMessage :异步消息发送,把消息放到对方的消息队列中,然后返回执行自己的代码;
SendMessage :同步消息发送,直接调用对方的消息处理函数,执行完毕后才返回执行自己的代码。
Qt:
PostEvent :将某个事件放入事件环的事件队列,之后调用者立刻返回,事件环会按照消息排队的顺序把事件送给接受者,这是个异步过程,事件对象
必须通过new来创建,在被事件环post出去后,刚才new的对象会被事件环销毁。
SendEvent :将某个事件立刻送给接受者,这是个同步过程,因为事件处理过程是一个函数调用过程,因此这也是同步函数调用,在执行完
事件处理函数后SendEvent才会返回。使用此函数,一般事件对象都是放在栈里,这也反过来证实了函数的同步调用属性。
类比Windows 和 Qt ,可以发现 Post 和 Send 的行为非常相似。
打开Qt的源码可以发现,PostEvent会把传递给自己的消息,创建一个副本然后放入postEventList队列中。而SendEvent会直接调用notify函数,
notify的后续操作比较复杂,中间是否有消息队列结构什么的目前还没有研究透,后续需要继续跟进。
Critical Section 不是 核心对象,它不存在于内核中,因此不需要使用Create样式的函数来获取(对比CreateMutex),但是需要对他进行初始化
即InitializeCritucalSection。
用完以后需要使用DeleteCriticalSection清理。
!!!这里需要注意:因为 Critical Section不存在于内核中,因此他的销毁遵守作用域规则,比如一个局部变量的Cirtical Section在离开
作用域后会自动销毁,即便没有调用DeleteCriticalSection。
上述说法是否正确???? 某些时候会在DeleteCritricalSecition的时候发现Critiacal Section对象不存在而导致崩溃。
由于Critical Section不是核心对象,因此如果一个线程在 EnterCriticalSeciton后挂掉了,那么系统是不会将Critical Section清除的。与之对比,
Mutex作为核心对象,系统会负责清理工作。
由此可见, Critical Section 更像是一个用户态的东西,Mutex则存在于内核态。
====================================================================================================================
死锁只会发生在多线程中每个线程都至少需要申请2个资源的情况下,因此一个解决方法就是 “要么一次性锁住两个资源,要么一个都不锁”
我们可以设计这样一个锁,这个锁中使用WaitForMutipleObjects,而当且其等待的所有核心对象都满足时才返回,这就模拟了一次性锁住两个或多个资源的
场景。
====================================================================================================================
Mutex通常情况下比Critical Section慢100倍,因为 Mutex 是内核态对象, 而Critical Section是用户态对象。
Windows下的Mutex可以跨进程使用!!!! linux下不行
Windows下通过Mutex的变量名来进行跨进程使用。
原型:
HANDLE CreateMutex(
LPSECURITY)ATTRIBUTES lpMutexAttributes, //安全属性,NULL表示默认
BOOL bInitialOwner, //Mutex创建出来就是已经被当前线程加了锁的,这类似于初始化,在创建Mutex后立刻对其加锁
LPCTSTR lpName //任何进程和线程都能通过这个名字来使用Mutex,这是操作系统层面的全局!!!
)
再次赘述 核心对象 的 关闭和清除,核心对象都有自己的HANDLE,每次调用CloseHandle都会把内核中对这个核心对象的引用计数 -1 ,当
计数变为0的时候,操作系统会销毁这个核心对象。
CreateMutex时指定第三个参数的 核心对象名字,如果这个核心对象已经被其他进程或线程打开了,那么相应的HANDLE会被返回,而不会
创建一个新的HANDLE。
!!!!!注意:从这里可以看出,Windows中HANDLE是操作系统层面的全局,同一个核心对象在不同的进程或线程中拥有
同一个HANDLE。
这点和Linux的文件描述符有着本质区别,Linux的文件描述符是每个进程有自己的一套。
如果CreateMutex一个已经被打开的名为 lpName 的核心对象,则GetLastError会获得ERROR_ALREADY_EXISTS错误码,表示HANDLE已经存在。
OpenMutex可以用来打开已经被打开的核心对象,并获得其HANDLE。
如果一个线程或线程对Mutex加了锁,然后在没有解锁的情况下直接线程退出,那么其他线程或者进程对这个Mutex加锁(WaitForSingleObject)
将会获得返回值WAIT_ABANDONED_0。
如果是WaitForMutipleObjects,则返回 WAIT_ABANDONED_0 和 数组总数-1 的其中一个值,用来表示是哪个Mutex
对于bInitialOwner参数,这个参数让调用者在创建Mutex的时候就立刻加锁,这个参数主要用来解决多进程共享Mutex的情况,因为Mutex是跨进程的,
进程可以通过Mutex的名称来直接打开Mutex,因此存在race condition。
ps:Mutex作为线程间同步时,可以不使用这个参数,
====================================================================================================================
p116