• 7253阅读
  • 0回复

[转载]Qt经典—事件与事件循环 [复制链接]

上一主题 下一主题
离线zjhcool
 
只看楼主 倒序阅读 楼主  发表于: 2011-02-21
线程是qt channel里最流行的讨论话题之一。许多人加入了讨论并询问如何解决他们在运行跨线程编程时所遇到的问题
快速检阅一下他们的代码,在发现的问题当中,十之八九遇到得最大问题是他们在某个地方使用了线程,而随后又坠入了并行编程的陷阱。Qt中创建、运行线程的“易用”性、缺乏相关编程尤其是异步网络编程知识或是养成的使用其它工具集的习惯、这些因素和Qt信号槽架构混合在一起,便经常使得人们自己把自己射倒在了脚下。此外,Qt对线程的支持是把双刃剑:它即使得你在进行Qt多线程编程时感觉十分简单,但同时你又必须对Qt所新添加许多的特性尤为小心,特别是与QObject的交互。
本文的目的不是教你如何使用线程、如何适当地加锁,也不是教你如何进行并行开发或是如何写可扩展的程序;关于这些话题,有很多好书,比如这个链接给的推荐读物清单. 这篇文章主要是为了向读者介绍Qt 4的事件循环以及线程使用,其目的在于帮助读者们开发出拥有更好结构的、更加健壮的多线程代码,并回避Qt事件循环以及线程使用的常见错误
先决条件
考虑到本文并不是一个线程编程的泛泛介绍,我们希望你有如下相关知识:
C++基础;
Qt 基础:QOjbects , 信号/槽,事件处理;
了解什么是线程、线程与进程间的关系和操作系统;
了解主流操作系统如何启动、停止、等待并结束一个线程;
了解如何使用mutexes, semaphores 和以及wait conditions 来创建一个线程安全/可重入的函数、数据结构、类。
本文我们将沿用如下的名词解释,即
可重入 一个类被称为是可重入的:只要在同一时刻至多只有一个线程访问同一个实例,那么我们说多个线程可以安全地使用各自线程内自己的实例。 一个函数被称为是可重入的:如果每一次函数的调用只访问其独有的数据(译者注:全局变量就不是独有的,而是共享的),那么我们说多个线程可以安全地调用这个函数。 也就是说,类和函数的使用者必须通过一些外部的加锁机制来实现访问对象实例或共享数据的序列化。
线程安全 如果多个线程可以同时使用一个类的对象,那么这个类被称为是线程安全的;如果多个线程可以同时使用一个函数体里的共享数据,那么这个函数被称为线程安全的。
(译者注: 更多可重入(reentrant)和t线程安全(thread-safe)的解释: 对于类,如果它的所有成员函数都可以被不同线程同时调用而不相互影响——即使这些调用是针对同一个类对象,那么该类被定义为线程安全。 对于类,如果其不同实例可以在不同线程中被同时使用而不相互影响,那么该类被定义为可重入。在Qt的定义中,在类这个层次,thread-safe是比 reentrant更严格的要求)
事件与事件循环
Qt作为一个事件驱动的工具集,其事件和事件派发起到了核心的作用。本文将不会全面的讨论这个话题,而是会聚焦于与线程相关的一些关键概念。想要了解更多的Qt事件系统专题参见 (这里[doc.qt.nokia.com] 和 这里 [doc.qt.nokia.com] ) (译者注:也欢迎参阅译者写的博文:浅议Qt的事件处理机制一,二)
一个Qt的事件是代表了某件另人感兴趣并已经发生的对象;事件与信号的主要区别在于,事件是针对于与我们应用中一个具体目标对象(而这个对象决定了我们如何处理这个事件),而信号发射则是“漫无目的”。从代码的角度来说,所有的事件实例是QEvent [doc.qt.nokia.com]的子类,并且所有的QObject的派生类可以重载虚函数QObject::event(),从而实现对目标对象实例事件的处理。
事件可以产生于应用程序的内部,也可以来源于外部;比如:
QKeyEvent和QMouseEvent对象代表了与键盘、鼠标相关的交互事件,它们来自于视窗管理程序。
当计时器开始计时,QTimerEvent 对象被发送到QObject对象中,它们往往来自于操作系统。
当一个子类对象被添加或删除时,QChildEvent对象会被发送到一个QObject对象重,而它们来自于你的应用程序内部
对于事件来讲,一个重要的事情在于它们并没有在事件产生时被立即派发,而是列入到一个事件队列(Event queue)中,等待以后的某一个时刻发送。分配器(dispatcher )会遍历事件队列,并且将入栈的事件发送到它们的目标对象当中,因此它们被称为事件循环(Event loop). 从概念上讲,下段代码描述了一个事件循环的轮廓:
1:  while (is_active)
2:  {
3:      while (!event_queue_is_empty)
4:          dispatch_next_event();
5:    
6:      wait_for_more_events();
7:  }



我们是通过运行QCoreApplication::exec()来进入Qt的主体事件循环的;这会引发阻塞,直至QCoreApplication::exit() 或者 QCoreApplication::quit() 被调用,进而结束循环。
这个“wait_for_more_events()” 函数产生阻塞,直至某个事件的产生。 如果我们仔细想想,会发现所有在那个时间点产生事件的实体必定是来自于外部的资源(因为当前所有内部事件派发已经结束,事件队列里也没有悬而未决的事件等待处理),因此事件循环被这样唤醒:
    * 视窗管理活动(键盘按键、鼠标点击,与视窗的交互等等);
     * socket活动 (有可见的用来读取的数据或者一个可写的非阻塞Socket, 一个新的Socket连接的产生);
     * timers (即计时器开始计时)
     * 其它线程Post的事件(见后文)。
       Unix系统中,视窗管理活动(即X11)通过Socket(Unix 域或者TCP/IP)通知应用程序(事件的产生),因为客户端使用它们与X服务器进行通讯。 如果我们决定用一个内部的socketpair(2)来实现跨线程的事件派发,那么视窗管理活动需要唤醒的是

     * sockets;
     * timers;



这也是*select(2)* 系统调用所做的: 它为视窗管理活动监控了一组描述符,如果一段时间内没有任何活动,它会超时。Qt所要做的是把系统调用select的返回值转换为正确的QEvent子类对象,并将其列入事件队列的栈中,现在你知道事件循环里面装着什么东西了吧:)
为什么需要运行事件循环?
...
..
.
标签:
QEventLoop, Qt, 事件循环
本文链接: Qt经典—事件与事件循环
版权所有: Venus, 转载请注明来源Venus并保留链接地址!

相关文章




我的博客地址: http://newfaction.net
快速回复
限100 字节
 
上一个 下一个