Windows消息调度机制和线程同步控制

 windows的所谓事件驱动核心是消息!

    消息分为进队消息消息和非进队消息。所谓进队消息就是windows将消息发送到每个线程所专有的队列中,然后由程序自主处理,这种消息基本上是由用户输入产生(wm_keydown,wm_keyup,wm_char,wm_mouse**,以及wm_paint,wm_timer,wm_quit)或者是调用postmessage,postthreadmessage产生的消息;所谓的非进队消息就是直接发送给窗口过程的消息,就是直接调用窗口过程,上述消息以外的一般都是这种类型!

    一个线程一旦建立了至少一个窗口,则系统就为其分配一个消息队列。主要表现形式为系统为其分配一个THREADINFO结构,该结构有四个指针分别指向登记消息队列,发送消息队列,应答消息队列和虚拟输入队列。如果想将消息放入登记消息队列,可以调用postmessage,或者postthreadmessage。其余的消息队列主要用于处理如下的事务。当某线程调用sendmessage给别的线程创建的窗口时,发送的消息首先追加到接收线程的发送消息队列,发送线程处于空闲状态,等待接收线程处理完他的消息返回给发送线程的应答队列,等到后发送线程被唤醒取得应答队列的消息(就是处理完消息的返回值),继续执行。而虚拟输入队列则是由windows的系统线程RIT(原始输入线程)负责将硬件事件转换成消息添加到对应线程的虚拟消息队列中。

    处理消息队列的顺序。首先windows绝对不是按队列先进先出的次序来处理的,而是有一定优先级的。优先级通过消息队列的状态标志来实现的。首先最高优先级的是别的线程发过来的消息(通过sendmessage),其次是处理登记消息队列消息,再次处理QS_QUIT标志,再处理虚拟输入队列,再处理wm_paint最后是wm_timer!

Windows这个操作系统是靠消息来驱动的,而且只有窗体才能接收消息,我们经常见到的窗体、按钮、文本框等这都是窗体,为了能够让窗体接受消息,对应于每一个窗体都有一个回调WndProc函数,Windows系统负责在必要的时候(有消息到达的时候)调用这个回调函数。 

线程与消息队列

当一个线程第一次被创建时,系统假定线程不会用于任何与用户相关的任务。这样可以减少线程对系统资源的要求。但是,一旦该线程调用一个与图形用户界面有关的函数 如检查它的消息队列或建立一个窗口 ),系统就会为该线程分配一些另外的资源,以便它能够执行与用户界面有关的任务。特别是,系统分配了一个THREADINFO结构,并将这个数据结构与线程联系起来。 
THREADINFO结构体如下: 

1.将消息发送到线程的消息队列 
   当线程有了与之联系的THREADINFO结构时,消息就有自己的消息队列集合。
    通过调用函数  BOOL  PostMesssage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
    可以将消息放置在线程的登记消息队列中。 
    当一个线程调用这个函数时,系统要确定是哪个线程建立了用 hwnd 参数标识的窗口。然后系统分配一块内存,将这个消息参数存储在这块内存中,并将这块内存增加到相应线程的登记消息队列中。并且该函数还设置QS_POSTMESSAGE唤醒位。函数 PostMesssage 登记了消息后立即返回,调用该函数的线程不知道登记的消息是否被指定窗口的窗口过程所处理。
    还可通过调用函数  BOOL  PostThreadMesssage(DWORD dwThreadId, UINT uMsg, WPARAM wParam, LPARAM lParam) 将消息放置在线程的登记消息队列中,同 PostMesssage 函数一样,该函数在向线程的队列登记消息后立即返回,调用该函数的线程不知道消息是否被处理。 
    向线程的队列发送消息的函数还有 VOID PostQuitMesssage(int nExitCode) ; 
