[Windows c++] Windows多线程编程笔记

====================================================================================================================
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

注:Semaphore模型可以比喻为停车场,假如停车场有 10个车位,那么当进入10辆车后,所有希望再进入的车都将被阻塞住,直到有车从
    停车场中出来,外面排队的车才会解除阻塞(或者超时后车开走绕一圈再回来排队)。
    Semaphore不能用来保护关键数据去,它的一个重要应用场景是实现 “具备阻塞功能的计数器” , 普通的全局变量配合mutex的计数
    器也可以完成同样的功能。


注: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作为线程间同步时,可以不使用这个参数,


====================================================================================================================

Windows 的 Semaphore 和 Linux 差不多,这里不再赘述

====================================================================================================================

Win32中最重要的同步机制是event对象,这是一种 核心对象,因此存在于内核态。


HANDLE CreateEvent(
    LPSECURITY_ATTRIBUTES lpEventAttributes,        //安全属性
    BOOL bManualReset,                                //FALSE --- event变为激发态后立刻重置为非激发态 ; TRUE --- event变为激发态后需要手动设置为非激发态
    BOOL bInitialState,                                //TRUE  ---  初始状态就是激发态 ; FALSE --- 初始状态是非激发态
    LPCSTR lpName                                    //Event的名称
)

SetEvent()   把event对象设置为激发态
ResetEvent()  把event设置为非激发态
PulseEvent()   bManualReset=true: 把event设置为激发态,唤醒所有等待的线程,然后把event回复为非激发态
               bManualReset=false:把event设置为激发态,唤醒任意一个的等待的线程,然后把event恢复为非激发态。
               
               
从Event的行为上来看,完全能够模拟出条件变量。使用PulseEvent来触发条件即可。

!!!!注意:SetEvent相当于将电平设置为高电平,在此期间所等待此事件的线程都会被激发,当ResetEvent后,所有线程
          都不会再被激发。
          PulseEvent相当于产生一次高电平脉冲,所有此时此刻处于等待状态的线程都会被激发,而未做好准备的线程
          只能等待下一个高电平事件点的出现。
          
          因此,event不存在排队一说,只是跟着当前此时此刻的电平做应激反应而已。


          
!!!event有一种引起死锁的场景,如果以event作为互斥控制,因为event的不保留特性,可能出现A线程的等待event动作晚于
B线程的event的发送的工作,由于错过了event,所以B发送的时候没有线程在消费event,所以A便会永远等待下去,
这个时候如果B中有等待A怎么样的流程,那么这两个线程就会无限等待下去。


Semaphore 的引入便是用来解决event的死锁问题。

Event通常被用来封装成新的线程间同步机制,比如用Event实现信号量。


!!!!但是务必要注意Event的不排队特性,event发出的时候,如果没有任何线程在waitforsingleobject等待他,那么这个event可以
被认为是没有发生过。只此一点需要注意。

====================================================================================================================

多线程场景下的计数问题

如果有一个整数值用来计数,而多线程都会操作和读取他,那么如何保证race condition能够合理有效地处理?

方案1:使用mutex 或 critical section 对变量进行保护,在进行 加/减

方案2:使用InterlockedIncrement() 或 InterlockedDecrement() 进行数值的原子 加/减 。
       使用InterlockedExchange() 重新设置计数值,同样也是原子性的
       
====================================================================================================================
       
如何安全地终止一个线程?????

方法一:

使用TerminateThread()终止线程的特点:

1)被终止的线程没有机会清理自己,也不会抛出异常;
2)(!)目标线程的堆栈无法被释放,如果程序没有退出的话,那么这个线程可能会造成内存泄漏;
3)(!)目标线程如果动态加载了动态库,那么这些动态库也无法被卸载掉;
4)(!)如果线程在锁住了critical section的时候被终结,那么这个critical section永远无法被释放,那么其他试图获得critical seciton
    的线程将永远等待下去

ps:针对第4条,如果使用Mutex就没关系,因为如果锁住Mutex的线程不存在的话,其他waitforsingleobject等待Mutex的线程会获得返回值WAIT_ABANDONED的返回
    通知,而不会无限等待下去。

方法二:

使用标记,线程轮训此标记的值,当值变为相应的值时,线程自行安全退出。使用这种方法就需要同步机制配合了。

目前推荐 Event 和 Mutex 这两种手段来进行 线程间的race condition控制。


====================================================================================================================

如何先配置好线程,再启动线程?

