Qt中线程的解释QThread(线程)代表一个在应用程序中可以独立控制的线程可以和进程中的其他线程分享数据。QThread 对象管理程序中的一个控制线程。QThreads 在 run 中开始执行。默认情况下run 通过调用 exec 来启动事件循环并在线程内运行 Qt 事件循环线程的使用条件常用于比较耗时的任务使用线程执行的话主界面才能正常运行。创建线程的方法继承QThread 重写run函数继承QObject 通过moveToThreadthread交给thread执行继承QRunnable 重写run函数但必须使用线程池QThreadPool运行继承QThread的特点可以通过信号槽与外界进行通信线程中的对象必须在run函数中创建每次新建一个线程都需要继承QThread实现一个新类使用不太方便。要自己进行资源管理线程释放和删除。并且频繁的创建和释放会带来比较大的内存开销。适用场景那些常驻内存的任务继承QObject 通过moveToThreadthread的特点使用比较简单方便但只能执行槽函数中的内容需要通过信号与槽的方式连接创建对象时不能指定父类如果指定父类那么将无法调用到线程中注意线程中无法使用任何界面部件类第一种创建方式class Thread : public QThread //继承QThread { Q_OBJECT public: explicit Thread(QObject *parent nullptr); protected: void run() override;//重写run函数 }; void Thread::run() { //执行比较耗时的任务 } //在主类中调用 Thread thread1; thread.start();//开起线程就会执行run函数第二种创建方式class Worker : public QObject //继承自QObject { Q_OBJECT public slots: void doWork(const QString parameter) { QString result; /* ... here is the expensive or blocking operation ... */ emit resultReady(result); } signals: void resultReady(const QString result); }; class Controller : public QObject { Q_OBJECT QThread workerThread;//创建一个线程 public: Controller() { Worker *worker new Worker;//创建一个工作对象 worker-moveToThread(workerThread);//移动到线程种执行 //当线程执行完成后销毁work对象 connect(workerThread, QThread::finished, worker, QObject::deleteLater); connect(this, Controller::operate, worker, Worker::doWork); connect(worker, Worker::resultReady, this, Controller::handleResults); workerThread.start();//开启线程 } ~Controller() { workerThread.quit();//关闭线程 workerThread.wait();//堵塞线程 } public slots: void handleResults(const QString ); signals: void operate(const QString );QThread 常用的函数exec进入事件循环并等待 exit 被调用返回传递给exit 的值。如果通过 quit 调用 exit则返回的值为 0。此函数旨在从 run 中调用。必须调用此函数才能启动事件处理exit告知线程的事件循环以返回代码退出currentThread()返回指向管理当前正在执行的线程的 QThread 的指针。idealThreadCount()返回可在系统上运行的理想线程数,如果无法检测到处理器内核数则此函数返回 1。isDone()如果线程完成返回true 否则返回 falseisRunning如果线程正在运行返回true 否则返回 falsemsleep强制当前线程休眠毫秒sleep强制当前线程休眠几秒钟此功能不保证准确性。在重负载条件下应用程序可能会休眠超过秒。usleep()强制当前线程休眠usecs微秒此功能不保证准确性。在重负载条件下应用程序可能比usecs休眠时间更长requestInterruption()请求中断线程。该请求是建议性的由线程上运行的代码决定是否以及如何对此类请求进行操作。此函数不会停止线程上运行的任何事件循环也不会以任何方式终止它。run()线程的起点,调用 start( 后新创建的线程调用此函数,您可以重新实现此函数以促进高级线程管理quit()告知线程的事件循环退出并返回代码 0成功。等效于调用 QThread::exit0wait()阻塞线程,直到如果线程已完成此函数将返回 true。如果线程尚未启动它也返回 true。或到了最后期限。如果达到截止时间此函数将返回 falsestart()通过调用 run 开始执行线程。操作系统将根据优先级参数调度线程。如果线程已在运行则此函数不执行任何操作terminate()终止线程的执行。线程可能会也可能不会立即终止具体取决于操作系统的调度策略。在 terminate 之后使用 QThreadwait 可以肯定。signalsfinished()此信号在完成执行之前从关联的线程发出started()在调用run 函数之前当关联线程开始执行时此信号从关联线程发出例子主界面有开启暂停关闭按钮在一个QLabel显示线程中的数据创建一个myThread类继承自QThreadclass myThread : public QThread { Q_OBJECT public: explicit myThread(QObject *parent nullptr); void stop_thread()//暂停 { thread_runfalse;//设置为不可执行 } void run()//重写run函数 (函数直接在这里实现比较好查看) { while(thread_run)//如果可以执行 { a10; emit show_number(a);//触发信号 sleep(1);//休眠5秒 } thread_runtrue; } protected: volatile bool thread_runtrue;//判断是否可以运行 int a0; signals: void show_number(int i);//发送信号给主窗口显示数据 };主类中的使用.cpp文件中Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); myThread *threadnew myThread; qDebug()本机线程数thread-idealThreadCount();//查看自己的线程数 connect(ui-pushButton,QPushButton::clicked,[]()//开启线程 { thread-start(); qDebug()线程已开启; qDebug()线程idthread-currentThreadId(); }); connect(ui-pushButton_2,QPushButton::clicked,[]()//暂停线程 { thread-stop_thread();//暂停 qDebug()线程已暂停; }); connect(ui-pushButton_3,QPushButton::clicked,[]()//结束线程 { ui-lcdNumber-display(0); thread-stop_thread();//暂停 thread-quit(); thread-wait(); thread-deleteLater();//销毁 ui-pushButton-setEnabled(false);//开启不可点击 }); connect(thread,SIGNAL(show_number(int)),this,SLOT(show_data(int))); } void Widget::show_data(int s) { ui-lcdNumber-display(s); } Widget::~Widget() { delete ui; }重入和线程安全在整个文档中重入和线程安全用于标记类和函数从而表明怎样在多线程应用中使用它们。线程安全函数可以从多个线程同时调用即使调用使用共享数据也是如此因为对共享数据的所有引用都是序列化的。也可以从多个线程同时调用重入函数但前提是每个调用都使用自己的数据以上可以得出结论线程安全函数始终是可重入的但可重入函数并不总是线程安全的。通过扩展如果可以从多个线程安全地调用一个类的成员函数只要每个线程使用该类的不同实例则该类就被称为可重入类。如果可以从多个线程安全地调用该类的成员函数则该类是线程安全的即使所有线程都使用该类的同一实例也是如此。重入c类通常是可重入的因为它们只能访问自己的成员数据。任何线程都可以在可重入类的实例上调用成员函数只要没有其他线程可以同时调用该类的同一实例上的成员函数。例如class Counter { public: Counter() { n 0; } void increment() { n; } void decrement() { --n; } int value() const { return n; } private: int n; };上面的类是可重入的但这并不是线程安全的当有多个线程同时修改类的一个成员变量可能会会产生多种结果 。因为和--的操作并不是总是原子的原子操作是指一个操作不会被其他线程中断。它们会分为3个机械指令在寄存器中加载变量的值递增或递减寄存器的值将寄存器的值存储回主存储器当有多个线程同时加载变量的旧值然后递增它们的寄存器然后将其存储回去最终它们会相互覆盖变量只会递增一次。线程安全显然当有多个线程访问时访问必须序列化当有一个线程访问时其他线程必须等待。例如使用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; };在构造函数中自动锁定互斥锁在函数结束时调用析构函数时解锁互斥锁锁定互斥锁可确保序列化来自不同线程的访问。数据成员mutex是用限定符mutable声明的因为我们需要锁定和解锁value中的互斥锁因为这是一个 const 函数。同步线程线程的目的是允许并行运行但有时线程必须停止等待其他线程。例如如果两个线程尝试访问同一个变量这样的话结果是未定义的。强制线程相互等待的原则成为互斥是一种保护共享资源的常用技术。同步线程类QMutex 互斥锁QReadWriteLock 读-写锁QSemaphore 信号量QWaitCondition 条件变量QMutex互斥锁提供一个互斥锁在任何事件至多有一个线程可以获得mutex。如果一个线程尝试获得mutex而mutex已经锁住那么这个线程将会睡眠QReadWriteLock 读-写锁读-写锁于QMutex相似但它对共享数据进行访问的区分分为“读”“写”访问允许多个线程同时对数据进行“读”访问QReadWiteLock的并行度大于QMutexQSemaphore 信号量QSemaphore是QMutex的概括可保护一定数量的相同资源。相比之下QMutex 只保护一种资源。信号量示例显示了信号量的典型应用同步对生产者和使用者之间循环缓冲区的访问QWaitCondition 条件变量QWaitCondition允许一个线程在一些条件满足时唤醒其他线程 不是通过强制执行互斥而是通过提供条件变量来同步线程。一个或多个线程可以被堵塞来等待一个QWaitCondition使用wakeOne()可以唤醒一个随机选取的等待的线程使用wakeAll可以唤醒所有正在等待的线程QMutexQMutex通常和QMutexLocker一起使用才可以确保这可以轻松确保锁定和解锁的执行一致。常用函数isRecursive判断互斥锁是否为递归Qt 5.7引入lock锁定互斥锁如果另一个线程锁定了互斥锁则此调用将阻塞直到该线程解锁它tryLockint 尝试锁定互斥锁如果已获得锁则函数返回true 否则返回 false可以设置等待时间。如果获得锁则必须使用unlock解锁互斥锁这样才能在另一个线程才能成功锁定它。try_lock()尝试锁定互斥锁如果已获得锁则函数返回true 否则返回 false,提供此功能是为了与标准库概念兼容,等价于tryLockQt 5.8 引入try_lock_for()尝试锁定互斥锁如果已获得锁则函数返回true 否则返回 false,如果获得锁必须使用unlock解锁互斥锁如果为递归互斥锁允许在同一线程的容易个互斥锁上多次调用该函数如果此互斥锁是非递归互斥锁则在尝试递归锁定互斥锁时此函数将始终返回 false。Qt 5.8 引入try_lock_until尝试锁定互斥锁如果已获得锁则函数返回true 否则返回 false,如果获得锁必须使用unlock解锁互斥锁如果为递归互斥锁允许在同一线程的容易个互斥锁上多次调用该函数如果此互斥锁是非递归互斥锁则在尝试递归锁定互斥锁时此函数将始终返回 false。Qt 5.8 引入unlock解锁互斥锁。QMutexQMutexQMutex::RecursionModemode)创建一个互斥锁时可以设置模式QMutex::Recursive在这种模式下线程可以多次锁定同一个互斥锁,并且在进行相应数量的 unlock 调用之前互斥锁不会被解锁QMutex::NonRecursive在此模式下线程只能锁定一次互斥锁互斥锁使用场景当一个变量同时被多个线程访问int number10; void text() { number*2; number5; } void text1() { number10; number*3; }正常调用 text和text1()的话text() number2*1020525 text1() number251035*3105同时调用的话可能会发生以下情况线程1调用text() number10*220; 线程2调用text1()现在text()的调用暂停 number201030 number30*390; 继续完成线程text() number90595为了防止以上情况可以上个互斥锁使得调用完某个函数才能调用其他函数。QMutex mutex;//互斥锁 int number10 void text() { mutex.lock();//上锁 number*2; number5; mutex.unlock();//解锁 } void text1() { mutex.lock();//上锁 number10; number*3; mutex.unlock();//解锁 }QMutexLocker类QMutexLocker类是一个方便点的类可以简化锁定和解锁的互斥锁。使用方法QMutexLocker应该在需要锁定QMutex的函数中创建创建一个QMutexLock时互斥锁被锁定。更加方便函数mutex(()返回正在运行的互斥锁relock重新锁定未锁定的互斥锁锁。unlock解锁此互斥锁使用QMutex的情况需要在分支中解锁互斥锁int complexFunction(int flag) { mutex.lock(); int retVal 0; switch (flag) { case 0: case 1: retVal moreComplexFunction(flag); break; case 2: { int status anotherFunction(); if (status 0) { mutex.unlock(); return -2; } retVal status flag; } break; default: if (flag 10) { mutex.unlock(); return -1; } break; } mutex.unlock(); return retVal; }QMutexLocker的使用int complexFunction(int flag) { QMutexLocker locker(mutex);//创建一个QmutexLocker对象 int retVal 0; switch (flag) { case 0: case 1: return moreComplexFunction(flag); case 2: { int status anotherFunction(); if (status 0) return -2; retVal status flag; } break; default: if (flag 10) return -1; break; } return retVal; }当函数执行完后QMutexLocker对象销毁时互斥锁会解锁就不用每个分支去解锁。使用QMutexLocker::mutex()可以获取当前正在运行的互斥锁。QReadWriteLock(读-写锁)读写锁是一种同步工具用于保护可以访问以进行读取和写入的资源。如果要允许多个线程同时具有只读访问权限则这种类型的锁定很有用但是一旦一个线程想要写入资源就必须阻止所有其他线程直到写入完成。函数lockForRead锁定读取如果另一个线程已锁定写入此函数将阻止当前线程。如果线程已锁定写入则无法锁定读取。lockForWrite锁定写入如果另一个线程包括当前线程已锁定以进行读取或写入则此函数将阻止当前线程tryLockForRead尝试锁定以进行读取如果线程已锁定写入则无法锁定读取tryLockForWrite尝试锁定以进行写入如果线程已锁定以进行读取则无法锁定写入unlock解锁QReadWriteLock通常使用于经常读取数据可以多个线程同时读取数据。QReadWriteLock lock; void ReaderThread::run() { ... lock.lockForRead(); read_file(); lock.unlock(); ... } void WriterThread::run() { ... lock.lockForWrite(); write_file(); lock.unlock(); ... }QSemaphore信号量信号量是互斥锁的概括。虽然互斥锁只能锁定一次但可以多次获取信号量。信号量通常用于保护一定数量的相同资源函数acquireint n尝试获取n个由信号量保护的资源当资源不够时将堵塞直到资源足够。releaseint n释放由信号量保护的n个资源此函数也可用于“创建”资源。available返回信号灯当前可用的资源数。这个数字永远不能是负数。tryAcquireint n尝试获取由信号量保护的资源成功返回true否则返回falsetryAcquireint n,int timeout尝试获取由信号量保护的资源成功返回true否则返回false调用最多等待timeout秒QSemaphore的创建QSemaphore::QSemaphore(int n0)创建新的信号量并将其保护的资源数初始化为n默认为 0。QSemaphore sem(5); sem.acquire(3); qDebug()资源还有sem.available()个; sem.acquire(2); qDebug()资源还有sem.available()个; sem.release(5); qDebug()资源还有sem.available()个;当释放的资源多余需要释放的资源时多余的会进行创建QSemaphore sem(5); sem.acquire(3); qDebug()资源还有sem.available()个; sem.release(5); qDebug()资源还有sem.available()个; sem.release(10); qDebug()资源还有sem.available()个;当资源少于需要获取的资源时不会获取成功。QSemaphore sem(5); if(sem.tryAcquire(7)){ qDebug()获取成功; } else { qDebug()获取失败; }QSemaphore信号量的生产者消费者问题。全局变量 const int DataSize1000;//生产者的数据量 const int BufferSize800;//缓冲区大小 char buffer[Buffersize]; QSemaphore freeBytes(BufferSize);//控制缓冲区的信号量 QSemaphore usedBytes;//控制已经使用的缓冲区生产者类class Producer public QThread { public: void run(); } void Producer::run() { qsrand(QTime(0.0.0).secsTo(QTime::currentTime()));//随机数 for(int i0;iDataSize;i){ freeBytes.acquire(); buffer[%BufferSize]ACGT[(int)qrand %4]; qDebug()QString(Producer:%1).arg(buffer[i%buffersize]); usedBytes.release(); }消费者类class Consumer public QThread { public: void run(); } void Producer::run() { for(int i0;iDataSize;i){ userBytes.acquire(); qDebug()QString(Producer:%1).arg(buffer[i%buffersize]); freeBytes.release(); } }mainint main(int argc, char *argv[]) { QCoreApplication app(argc, argv); Producer producer;//生产者 Consumer consumer;//消费者 producer.start();//开启线程 consumer.start();//开启线程 //两个线程调用wait()阻塞线程确保两个线程在退出前都有时间能完成main() producer.wait(); consumer.wait(); return 0; }QWaitCondition允许一个线程在一些条件满足时唤醒其他线程 不是通过强制执行互斥而是通过提供条件变量来同步线程。一个或多个线程可以被堵塞来等待一个QWaitCondition使用wakeOne()可以唤醒一个随机选取的等待的线程使用wakeAll可以唤醒所有正在等待的线程函数wait(QMutex*time)释放锁定的互斥锁并等待等待条件wakeOne()唤醒一个等待等待条件的线程。唤醒的线程取决于操作系统的调度策略无法控制或预测。如果要唤醒特定线程解决方案通常是使用不同的等待条件并让不同的线程等待不同的条件wakeAll()唤醒等待等待条件的所有线程。线程的唤醒顺序取决于操作系统的调度策略无法控制或预测。notify_one()提供此函数是为了与 STL 兼容。它等效于 wakeOneQt 5.8中引入notify_all()提供此函数是为了与 STL 兼容。它等效于 wakeAllQt 5.8中引入QWaitCondition的示例使用QWaitCondition的QMutex解决生产者-消费者问题设置全局变量const int DataSize 100000;//生产者将生成的数据量 const int BufferSize 8192;//缓冲区 char buffer[BufferSize]; //两个等待条件一个互斥锁和计数器 QWaitCondition bufferNotEmpty; QWaitCondition bufferNotFull; QMutex mutex; int numUsedBytes 0;生产者类class Producer : public QThread { public: Producer(QObject *parent NULL) : QThread(parent) { } void run() override { for (int i 0; i DataSize; i) { mutex.lock();//上锁 if (numUsedBytes BufferSize)//检查缓冲区是否已满 bufferNotFull.wait(mutex);//已满的话等待条件满足 mutex.unlock();//解锁 //存放数据(随机数) buffer[i % BufferSize] ACGT[QRandomGenerator::global()-bounded(4)]; mutex.lock();//上锁 numUsedBytes;计数器1 bufferNotEmpty.wakeAll();//唤醒全部线程 mutex.unlock();//解锁 } } };消费者类class Consumer : public QThread { Q_OBJECT public: Consumer(QObject *parent NULL) : QThread(parent) { } void run() override { for (int i 0; i DataSize; i) { mutex.lock(); if (numUsedBytes 0)//检查缓冲区是否为空 bufferNotEmpty.wait(mutex); mutex.unlock(); fprintf(stderr, %c, buffer[i % BufferSize]);//输出内容 mutex.lock(); --numUsedBytes;//计数器-1 bufferNotFull.wakeAll(); mutex.unlock(); } fprintf(stderr, \n); } signals: void stringConsumed(const QString text); };main函数int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); Producer producer;//生产者 Consumer consumer;//消费者 producer.start();//开启线程 consumer.start();//开启线程 //两个线程调用wait()阻塞线程确保两个线程在退出前都有时间能完成main() producer.wait(); consumer.wait(); return 0; }
Qt 线程
Qt中线程的解释QThread(线程)代表一个在应用程序中可以独立控制的线程可以和进程中的其他线程分享数据。QThread 对象管理程序中的一个控制线程。QThreads 在 run 中开始执行。默认情况下run 通过调用 exec 来启动事件循环并在线程内运行 Qt 事件循环线程的使用条件常用于比较耗时的任务使用线程执行的话主界面才能正常运行。创建线程的方法继承QThread 重写run函数继承QObject 通过moveToThreadthread交给thread执行继承QRunnable 重写run函数但必须使用线程池QThreadPool运行继承QThread的特点可以通过信号槽与外界进行通信线程中的对象必须在run函数中创建每次新建一个线程都需要继承QThread实现一个新类使用不太方便。要自己进行资源管理线程释放和删除。并且频繁的创建和释放会带来比较大的内存开销。适用场景那些常驻内存的任务继承QObject 通过moveToThreadthread的特点使用比较简单方便但只能执行槽函数中的内容需要通过信号与槽的方式连接创建对象时不能指定父类如果指定父类那么将无法调用到线程中注意线程中无法使用任何界面部件类第一种创建方式class Thread : public QThread //继承QThread { Q_OBJECT public: explicit Thread(QObject *parent nullptr); protected: void run() override;//重写run函数 }; void Thread::run() { //执行比较耗时的任务 } //在主类中调用 Thread thread1; thread.start();//开起线程就会执行run函数第二种创建方式class Worker : public QObject //继承自QObject { Q_OBJECT public slots: void doWork(const QString parameter) { QString result; /* ... here is the expensive or blocking operation ... */ emit resultReady(result); } signals: void resultReady(const QString result); }; class Controller : public QObject { Q_OBJECT QThread workerThread;//创建一个线程 public: Controller() { Worker *worker new Worker;//创建一个工作对象 worker-moveToThread(workerThread);//移动到线程种执行 //当线程执行完成后销毁work对象 connect(workerThread, QThread::finished, worker, QObject::deleteLater); connect(this, Controller::operate, worker, Worker::doWork); connect(worker, Worker::resultReady, this, Controller::handleResults); workerThread.start();//开启线程 } ~Controller() { workerThread.quit();//关闭线程 workerThread.wait();//堵塞线程 } public slots: void handleResults(const QString ); signals: void operate(const QString );QThread 常用的函数exec进入事件循环并等待 exit 被调用返回传递给exit 的值。如果通过 quit 调用 exit则返回的值为 0。此函数旨在从 run 中调用。必须调用此函数才能启动事件处理exit告知线程的事件循环以返回代码退出currentThread()返回指向管理当前正在执行的线程的 QThread 的指针。idealThreadCount()返回可在系统上运行的理想线程数,如果无法检测到处理器内核数则此函数返回 1。isDone()如果线程完成返回true 否则返回 falseisRunning如果线程正在运行返回true 否则返回 falsemsleep强制当前线程休眠毫秒sleep强制当前线程休眠几秒钟此功能不保证准确性。在重负载条件下应用程序可能会休眠超过秒。usleep()强制当前线程休眠usecs微秒此功能不保证准确性。在重负载条件下应用程序可能比usecs休眠时间更长requestInterruption()请求中断线程。该请求是建议性的由线程上运行的代码决定是否以及如何对此类请求进行操作。此函数不会停止线程上运行的任何事件循环也不会以任何方式终止它。run()线程的起点,调用 start( 后新创建的线程调用此函数,您可以重新实现此函数以促进高级线程管理quit()告知线程的事件循环退出并返回代码 0成功。等效于调用 QThread::exit0wait()阻塞线程,直到如果线程已完成此函数将返回 true。如果线程尚未启动它也返回 true。或到了最后期限。如果达到截止时间此函数将返回 falsestart()通过调用 run 开始执行线程。操作系统将根据优先级参数调度线程。如果线程已在运行则此函数不执行任何操作terminate()终止线程的执行。线程可能会也可能不会立即终止具体取决于操作系统的调度策略。在 terminate 之后使用 QThreadwait 可以肯定。signalsfinished()此信号在完成执行之前从关联的线程发出started()在调用run 函数之前当关联线程开始执行时此信号从关联线程发出例子主界面有开启暂停关闭按钮在一个QLabel显示线程中的数据创建一个myThread类继承自QThreadclass myThread : public QThread { Q_OBJECT public: explicit myThread(QObject *parent nullptr); void stop_thread()//暂停 { thread_runfalse;//设置为不可执行 } void run()//重写run函数 (函数直接在这里实现比较好查看) { while(thread_run)//如果可以执行 { a10; emit show_number(a);//触发信号 sleep(1);//休眠5秒 } thread_runtrue; } protected: volatile bool thread_runtrue;//判断是否可以运行 int a0; signals: void show_number(int i);//发送信号给主窗口显示数据 };主类中的使用.cpp文件中Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); myThread *threadnew myThread; qDebug()本机线程数thread-idealThreadCount();//查看自己的线程数 connect(ui-pushButton,QPushButton::clicked,[]()//开启线程 { thread-start(); qDebug()线程已开启; qDebug()线程idthread-currentThreadId(); }); connect(ui-pushButton_2,QPushButton::clicked,[]()//暂停线程 { thread-stop_thread();//暂停 qDebug()线程已暂停; }); connect(ui-pushButton_3,QPushButton::clicked,[]()//结束线程 { ui-lcdNumber-display(0); thread-stop_thread();//暂停 thread-quit(); thread-wait(); thread-deleteLater();//销毁 ui-pushButton-setEnabled(false);//开启不可点击 }); connect(thread,SIGNAL(show_number(int)),this,SLOT(show_data(int))); } void Widget::show_data(int s) { ui-lcdNumber-display(s); } Widget::~Widget() { delete ui; }重入和线程安全在整个文档中重入和线程安全用于标记类和函数从而表明怎样在多线程应用中使用它们。线程安全函数可以从多个线程同时调用即使调用使用共享数据也是如此因为对共享数据的所有引用都是序列化的。也可以从多个线程同时调用重入函数但前提是每个调用都使用自己的数据以上可以得出结论线程安全函数始终是可重入的但可重入函数并不总是线程安全的。通过扩展如果可以从多个线程安全地调用一个类的成员函数只要每个线程使用该类的不同实例则该类就被称为可重入类。如果可以从多个线程安全地调用该类的成员函数则该类是线程安全的即使所有线程都使用该类的同一实例也是如此。重入c类通常是可重入的因为它们只能访问自己的成员数据。任何线程都可以在可重入类的实例上调用成员函数只要没有其他线程可以同时调用该类的同一实例上的成员函数。例如class Counter { public: Counter() { n 0; } void increment() { n; } void decrement() { --n; } int value() const { return n; } private: int n; };上面的类是可重入的但这并不是线程安全的当有多个线程同时修改类的一个成员变量可能会会产生多种结果 。因为和--的操作并不是总是原子的原子操作是指一个操作不会被其他线程中断。它们会分为3个机械指令在寄存器中加载变量的值递增或递减寄存器的值将寄存器的值存储回主存储器当有多个线程同时加载变量的旧值然后递增它们的寄存器然后将其存储回去最终它们会相互覆盖变量只会递增一次。线程安全显然当有多个线程访问时访问必须序列化当有一个线程访问时其他线程必须等待。例如使用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; };在构造函数中自动锁定互斥锁在函数结束时调用析构函数时解锁互斥锁锁定互斥锁可确保序列化来自不同线程的访问。数据成员mutex是用限定符mutable声明的因为我们需要锁定和解锁value中的互斥锁因为这是一个 const 函数。同步线程线程的目的是允许并行运行但有时线程必须停止等待其他线程。例如如果两个线程尝试访问同一个变量这样的话结果是未定义的。强制线程相互等待的原则成为互斥是一种保护共享资源的常用技术。同步线程类QMutex 互斥锁QReadWriteLock 读-写锁QSemaphore 信号量QWaitCondition 条件变量QMutex互斥锁提供一个互斥锁在任何事件至多有一个线程可以获得mutex。如果一个线程尝试获得mutex而mutex已经锁住那么这个线程将会睡眠QReadWriteLock 读-写锁读-写锁于QMutex相似但它对共享数据进行访问的区分分为“读”“写”访问允许多个线程同时对数据进行“读”访问QReadWiteLock的并行度大于QMutexQSemaphore 信号量QSemaphore是QMutex的概括可保护一定数量的相同资源。相比之下QMutex 只保护一种资源。信号量示例显示了信号量的典型应用同步对生产者和使用者之间循环缓冲区的访问QWaitCondition 条件变量QWaitCondition允许一个线程在一些条件满足时唤醒其他线程 不是通过强制执行互斥而是通过提供条件变量来同步线程。一个或多个线程可以被堵塞来等待一个QWaitCondition使用wakeOne()可以唤醒一个随机选取的等待的线程使用wakeAll可以唤醒所有正在等待的线程QMutexQMutex通常和QMutexLocker一起使用才可以确保这可以轻松确保锁定和解锁的执行一致。常用函数isRecursive判断互斥锁是否为递归Qt 5.7引入lock锁定互斥锁如果另一个线程锁定了互斥锁则此调用将阻塞直到该线程解锁它tryLockint 尝试锁定互斥锁如果已获得锁则函数返回true 否则返回 false可以设置等待时间。如果获得锁则必须使用unlock解锁互斥锁这样才能在另一个线程才能成功锁定它。try_lock()尝试锁定互斥锁如果已获得锁则函数返回true 否则返回 false,提供此功能是为了与标准库概念兼容,等价于tryLockQt 5.8 引入try_lock_for()尝试锁定互斥锁如果已获得锁则函数返回true 否则返回 false,如果获得锁必须使用unlock解锁互斥锁如果为递归互斥锁允许在同一线程的容易个互斥锁上多次调用该函数如果此互斥锁是非递归互斥锁则在尝试递归锁定互斥锁时此函数将始终返回 false。Qt 5.8 引入try_lock_until尝试锁定互斥锁如果已获得锁则函数返回true 否则返回 false,如果获得锁必须使用unlock解锁互斥锁如果为递归互斥锁允许在同一线程的容易个互斥锁上多次调用该函数如果此互斥锁是非递归互斥锁则在尝试递归锁定互斥锁时此函数将始终返回 false。Qt 5.8 引入unlock解锁互斥锁。QMutexQMutexQMutex::RecursionModemode)创建一个互斥锁时可以设置模式QMutex::Recursive在这种模式下线程可以多次锁定同一个互斥锁,并且在进行相应数量的 unlock 调用之前互斥锁不会被解锁QMutex::NonRecursive在此模式下线程只能锁定一次互斥锁互斥锁使用场景当一个变量同时被多个线程访问int number10; void text() { number*2; number5; } void text1() { number10; number*3; }正常调用 text和text1()的话text() number2*1020525 text1() number251035*3105同时调用的话可能会发生以下情况线程1调用text() number10*220; 线程2调用text1()现在text()的调用暂停 number201030 number30*390; 继续完成线程text() number90595为了防止以上情况可以上个互斥锁使得调用完某个函数才能调用其他函数。QMutex mutex;//互斥锁 int number10 void text() { mutex.lock();//上锁 number*2; number5; mutex.unlock();//解锁 } void text1() { mutex.lock();//上锁 number10; number*3; mutex.unlock();//解锁 }QMutexLocker类QMutexLocker类是一个方便点的类可以简化锁定和解锁的互斥锁。使用方法QMutexLocker应该在需要锁定QMutex的函数中创建创建一个QMutexLock时互斥锁被锁定。更加方便函数mutex(()返回正在运行的互斥锁relock重新锁定未锁定的互斥锁锁。unlock解锁此互斥锁使用QMutex的情况需要在分支中解锁互斥锁int complexFunction(int flag) { mutex.lock(); int retVal 0; switch (flag) { case 0: case 1: retVal moreComplexFunction(flag); break; case 2: { int status anotherFunction(); if (status 0) { mutex.unlock(); return -2; } retVal status flag; } break; default: if (flag 10) { mutex.unlock(); return -1; } break; } mutex.unlock(); return retVal; }QMutexLocker的使用int complexFunction(int flag) { QMutexLocker locker(mutex);//创建一个QmutexLocker对象 int retVal 0; switch (flag) { case 0: case 1: return moreComplexFunction(flag); case 2: { int status anotherFunction(); if (status 0) return -2; retVal status flag; } break; default: if (flag 10) return -1; break; } return retVal; }当函数执行完后QMutexLocker对象销毁时互斥锁会解锁就不用每个分支去解锁。使用QMutexLocker::mutex()可以获取当前正在运行的互斥锁。QReadWriteLock(读-写锁)读写锁是一种同步工具用于保护可以访问以进行读取和写入的资源。如果要允许多个线程同时具有只读访问权限则这种类型的锁定很有用但是一旦一个线程想要写入资源就必须阻止所有其他线程直到写入完成。函数lockForRead锁定读取如果另一个线程已锁定写入此函数将阻止当前线程。如果线程已锁定写入则无法锁定读取。lockForWrite锁定写入如果另一个线程包括当前线程已锁定以进行读取或写入则此函数将阻止当前线程tryLockForRead尝试锁定以进行读取如果线程已锁定写入则无法锁定读取tryLockForWrite尝试锁定以进行写入如果线程已锁定以进行读取则无法锁定写入unlock解锁QReadWriteLock通常使用于经常读取数据可以多个线程同时读取数据。QReadWriteLock lock; void ReaderThread::run() { ... lock.lockForRead(); read_file(); lock.unlock(); ... } void WriterThread::run() { ... lock.lockForWrite(); write_file(); lock.unlock(); ... }QSemaphore信号量信号量是互斥锁的概括。虽然互斥锁只能锁定一次但可以多次获取信号量。信号量通常用于保护一定数量的相同资源函数acquireint n尝试获取n个由信号量保护的资源当资源不够时将堵塞直到资源足够。releaseint n释放由信号量保护的n个资源此函数也可用于“创建”资源。available返回信号灯当前可用的资源数。这个数字永远不能是负数。tryAcquireint n尝试获取由信号量保护的资源成功返回true否则返回falsetryAcquireint n,int timeout尝试获取由信号量保护的资源成功返回true否则返回false调用最多等待timeout秒QSemaphore的创建QSemaphore::QSemaphore(int n0)创建新的信号量并将其保护的资源数初始化为n默认为 0。QSemaphore sem(5); sem.acquire(3); qDebug()资源还有sem.available()个; sem.acquire(2); qDebug()资源还有sem.available()个; sem.release(5); qDebug()资源还有sem.available()个;当释放的资源多余需要释放的资源时多余的会进行创建QSemaphore sem(5); sem.acquire(3); qDebug()资源还有sem.available()个; sem.release(5); qDebug()资源还有sem.available()个; sem.release(10); qDebug()资源还有sem.available()个;当资源少于需要获取的资源时不会获取成功。QSemaphore sem(5); if(sem.tryAcquire(7)){ qDebug()获取成功; } else { qDebug()获取失败; }QSemaphore信号量的生产者消费者问题。全局变量 const int DataSize1000;//生产者的数据量 const int BufferSize800;//缓冲区大小 char buffer[Buffersize]; QSemaphore freeBytes(BufferSize);//控制缓冲区的信号量 QSemaphore usedBytes;//控制已经使用的缓冲区生产者类class Producer public QThread { public: void run(); } void Producer::run() { qsrand(QTime(0.0.0).secsTo(QTime::currentTime()));//随机数 for(int i0;iDataSize;i){ freeBytes.acquire(); buffer[%BufferSize]ACGT[(int)qrand %4]; qDebug()QString(Producer:%1).arg(buffer[i%buffersize]); usedBytes.release(); }消费者类class Consumer public QThread { public: void run(); } void Producer::run() { for(int i0;iDataSize;i){ userBytes.acquire(); qDebug()QString(Producer:%1).arg(buffer[i%buffersize]); freeBytes.release(); } }mainint main(int argc, char *argv[]) { QCoreApplication app(argc, argv); Producer producer;//生产者 Consumer consumer;//消费者 producer.start();//开启线程 consumer.start();//开启线程 //两个线程调用wait()阻塞线程确保两个线程在退出前都有时间能完成main() producer.wait(); consumer.wait(); return 0; }QWaitCondition允许一个线程在一些条件满足时唤醒其他线程 不是通过强制执行互斥而是通过提供条件变量来同步线程。一个或多个线程可以被堵塞来等待一个QWaitCondition使用wakeOne()可以唤醒一个随机选取的等待的线程使用wakeAll可以唤醒所有正在等待的线程函数wait(QMutex*time)释放锁定的互斥锁并等待等待条件wakeOne()唤醒一个等待等待条件的线程。唤醒的线程取决于操作系统的调度策略无法控制或预测。如果要唤醒特定线程解决方案通常是使用不同的等待条件并让不同的线程等待不同的条件wakeAll()唤醒等待等待条件的所有线程。线程的唤醒顺序取决于操作系统的调度策略无法控制或预测。notify_one()提供此函数是为了与 STL 兼容。它等效于 wakeOneQt 5.8中引入notify_all()提供此函数是为了与 STL 兼容。它等效于 wakeAllQt 5.8中引入QWaitCondition的示例使用QWaitCondition的QMutex解决生产者-消费者问题设置全局变量const int DataSize 100000;//生产者将生成的数据量 const int BufferSize 8192;//缓冲区 char buffer[BufferSize]; //两个等待条件一个互斥锁和计数器 QWaitCondition bufferNotEmpty; QWaitCondition bufferNotFull; QMutex mutex; int numUsedBytes 0;生产者类class Producer : public QThread { public: Producer(QObject *parent NULL) : QThread(parent) { } void run() override { for (int i 0; i DataSize; i) { mutex.lock();//上锁 if (numUsedBytes BufferSize)//检查缓冲区是否已满 bufferNotFull.wait(mutex);//已满的话等待条件满足 mutex.unlock();//解锁 //存放数据(随机数) buffer[i % BufferSize] ACGT[QRandomGenerator::global()-bounded(4)]; mutex.lock();//上锁 numUsedBytes;计数器1 bufferNotEmpty.wakeAll();//唤醒全部线程 mutex.unlock();//解锁 } } };消费者类class Consumer : public QThread { Q_OBJECT public: Consumer(QObject *parent NULL) : QThread(parent) { } void run() override { for (int i 0; i DataSize; i) { mutex.lock(); if (numUsedBytes 0)//检查缓冲区是否为空 bufferNotEmpty.wait(mutex); mutex.unlock(); fprintf(stderr, %c, buffer[i % BufferSize]);//输出内容 mutex.lock(); --numUsedBytes;//计数器-1 bufferNotFull.wakeAll(); mutex.unlock(); } fprintf(stderr, \n); } signals: void stringConsumed(const QString text); };main函数int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); Producer producer;//生产者 Consumer consumer;//消费者 producer.start();//开启线程 consumer.start();//开启线程 //两个线程调用wait()阻塞线程确保两个线程在退出前都有时间能完成main() producer.wait(); consumer.wait(); return 0; }