该函数可以终止线程消息的循环,调用该函数类似于调用:PostThreadMesssage(GetCurrenThreadId( ), WM_QUIT, nExitCode, 0); 但 PostQuitMesssage 并不实际登记一个消息到任何队列中。只是在内部,该函数设定 QS_QUIT 唤醒标志,并设置 THREADINFO 结构的 nExitCode 成员。 

  线程间用postmessage通信和线程同步控制LOCK()搅绊在一起容易出问题,如造成主线程进度条无法进度刷新等,出现短暂的死锁挂起等现象。

2.向窗口发送消息 
    将窗口消息直接发送给一个窗口过程可以使用函数 LRESULT SendMessage( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 窗口过程将处理这个消息,只有当消息被处理后,该函数才能返回。即具有同步的特性。
     该函数的工作机制: 
     2.1 如果调用该函数的线程向该线程所建立的窗口发送了一个消息,SendMessage 就很简单:它只是调用指定窗口的窗口过程,将其作为一个子例程。当窗口过程完成对消息的处理时,它向 SendMessage 返回一个值。SendMessage 再将这个值返回给调用线程。 
     2.2 当一个线程向其他线程所建立的窗口发送消息时,SendMessage 就复杂很多(即使两个线程在同一个进程中也是如此)。windows 要求建立窗口的线程处理窗口的消息。所以当一个线程调用 SendMessage  向一个由其他进程所建立的窗口发送一个消息,也就是向其他线程发送消息,发送线程不可能处理该窗口消息,因为发送线程不是运行在接收进程的地址空间中,因此不能访问相应窗口的过程的代码和数据。(对于这个,我有点疑问:同一个进程的不同线程是运行在相同进程的地址空间中,它也采用这种机制,又作何解释呢?)实际上,发送线程要挂起,而有另外的线程处理消息。所以为了向其他线程建立的窗口发送一个窗口消息,系统必须执行一些复杂的动作。 
     由于windows使用上述方法处理线程之间的发送消息,所以有可能造成线程挂起,严重的会出现死锁。 
     利用一下4个函数可以编写保护性代码防护出现这种情况。 
    1. LRESULT SendMessageTimeout( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT fuFlags, UINT uTimeout , PDWORD_PTR pdwResult); 

    2. BOOL SendMessageCallback( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, SENDSYNCPROC pfnResultCallback,  ULONG_PTR dwData); 

   3. BOOL SendNotifyMessage( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 

    4.BOOL ReplyMessage( LRESULT lResult); 
    另外可以使用函数 BOOL InSendMessage( ) 判断是在处理线程间的消息发送,还是在处理线程内的消息发送 。

 

window 操作系统猜测。

2.每当一个线程创建一个窗口的时候,操作系统内部都会把该窗口的Handle和线程相关联。很有可能在操作系统内部会维护一个窗口handle到线程的map. 还有一种可能就是窗口的成员变量里面有一个指针,指向创建它的线程。

3.窗口本身并没有消息队列,所有发到窗口的消息,都会自动被发到创建该窗口的线程的消息队列中。

4.每个线程只能处理自己线程队列里面的消息,不能处理其他线程消息队列里面的消息。
所以PeekMessage(LPMSG lpMsg, HWND hWnd, UINT,UINT,UINT)函数中,如果hWnd不是本线程创建的窗口,则该函数调用失败。

5.由于在线程消息队列里面的消息会包含有窗口句柄,所以PeekMessage可以专门处理某个特殊窗口的消息。

1. 曾经有疑问线程是不是只有创建了窗口才具有消息队列,但又觉得应该不是这样,因为在windows的API里面有个函数叫PostThreadMessage,可以直接把消息投递到线程的消息队列里面,而不需要任何窗口句柄。后来在MSDN里面有这么一段描述,觉得解释的很详细:
 这里唯一的疑问我想应该是”makes its first call to one of the User or Windows Graphics Device Interface (GDI) functions", 这句话的意思是不是等同于创建一个窗口呢?

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值