在 Qt文档中,术语“可重入”与“线程安全”被用来说明一个函数如何用于多线程程序。假如一个类的任何 函数在此类的多个不同的实例上,可以被多个线程同时调用,那么这个类被称为是“可重入”的。假如不同的线程作用在同一个实例上仍可以正常工作,那么称之为 “线程安全”的。
大多数c++类天生就是可重入的,因为它们典型地仅仅引用成员数据。任何线程可以在类的一个实例上调用这样的成员函数,只要没有 别的线程在同一个实例上调用这个成员函数。举例来讲,下面的Counter 类是可重入的:
class Counter
{
public:
Counter() {n=0;}
void increment() {++n;}
void decrement() {--n;}
int value() const {return n;}
private:
int n;
};
这个类不是线程安全的,因为假如多个线程都试图修改数据成员 n,结果未定义。这是因为c++中的++和--操作符不是原子操作。实际上,它们会被扩展为三个机器指令:
1,把变量值装入寄存器
2,增加或减少寄存器中的值
3,把寄存器中的值 写回内存
假如线程A与B同时装载变量的旧值,在寄存器中增值,回写。他们写操作重叠了,导致变量值仅增加了一次。很明显,访问应该 串行化:A执行123步骤时不应被打断。使这个类成为线程安全的最简单方法是使用QMutex来保护数据成员:
class Counter
{
public:
Counter() { n = 0; }
void increment() { QMutexLocker locker(&mutex); ++n; }
void decrement() { QMutexLocker locker(&mutex); --n; }
int value() const { QMutexLocker locker(&mutex); return n; }
private:
mutable QMutex mutex;
int n;
};
QMutexLocker 类在构造函数中自动对mutex进行加锁,在析构函数中进行解锁。随便一提的是,mutex使用了mutable关键字来修饰,因为我们在value()函数中对mutex进行加锁与解锁操作,而value()是一个const函数。
大多数Qt类是可重入,非线程安全的。有一些类与函数是线程安全的,它们 主要是线程相关的类,如QMutex,QCoreApplication::postEvent()。
线程与QObjects
QThread 继承自QObject,它发射信号以指示线程执行开始与结束,而且也提供了许多slots。更有趣的是,QObjects可以用于多线程,这是因为每个线 程被允许有它自己的事件循环。
QObject 可重入性
QObject是可重入的。它的大多数非GUI子类,像QTimer,QTcpSocket,QUdpSocket,QHttp,QFtp,QProcess也 是可重入的,在多个线程中同时使用这些类是可能的。需要注意的是,这些类被设计成在一个单线程中创建与使用,因此,在一个线程中创建一个对象,而在另外的 线程中调用它的函数,这样的行为不能保证工作良好。有三种约束需要注意:
1,QObject的孩子总是应该在它父亲被创建的那个线程中创建。这意味着,你绝不应该传递QThread对象作为另一个对象 的父亲(因为QThread对象本身会在另一个线程中被创建)
2,事 件驱动对象仅仅在单线程中使用。明确地说,这个规则适用于"定时器机制“与”网格模块“,举例来讲,你不应该在一个线程中开始一个定时器或 是连接一个套接字,当这个线程不是这些对象所在的线程。
3,你必须 保证在线程中创建的所有对象在你删除QThread前被删除。这很容易做到:你可以run()函数运行的栈上创建对象。
尽管QObject是可重入的,但GUI类,特别是QWidget与它的所有子类都是不可重入的。它们仅用于主线程。正如前面提到过 的,QCoreApplication::exec() 也必须从那个线程中被调用。实践上,不会在别的线程中使用GUI类,它们工作在主线程上,把一些耗时的操作放入独立的工作线程中,当工作线程运行完成,把 结果在主线程所拥有的屏幕上显示。
逐线程事件循环
每 个线程可以有它的事件循环,初始线程开始它的事件循环需使用QCoreApplication::exec(),别的线程开始它的事件循环需要用QThread::exec().像QCoreApplication一 样,QThreadr提供了exit(int)函数,一 个quit() slot。
线程中的事件循环,使得线程可以使用那些需要事件循环的非GUI 类(如,QTimer,QTcpSocket,QProcess)。也可以把任何线程的signals连接到特定线程 的slots,也就是说信号-槽机制是可以跨线程使用的。对于在QApplication之前创建的对象,QObject::thread()返回0,这意味着主线程仅为这些对象处理投递事件, 不会为没有所属线程的对象处理另外的事件。可以用QObject::moveToThread() 来改变它和它孩子们的线程亲缘关系,假如对象有父亲,它不能移动这种关系。在另一个线程(而不是创建它的那个线程)中delete QObject对象是不安全的。除非你可以保证在同一时刻对象不在处理事件。可以用QObject::deleteLater(),它会投递一个DeferredDelete事件,这会被对象线程的事件循环最终选取到。
假如没有事件循 环运行,事件不会分发给对象。举例来说,假如你在一个线程中创建了一个QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它的timeout()信号.对deleteLater()也不会工作。(这同样适用于主线程)。你可以手工使用线程安全的函数 QCoreApplication::postEvent(), 在任何时候,给任何线程中的任何对象投递一个事件,事件会在那个创建了对象的线程中通过事件循环派发。事件过滤器在所有线程中也被支持,不过它限定被监视 对象与监视对象生存在同一线程中。类似地,QCoreApplication::sendEvent(不是postEvent()), 仅用于在调用此函数的线程中向目标对象投递事件。
从 别的线程中访问QObject子类
QObject和所有它的子类是非线程安全的。这包括整个的事件投递 系统。需要牢记的是,当你正从别的线程中访问对象时,事件循环可以向你的QObject子类投递事件。假如你调用一个不生存在当前线程中的QObject 子类的函数时,你必须用mutex来保护QObject子类的内部数据,否则会遭遇灾难或非预期结果。像其它的对象一样,QThread对象生存在创建它 的那个线程中---不是当QThread::run()被 调用时创建的那个线程。一般来讲,在你的QThread子类中提供slots是不安全的,除非你用mutex保护了你的成员变量。
另一方面,你可 以安全的从QThread::run()的实现中发射信 号,因为信号发射是线程安全的。
跨线程的 信号-槽
Qt支持三种类型的信号-槽连接:
1,直接连接,当signal发射时,slot立即调用。此slot在发射signal的那个线程中被 执行(不一定是接收对象生存的那个线程)
2,队列连接, 当控制权回到对象属于的那个线程的事件循环时,slot被调用。此slot在接收对象生存的那个线程中被执行
3,自动连接(缺省),假如信号发射与接收者在同一个线程中,其行为如直 接连接,否则,其行为如队列连接。
连接类型可能通过以向connect() 传递参数来指定。注意的是,当发送者与接收者生存在不同的线程中,而事件循环正运行于接收者的线程中,使用直接连接是不安全的。同样的道理,调用生存在不 同的线程中的对象的函数也是不是安全的。QObject::connect() 本身是线程安全的。
多线程与隐含共享
Qt 为它的许多值类型使用了所谓的隐含共享(implicit sharing)来优化性能。原理比较简单,共享类包含一个指向共享数据块的指针,这个数据块中包含了真正原数据与一个引用计数。把深拷贝转化为一个浅拷 贝,从而提高了性能。这种机制在幕后发生作用,程序员不需要关心它。如果深入点看,假如对象需要对数据进行修改,而引用计数大于1,那么它应该先 detach()。以使得它修改不会对别的共享者产生影响,既然修改后的数据与原来的那份数据不同了,因此不可能再共享了,于是它先执行深拷贝,把数据取 回来,再在这份数据上进行修改。例如:
void QPen::setStyle(Qt::PenStyle style)
{
detach(); // detach from common data
d->style = style; // set the style member
}
void QPen::detach()
{
if (d->ref != 1) {
... // perform a deep copy
}
}
一般认为,隐含共享与多线程不太和谐,因为有引用计数的存在。对引用计数进行 保护的方法之一是使用mutex,但它很慢,Qt早期版本没有提供一个满意的解决方案。从4.0开始,隐含共享类可以安全地跨线程拷贝,如同别的值类型一 样。它们是完全可重入的。隐含共享真的是"implicit"。它使用汇编语言实现了原子性引用计数操作,这比用mutex快多了。
假如你在多个 线程中同进访问相同对象,你也需要用mutex来串行化访问顺序,就如同其他可重入对象那样。总的来讲,隐含共享真的给”隐含“掉了,在多线程程序中,你 可以把它们看成是一般的,非共享的,可重入的类型,这种做法是安全的。
分享到:
相关推荐
在Qt框架下实现多线程TCP通信是一种常见且高效的方法,尤其在处理实时性要求高、数据量大的网络应用中。以下将详细讲解Qt中如何进行多线程TCP通信,以及涉及的关键知识点。 首先,标题"qt 中 多线程tcp通信"表明...
Qt5框架提供了一种方便的方式来实现多线程,它允许开发者在不同的线程中执行任务,从而避免主线程(GUI线程)因处理耗时操作而变得卡顿。本知识点将深入探讨Qt5中的多线程以及一个简单的实例——WorkThread。 **1. ...
文件列表中的`tcpserver2`可能是一个示例项目或者源代码文件,它展示了如何在Qt环境中实现一个多线程并发服务器的具体细节。通过分析这个文件,你可以更深入地理解如何在实践中应用上述概念。 总的来说,基于Qt的多...
QT下多线程UDP Socket示例是一个典型的网络通信编程应用场景,它涉及到QT库中的网络模块,特别是关于UDP(用户数据报协议)的使用以及多线程技术。在本示例中,开发者创建了一个UDP服务器,该服务器能够在不影响主...
本项目"qt多线程高效下载文件"是基于Qt实现的一个实用工具,它利用多线程技术来提高文件下载的效率,特别是对于大文件或者需要同时下载多个文件的场景,这种多线程策略显得尤为重要。 在HTTP协议方面,它是互联网上...
在基于QT的多线程定时器项目中,我们主要探讨的是如何在QT环境中利用多线程和定时器功能来实现特定的程序逻辑。 1. **QT中的线程(QThread)** 在QT中,`QThread`类是处理线程的基础。通过继承`QThread`,我们可以...
### QT中sqlite多线程操作4个注意问题 在开发基于Qt的应用程序时,经常会遇到需要使用多线程来进行SQLite数据库操作的情况。然而,多线程环境下的数据库操作相较于单线程来说更为复杂,需要特别注意一些细节问题,...
QT+OpenGL多线程测试是将高性能图形渲染与Qt框架结合的一种技术应用,它通过利用多线程的优势来提升程序的运行效率。在本项目中,开发者采用了2D纹理贴图的方法,使得图像处理和显示更为高效。接下来,我们将深入...
在QT5中,多线程技术与TCP网络编程相结合,可以构建高效、稳定的网络服务。本示例将深入探讨如何利用QT5实现多线程TCP服务器和客户端。 一、QT5中的多线程 在QT5中,QThread类提供了多线程支持。通过继承QThread...
在本项目中,“qt5多线程pingIP地址(线程池)”是一个利用Qt5框架和多线程技术来实现对多个IP地址进行并发ping操作的应用。这个应用可能会被网络管理员或者开发人员用来快速检测网络连通性,特别是在大规模网络环境...
本项目标题为“Qt Socket 多线程代码实现”,是一个适合初学者的示例,它展示了如何使用QtcpSocket在多线程环境中构建一个服务器。下面将详细介绍相关知识点。 首先,我们来理解什么是Socket。Socket是一种在不同...
在“Qt多线程通讯”DEMO中,主线程可能创建了一个`QThread`实例,并启动它。接着,一个工作对象(可能是自定义的QObject派生类)被移动到子线程中。这个工作对象可能会有一个接收参数的槽函数,用于处理主线程传递...
Qt 多线程及简单实例 demo。 多线程的几大特点: 1.多线程的执行顺序无法保证,与操作系统的调度策略和线程优先级等因素有关。 2.多线程的切换可能发生在任何时刻、任何地点。 3.多线程对代码的敏感度高,因此对...
QTcpServer多线程 每个客户端连接的tcpSocket分别分配一个专门的线程来处理。 核心思想:继承并重写QTcpServer的incomingConnection函数去自己实现tcpsocket连接的建立和分配。 incomingConnection函数说明: 当...
QT框架中的多线程TCP服务器与客户端编程是一个关键的领域,尤其在开发高效、响应迅速的网络应用时。本文将深入探讨如何利用QT库,特别是QT5.11.1版本,来构建多线程的TCP服务器和客户端,以及QThread在其中的作用。 ...
QT框架是广受欢迎的开源跨平台应用程序开发工具,它提供了丰富的功能和库,包括对多线程的支持。在QT中实现多线程可以帮助开发者优化资源使用,提高程序的响应速度和并发性能。本文将深入探讨QT中如何使用多线程,并...
在Qt框架中,多线程是提升应用程序性能和响应性的重要技术。QThread类是Qt提供的用于线程操作的工具,它使得线程管理变得更加方便,尤其在UI更新和耗时计算任务之间需要分离时。本篇文章将深入探讨如何利用QThread来...