createthread函数如果第五个参数不指定 CREATE_SUSPENDED 的话,会立刻执行传入的线程执行函数。因此可以通过设置
这个参数来先配置一个线程,然后通过ResumeThread来启动这个线程。这样我们便可以在这二者之间做线程优先级配置
或者线程入参准备的等动作。

如何挂起一个线程?
既然有ResumeThread来暂停线程和接着运行线程,那么是否有一个函数用来挂起线程,答案是肯定的,我们可以用SuspendThread将
一个正在运行的线程挂起。
在使用SuspendThread挂起线程的时候请务必处理好race condition,防止死锁的发生。

====================================================================================================================

overlapped I/O (Asynchronous I/O) 异步I/O

异步I/O的核心思想就是把I/O动作放到单独的线程中执行,并在执行完毕后通知其他等待I/O完成的线程

异步I/O的四种场景和模型:
1)激发的文件handles     (使用文件HANDLE作为激发条件,不能对同一个HANDLE进行多次异步IO,因为一个资源的HANDLE是唯一的)
2)激发的event对象            (可以对同一个HANDLE进行多次异步IO)
3)异步过程调用(Asynchronous Procedure Calls APCs)        (通过指定回调函数的方式让流程更简便,但是不够scalable)
4)I/O completion ports

其中I/O completion ports最重要


   备注:什么是scalable服务器?scalable系统是指能够通过增加 物理内存 、 物理磁盘 、 CPU 个数这些硬件
         设备就能够直接影响系统性能的一种软件架构模型。
         


====================================================================================================================

CreateFile函数的第六个参数 dwFlagsAndAtrtibutes可以通过制定 FILE_FLAG_OVERLAPPED来告知系统在操作文件时是否
使用异步行为。
如果设置了,那么对此文件的读写动作都将是异步的。所谓的异步是指读请求会被组装成读请求消息发送给I/O系统,I/O系统
将请求排入自己的消息队列中,而不是立刻读取数据反馈给调用线程。这就带来一个隐患,如果多个线程密集发出很多异步
I/O请求,而因为这些请求的返回仅仅说明自己被加入I/O的处理队列,所以这就无法保证这些请求被处理的顺序是按照调用
函数时的顺序,因为线程调度有先后。
更极端的情况,即便是一个线程在发出N多一步I/O,但是操作的是网络I/O,那么这些请求的次序可能完全失序,从而导致网络数据混乱。

注意:如果CreateFile时指定了 FILE_FLAG_OVERLAPPED , 那么ReadFile 和 WriteFile的时候最后一个参数一定要给予合适的值
      用来说明异步的相关参数配置,即OVERLAPPED。
      
      !!!ps:OVERLAPPED参数可能在后续的很多地方都是用,这里不建议将其存放在栈里面,因为ReadFile 和 WriteFile接受
          的是一个指向结构体的指针,所以当退出作用域后将会被释放,这里推荐把OVERLAPPED参数放到堆里面。
      
====================================================================================================================

异步I/O机制的异步体现在,I/O动作完成后通知调用者线程,那么是如何通知呢?这里使用文件HANDLE作为通知的媒介。
可以在调用异步I/O的线程中waitforsingleobject等待文件HANDLE,如果这个函数返回了,说明HANDLE被激发了,也说明I/O动作已经
完成了,此时线程可以通过调用GetOverlappedResult来获取I/O操作的结果。此函数的原型:

BOOL GetOverlappedResult(
    HANDLE hFile,                        //要检查的文件HANDLE
    LPOVERLAPPED lpOverlapped,            //读写文件的时候使用的OVERLAPPED配置
    LPDWORD lpNumberOfBytesTransfered,    //有多少字节完成了读/写
    BOOL bWait,                            //是否要等待操作完成
)

如果此函数返回TRUE,则表示异步I/O已经完成,否则表示异步操作失败了,也因此才会触发HANDLE被激发。这是可以使用GetLastError()
来获取错误码,比如ERROR_IO_INCOMPLETE就表示I/O动作未完成

====================================================================================================================


ReadFile 和 WriteFile的返回值有蹊跷!

如果I/O不繁忙,那么使用overlapped I/O的时候,ReadFile 和 WriteFile立刻就反回了true,那么这也有一定可能是在函数
返回前I/O动作就完成了,这个时候通过waitforsingleobject检查文件HANDLE会发现立刻返回,因为HANDLE早在函数返回前就
已经处于激发态。
    这里对比下Event,如果检查Event是否激发,就可能因为event的发送遭遇waitforsingleobject的调用而被忽略掉,
    严重的会形成死锁,索性!我们检查的是 文件 HANDLE,还好Windows没有提供Event让我们检查,但是在OVERLAPPED
    中有一个Event,不知道用作何处,需要研究一下是否存在潜在的event死锁问题。
    
    
