进入run()函数后,新线程就开始运行,有自己的堆栈。
出了run()函数后,新建的线程就结束,并会发出信号,可以在另外一个线程中捕获,然后响应。、
currentThreadId()和 currentThread()分别会返回线程的ID号和线程对象指针。
最后在run()中调用exec()开始本线程的事件循环。
调用exit()或者quit()来退出线程。
QMutex加锁时候,其他的读和写都不能进行
他的简化使用类是QMutexLocker
QReadWriteLock
他的简化类是QReadLocker和QWriteLocker
写锁加上去的时候,不可以加读锁,要等待写锁释放才可以
读锁加上的时候,也不能加写锁,要等待读锁释放时候才可以加上去。
但是一个读锁加上的时候,可以再加读锁。
QSemaphore 是QMutex 的一般化,它可以保护一定数量的相同资源,与此相对,一个mutex只保护一个资源。
QWaitCondition 当某个条件满足时候,用来在另一个线程中唤醒其他的线程,
wakeOne()会随机的唤醒一个线程,而wakeAll()会唤醒所有等待的线程。
可以使用这个类来代替信号量QSemphore。
线程安全和可重入函数
1、 一个线程安全的函数可以同时被多个线程调用,即便这个函数使用共享数据,因为所有在共享数据上的操作都是串行的。
2、 可重入的函数也可以同时被多个线程调用,但是各个线程应该用自己的数据。
所以,一个线程安全的函数通常是可重入的,但是可重入的函数却不一定是线程安全的。
由此拓展,当一个类可以被多个线程调用时候,我们称它是可重入的,只要不同的线程使用不同的类对象。
当一个类可以被多个线程调用,即便这几个线程使用的是同一个类对象,这时候我们称这个类是线程安全的。
C++类通常是可重入的,因为每一个类都只操作自己的成员数据。任何一个线程都可以调用类对象的成员函数。只要与此同时其他线程没有调用这个类对象的成员函数。
关于可重入线程的例子,在另一篇文章里面介绍过了。
Qt的很多类都似乎可重入的,但是非线程安全的,因为要保证一个类是线程安全的,需要重复的来加锁和解锁,这会造成额外的开销
例如QString是可重入的,但是是非线程安全的,你可以在不同的线程分别调用成员函数处理不同对象,但是不能同时在不同的线程中调用成员函数来处理同一个对象,除非自己进行了锁操作。
注意:多线程领域的不完全统一标准。因此在配合QT使用其他面向对象的C++库时候,一定要先理解。
QThread and QObject
QObject可以在不同的线程中使用,在一个线程中发射信号,让另一个线程的槽函数来接收。或者发射自定义事件给其他线程中的object,因为每个线程都有自己的事件循环
QObject和他的非GUI子类,例如QTimer,QFtp,等都是可重入的,这样就可以在多个线程中使用。但是在一个线程中定义对象,在另一个线程中使用它调用它的成员函数不一定总是可以工作。
事件驱动对象只能在单线程中使用,特别的,在定时器机制和网络模块中,你不能在一个非声明线程中启动一个定时器或者连接socket,也就是说,如果在A线程中定义的定时器或者套接字,就不能再B线程中启动这个定时器或者用这个套接字去连接其他套接字。
尽管QObject是可重入的,但是GUI类确实不可重入的,这些只能在主线程中使用。
线程的事件循环。
事件循环让一个线程可以使用非GUI类但是需要使用事件循环的类,例如QTimer,QTcpSocket,QProcess,也使的该线程可以连接其他线程的信号到本线程的槽函数。
一个QObject对象生存在定义他的那个线程中,该线程的事件循环将该该Object的事件分发,
使用QObject::moveToThread()来转移一个QObject以及其孩子的线程关系,如果这个对象有父对象,那就不能转移。
在其他线程(非创建该QObject对象的线程)中调用delete来操作一个QObject,或者其他方法来操作这个QObject对象,是不安全的,除非你保证在此时该QObject对象此时没有处理事件。或者可以使用QObject::deleteLater()来代替。这样一个QEvent::DeferredDelete事件将会被发生。这样改对象所在线程的事件循环将会在最后被回收,拥有这个对象的线程是创建这个对象的线程,而不是使用QObject::moveToThread()之后的线程。
如果没有事件循环,事件将永远不会传送给object,例如 ,你可以创建一个定时器QTimer,但是不使用exec()来开启事件循环,将永远不会发送timeout信号给这个定时器,这个定时器调用deletelater也将不会有用。这些规则对主线程同样适用。
你可以在任何时候在任何线程中使用线程安全函数QCoreApplication::postEvent()传送事件给任何对象,事件将会通过该对象所在线程的事件循环发送给目标对象。
事件过滤器在所有的线程中都支持,如此限制便要求监听对象和被监听对象必须处于同一个线程中,相似的,使用QCoreApplication::sendEvent()来分发事件的话,目标对象必须在调用改函数的线程中,而不像postEvent()可以分发事件给其他线程中的对象。
在其他线程中调用QObject的子类
QObejct和她所有的子类都是非线程安全的,这包括所有的事件分发系统,一定要小心的是,当你在其他线程中处理这个QObject子类对象时候,事件循环可能会分发事件给这个类。
当你在非本线程中调用QObject子类对象时候,可能会有事件到来,你应该把QObject子类对象的数据保护起来,否则将会发生灾难或者意想不到的结果。
和其他对象一样,QThread对象所在的线程也是创建它的按个线程,而不是调用了run()之后所产生的那个新线程,在QThread子类中创建槽函数是不安全的,除非你用锁来保护成员变量。
但是,你可以在run()函数里面发射信号,因为发射信号是线程安全的。
跨线程的信号和槽
QT支持的信号和槽连接类型
1、 Auto Connection,自动连接,这个是默认的
2、 Direct Connection直接连接
3、 Queued Connection队列连接
4、 Blocking Queued Connection阻塞队列连接
5、 Unique Connection 独一无二的连接
Mandelbrot例子,为了防止界面假死,所有的计算都在另一个线程中进行,而主线程负责绘图和用户交互
Blocking Fortune Client例子使用一个单独的线程来异步的连接TCP服务端
Concurrent programming
QtConcurrent提供了高层次的API,可以让我们不用使用低级的基元,例如互斥锁,读写锁,信号量等来实现多线程。使用QtConcurrent写的程序可以根据处理器的核心数自动调节线程数目。这标示着现在写的程序在将来部署到多核心系统上仍然可以
QT中的线程支持
线程和SQL模块
和SQL的连接只能在创建连接的线程中使用,在线程之间移动连接或者在其他线程中制造疑问将不支持。
此外,被QSqlDrivers模块使用的第三方库在多线程中可能会带来更大的限制。查阅数据库的客户端来获得更多的信息
线程中的绘图
在新建的线程中可以使用QPainter来在QImage, QPrinter, 和 QPicture 绘图设备上绘图,但是不支持在QPixmaps和QWidget上绘图。在mac os上如果你在GUI线程外绘图,系统讲不会显现所绘的图片。
任何时候可以有任何多个线程中绘图,但是同一时刻只能有一个线程在一个指定的绘图设备上绘图,换句话说,两个线程可以在同一时候绘图,只要在不同的QImage上,但是同一时刻只能有一个线程在这个QImage上绘图。
注意:在没有fonconfig支持的X11系统上,线程外不能打印文字,可以使用QFontDatabase::supportsThreadedFontRendering() 来检测你的系统是否支持在GUI线程外打印字体。
多线程的文本处理
QTextDocument, QTextCursor等都是可重入的
注意,QTextDocument可能在创建时候就有一个QPixmap,这时候要使用QTextDocument::clone()来创建一个QTextDocument的拷贝来传递给另一个线程,例如打印。
多线程和SVG 模块
QtSvg模块中的QSvgGenerator和QSvgRenderer都是可重入的
发表评论
要发表评论,您必须先登录。