如果I/O比较繁忙,ReadFile 和 WriteFile 将I/O请求递送给 操作系统时,操作系统没有立刻执行而是把请求排队,那么此时
ReadFile 和 WriteFile将返回FALSE,注意,这个时候不可以认为动作失败了,这是我们需要调用GetLastError()来判断,
如果返回 ERROR_IO_PENDING,则表示消息在排队中,如果返回 ERROR_HANDLE_EOF则表示真的失败了。
    注意:这里存在GetLastError时效的问题,GetLastError是不是线程独享的?????

这里来说明OVERLAPPED中的Event的作用,我们可以选择等待文件HANDLE的被激发,这个时候只需要把这个Event置为NULL即可,
我们可以选择使用Event作为被激发对象,当在OVERLAPPED中设置了Event后,异步I/O完成时会将此Event设置为被激发。
这里需要注意的是这个Event必须设置为手动reset,即bManualReset=true。 

!!!如果希望对同一个文件进行N多个读取/写入操作,那么就必须使用Event作为异步通讯机制,因为在Windows下文件HANDLE是
唯一的,所以多个动作同时操作一个文件也只会异步等待同一个HANDLE,而每个OVERLAPPED都有自己的Event,即每个读/写
动作都有自己的Event,所以可以并行。

p160的 IOBYEVNT.c

====================================================================================================================

APCs 即 ReadFileEx 和 WriteFileEx


这个版本的文件操作允许用户指定一个回调函数,当异步I/O完成时,进入回调函数。但是有几点要注意:

1)必须是异步I/O,即createfile的时候指定FILE_FLAG_OVERLAPPED。
2)并不是I/O一完成CPU就会进入回调函数,在I/O完成时操作系统会记录下来那个回调要被调用了,但是会等到
   调用ReadFileEx或WriteFileEx的线程处于alertable状态才会进入回调。
   
   
   alertable的状态会在线程调用如下函数时进入:
   1)SleepEx()
   2)WaitForSingleObjectEx()
   3)WaitForMutipleObjectsEx()
   4)MsgWaitForMutipleObjectsEx()
   5)SignakObjectAndWait()
   
这里对回调函数的函数类型有要求:
VOID WINAPI FileIOCompletionRoutine(
    DWORD dwErrorCode,
    DWORD dwNumberOfBytesTransferred,
    LPOVERLAPPED lpOverlapped
)
即注册给ReadFileEx 和 WriteFileEx的回调函数要是上述函数类型的。

注意:我们可发现FileIOCompletionRoutine的入参中并没有自定义函数入参选项,所以如果我们想在调用
      这个回调给他带一些参数,那么可以利用ReadFileEx 和 WriteFileEx的第四个参数OVERLAPPED中的
      Event作为参数传递,ReadFileEx 和 WriteFileEx的 第四个参数OVERLAPPED 会被赋值给 FileIOCompletionRoutine
      的 最后一个参数 LPOVERLAPPED lpOverlapped。 而在微软标准中明确表示ReadFileEx 和 WriteFileEx
      不需要Event(废话,都已经直接调用回调了,当然不需要再用Event进行事件被激发监控了),所以
      把Event指针开放出来让用户可以有途径传参。
      
!!!!样例见   p165

====================================================================================================================

重要的一点

上面说到了在做异步I/O的时候,可能会出现I/O动作早于/晚于 ReadFile 和 WriteFile执行,具体操作系统是怎么对待的呢?

如果请求读/写的数据小于8KB,则Windows内核会直接进行函数递归调用,直到完成I/O才一级级往上返回,直到ReadFile 和 WriteFile
返回,这也是为什么异步I/O会比函数早结束的原因,其实这里并不是异步,而是做了同步动作。

如果请求读/写数据大则会触发真正的异步,读写函数会立刻返回,而请求会被排入I/O队列中等待执行。


====================================================================================================================

APCs的缺点:1)listen 和 WaitCommEvent 等 I/O 相关的API不支持APCs
            2)回调函数必须是调用ReadFileEx 和 WriteFileEx提供,即回调函数的执行线程一定是和调用ReadFileEx 和 WriteFileEx的线程是一样的。
               这有悖于scalable系统原则,即event可以有所有线程执行。
               
====================================================================================================================


p192   ---   I/O Completion Ports

I/O Completion Ports 的 核思想  “用一大堆线程服务于一大堆events”

WaitForMutipleObjects有个数限制,目前最多同时等待64个

I/O Completion Ports 允许将一个线程的请求保存下来,而由另一个线程为他提供服务

原型:
HANDLE CreateIoCompletionPort(
    HANDLE FileHandle,                    //需要被注册到监听列表中的文件HANDLE
    HANDLE ExistingCompletionPort,        //如果有值,则FileHandle被绑定到此port上,如果是NULL,则产生一个新的port和FileHandle绑定
    DWORD CompletionKey,                //用来和FileHandle绑定关联,因为一个port可以绑定多个HANDLE,所以这里提供一个索引用来区分是哪个HANDLE上的消息到达了port
    DWORD NubberOfConcurrentThreads        //与当前 port 相关的线程数量,一般是CPU个数的2倍再加上2
)

说明:CreateIoCompletionPort可以把多个 HANDLE 绑定到一个 port上,那么所有 不同HANDLE的异步I/O完成后都会把消息推送给同一个port,
      那些消费消息的线程只需要时刻关注port上是否有消息到达即可。

一个port绑定多个HANDLE是通过循环调用CreateIoCompletionPort实现的,这个函数不接受HANDLE数组,所以需要依次绑定。

如何让线程监视port呢?
在已经创建完毕的线程函数中调用 GetQueuedCompletionStatus函数即可,此函数是 WaitForSingleObject 和 GetOverlappedResult的组合。原型如下:
BOOL GetQueuedCompletionStatus(
    HANDLE CompletionPort,                    //本线程即将监视哪个port
    LPDWORD lpNumberOfBytesTransfered,        //(输出参数)当前收到的字节数
    LPDWORD lpCompletionKey,                //(输出参数)通知当前消费者线程,本次传出的消息对应的key值是什么,和CreateIoCompletionPort的 CompletionKey对应
    LPOVERLAPPED *lpOverlapped,                //(输出参数)系统会把当前异步I/O对相应的OVERLAPPED值传出来,告知消费者自己做了什么样的异步IO
    DWORD dwMilliseconds                    //超时时间
)


(!!!)如何让线程只消费指定key值的消息呢?
答案是不需要让消费者只消费某个key值的消息,因为这个I/O Completion Ports组件存在的核心思想就是让一堆“功能一样”的线程去消费
一个/多个HANDLE,所以这些线程应当具备处理所有HANDLE被激发事件的能力,可以在线程中通过 switch 配合 key 值进行分支处理。但是
没法让某个线程只接受指定key。


哪些操作会触发 FileHandle 的被激发事件?
异步I/O的主旨还是解决I/O问题,通过CreateIoCompletionPort的第一个参数名称也可以看出来,要被监视的HANDLE应当是一个FILEHANDLE,
因此值针对  管道、硬件I/O设备、文件、SOCKET 这些I/O设备有效。
所以如下函数可以出发 FILEHANDLE 的被激发:
ConnectNamePipe()
DeviceIoControl()
LockFileEx()
ReadFile()
TransactNamePipe()
WaitCommEvent()
WriteFile()


====================================================================================================================


====================================================================================================================

使用 _beginthreadex()   创建的线程可以安全地使用任何 C runtime函数,而CreateThread和_beginthread()则不能很好地
和 C runtime配合。

一般 C runtime Library 都会有两个版本,一个是单线程版本,一个是多线程版本。
多线程版本的C runtime Library 的 errono是每个线程都有一个。、


多线程场景下,请使用 /MT 或者 /MD ,再配合 _beginthreadex 和 _endthreadex 使用。


====================================================================================================================

 stdio.h 中的某些特性 在 windows 中的替代品
 
 1.printf   的替代品为   wprintf 、 wprintfA 、 wprintfW
 2.stdin 和 stdout  的替代品为  GetStdHandle,其中入参可以是 STD_INPUT_HANDLE/STD_OUTPUT_HANDLE/STD_ERROR_HANDLE
 
 
p240演示了如何使用windows的api替换stdio
====================================================================================================================

在c++中使用多线程

可以把critical_section进一步封装成类

stack unwinding:当一个异常情况发生时,将堆栈中的所有对象都析构掉。  自动释放的锁就会在析构是进行他自己的stack unwinding


====================================================================================================================

MFC中的线程

AfxBrginThread()内部的动作:
1)在heap中配置一个新的CWinThread对象;
2)调用CWinThread::CreateThread并设定属性,产生尚未启动的suspend线程
3)设定线程优先级
4)调用CWinThread::ResumeThread()


====================================================================================================================

第10章以后未细读

   


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值