当前位置:   article > 正文

Qt之线程的同步和互斥_qt中实现线程同步互斥的方式有哪些?

qt中实现线程同步互斥的方式有哪些?

线程同步基础

临界资源每次只允许一个线程进行访问的资源

线程间互斥多个线程在同一时刻都需要访问临界资源

线程锁能够保证临界资源的安全性,通常,每个临界资源需要一个线程锁进行保护

线程死锁线程间相互等待临界资源而造成彼此无法继续执行。

产生死锁的条件

A、系统中存在多个临界资源且临界资源不可抢占

B、线程需要多个临界资源才能继续执行

死锁的避免

A、对使用的每个临界资源都分配一个唯一的序号

B、对每个临界资源对应的线程锁分配相应的序号

C、系统中的每个线程按照严格递增的次序请求临界资源

        QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了线程同步的手段。使用线程的主要想法是希望它们可以尽可能并发执行,而一些关键点上线程之间需要停止或等待。例如,假如两个线程试图同时访问同一个全局变量,结果可能不如所愿。

互斥量QMutex

        QMutex 提供相互排斥的锁,或互斥量。在一个时刻至多一个线程拥有mutex,假如一个线程试图访问已经被锁定的mutex,那么线程将休眠,直到拥有mutex的线程对此mutex解锁。QMutex常用来保护共享数据访问。QMutex类所以成员函数是线程安全的。

  1. 头文件声明:#include <QMutex>
  2. 互斥量声明:QMutex m_Mutex;
  3. 互斥量加锁:m_Mutex.lock();
  4. 互斥量解锁:m_Mutex.unlock();

        如果对没有加锁的互斥量进行解锁,结果是未知的。

示例场景:

继承QThread类实现多线程

  1. #ifndef MYTHREAD_H
  2. #define MYTHREAD_H
  3. #include <QObject>
  4. #include <QThread>
  5. class MyThread :public QThread
  6. {
  7. Q_OBJECT
  8. public:
  9. MyThread();
  10. protected:
  11. virtual void run();
  12. private:
  13. };
  14. #endif // MYTHREAD_H
  1. #include "mythread.h"
  2. #include <QDebug>
  3. externint global_Val;
  4. MyThread::MyThread()
  5. {
  6. qDebug()<<"mainThread::currentId:"<<QThread::currentThreadId();
  7. }
  8. void MyThread::run()
  9. {
  10. while (global_Val>0) {
  11. qDebug()<<"threadId:"<< QThread::currentThreadId()<<" global_val:"<<global_Val--;
  12. // QThread::msleep(200);
  13. }
  14. qDebug()<<"Task finish";
  15. }
  1. #include <QCoreApplication>
  2. #include "mythread.h"
  3. int global_Val = 10;
  4. int main(int argc, char *argv[])
  5. {
  6. QCoreApplication a(argc, argv);
  7. MyThread thread1,thread2;
  8. thread1.start();
  9. thread2.start();
  10. return a.exec();
  11. }

        定义全局变量,两个线程都可以访问,当两个线程同时访问时,会发生资源的争夺,造成不可预知的

程序运行结果如下

  1. mainThread::currentId: 0x222c
  2. mainThread::currentId: 0x222c
  3. threadId: 0x3df4 global_val: 10
  4. threadId: 0x3df4 global_val: 8
  5. threadId: 0x3df4 global_val: 7
  6. threadId: 0x10b8 global_val: 9
  7. threadId: 0x3df4 global_val: 6
  8. threadId: 0x10b8 global_val: 5
  9. threadId: 0x3df4 global_val: 4
  10. threadId: 0x3df4 global_val: 2
  11. threadId: 0x3df4 global_val: 1
  12. threadId: 0x10b8 global_val: 3
  13. Task finish
  14. Task finish

可以看到数据是乱的,这是没加锁的情况,接下来加上锁

  1. #include "mythread.h"
  2. #include <QDebug>
  3. externint global_Val;
  4. extern QMutex globlMutex; //锁一定是全局的
  5. MyThread::MyThread()
  6. {
  7. qDebug()<<"mainThread::currentId:"<<QThread::currentThreadId();
  8. }
  9. void MyThread::run()
  10. {
  11. while (global_Val>0) {
  12. globlMutex.lock();
  13. qDebug()<<"threadId:"<< QThread::currentThreadId()<<" global_val:"<<global_Val--;
  14. // QThread::msleep(200);
  15. globlMutex.unlock();
  16. }
  17. qDebug()<<"Task finish";
  18. }

        互斥量的加锁和解锁必须在同一线程中成对出现。

        运行之后就不会出现资源抢夺造成数据丢失等等问题了

  1. mainThread::currentId: 0x3b28
  2. mainThread::currentId: 0x3b28
  3. threadId: 0x1ae8 global_val: 10
  4. threadId: 0x1024 global_val: 9
  5. threadId: 0x1ae8 global_val: 8
  6. threadId: 0x1024 global_val: 7
  7. threadId: 0x1ae8 global_val: 6
  8. threadId: 0x1024 global_val: 5
  9. threadId: 0x1ae8 global_val: 4
  10. threadId: 0x1024 global_val: 3
  11. threadId: 0x1ae8 global_val: 2
  12. threadId: 0x1024 global_val: 1
  13. Task finish
  14. threadId: 0x1ae8 global_val: 0
  15. Task finish

如果觉得10太小了可以换成100,在未加锁的情况下数据的丢失更为严重

互斥锁QMutexLocker

        在较复杂的函数和异常处理中对QMutexmutex对象进行lock()和unlock()操作将会很复杂,进入点要lock(),在所有跳出点都要unlock(),很容易出现在某些跳出点未调用unlock(),所以Qt引进了QMutex的辅助类QMutexLocker来避免lock()unlock()操作。在函数需要的地方建立QMutexLocker对象,并把mutex指针传给QMutexLocker对象,此时mutex已经加锁,等到退出函数后,QMutexLocker对象局部变量会自己销毁,此时mutex解锁。

  1. 头文件声明: #include<QMutexLocker>
  2. 互斥锁声明: QMutexLocker mutexLocker(&m_Mutex);
  3. 互斥锁加锁: 从声明处开始(在构造函数中加锁)
  4. 互斥锁解锁: 出了作用域自动解锁(在析构函数中解锁)

使用互斥锁进行线程的同步

  1. #include "mythread.h"
  2. #include <QDebug>
  3. externint global_Val;
  4. extern QMutex globlMutex; //锁一定是全局的
  5. MyThread::MyThread()
  6. {
  7. qDebug()<<"mainThread::currentId:"<<QThread::currentThreadId();
  8. }
  9. void MyThread::run()
  10. {
  11. while (global_Val>0) {
  12. QMutexLocker locker(&globlMutex);
  13. qDebug()<<"threadId:"<< QThread::currentThreadId()<<" global_val:"<<global_Val--;
  14. // QThread::msleep(200);
  15. }
  16. qDebug()<<"Task finish";
  17. }

条件变量QWaitCondition

        Qt里面叫等待条件,Linux下叫条件变量,我统一都称呼为条件变量

        QWaitCondition 允许线程在某些情况发生时唤醒另外的线程。一个或多个线程可以阻塞等待QWaitCondition ,用wakeOne()或wakeAll()设置一个条件。wakeOne()随机唤醒一个,wakeAll()唤醒所有

  1. QWaitCondition ()
  2. bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX )
  3. bool wait ( QReadWriteLock * readWriteLock, unsigned long time = ULONG_MAX )
  4. void wakeOne ()
  5. void wakeAll ()
  6. 头文件声明: #include <QWaitCondition>
  7. 等待条件声明: QWaitCondtion m_WaitCondition;
  8. 等待条件等待: m_WaitConditon.wait(&m_muxtex, time);
  9. 等待条件唤醒: m_WaitCondition.wakeAll();

        在经典的生产者-消费者场合中,生产者首先必须检查缓冲是否已满,如果缓冲区已满,线程停下来等待 notfull条件。如果没有满,在缓冲中生产数据,激活条件 nottempty。使用mutex来保护对buffer的访问。QWaitCondition::wait() 接收一个mutex作为参数,mutex被调用线程初始化为锁定状态。在线程进入休眠状态之前,mutex会被解锁。而当线程被唤醒时,mutex会处于锁定状态,从锁定状态到等待状态的转换是原子操作。当程序开始运行时,只有生产者可以工作,消费者被阻塞等待nottempty条件,一旦生产者在缓冲中放入一个字节,nottempty条件被激发,消费者线程于是被唤醒。

生产者和消费者示例:

  1. #include <QtCore/QCoreApplication>
  2. #include <QThread>
  3. #include <QWaitCondition>
  4. #include <QMutex>
  5. #include <QDebug>
  6. #define BUFFER_SIZE 2
  7. /*生产者*/
  8. class producons
  9. {
  10. public:
  11. int buffer[BUFFER_SIZE]; /*数据*/
  12. QMutex lock; //互斥锁
  13. int readpos,writepos; //读写位置
  14. QWaitCondition nottempty; //条件变量 没有空间
  15. QWaitCondition notfull; //条件变量 没有货物
  16. producons()
  17. {
  18. readpos = writepos = 0;
  19. }
  20. };
  21. producons buffer; //生产者对象
  22. class Producor:public QThread
  23. {
  24. public:
  25. void run();
  26. void put(producons * prod,int data);
  27. };
  28. void Producor::run()
  29. {
  30. int n;
  31. for(n = 0;n<5;n++)
  32. {
  33. qDebug()<<"生产者睡眠 1s...";
  34. sleep(1);
  35. qDebug()<<"生产信息:" << n;
  36. put(&buffer, n);
  37. }
  38. for(n=5; n<10; n++)
  39. {
  40. qDebug()<<"生产者睡眠 3s...";
  41. sleep(3);
  42. qDebug()<<"生产信息:"<< n;
  43. put(&buffer,n);
  44. }
  45. put(&buffer, -1);
  46. qDebug()<<"结束生产者!\n";
  47. return;
  48. }
  49. void Producor::put(producons *prod, int data)
  50. {
  51. prod->lock.lock();
  52. //write until buffer not full
  53. while((prod->writepos + 1)%BUFFER_SIZE == prod->readpos)
  54. {
  55. qDebug()<<"生产者等待生产,直到buffer有空位置";
  56. prod->notfull.wait(&prod->lock);
  57. }
  58. //将数据写入到buffer里面去
  59. prod->buffer[prod->writepos] = data;
  60. prod->writepos++;
  61. if(prod->writepos >= BUFFER_SIZE)
  62. prod->writepos = 0;
  63. //仓库已满,等待消费者消费
  64. prod->nottempty.wakeAll();
  65. prod->lock.unlock();
  66. }
  67. class Consumer:public QThread
  68. {
  69. public:
  70. void run();
  71. int get(producons *prod);
  72. };
  73. void Consumer::run()
  74. {
  75. int d = 0;
  76. while(1)
  77. {
  78. qDebug()<<"消费者睡眠 2s...";
  79. sleep(2);
  80. d = get(&buffer);
  81. qDebug()<<"读取信息:"<< d;
  82. if(d == -1) break;
  83. }
  84. qDebug()<<"结束消费者!";
  85. return;
  86. }
  87. int Consumer::get(producons *prod)
  88. {
  89. int data;
  90. prod->lock.lock(); //加锁
  91. while(prod->writepos == prod->readpos)
  92. {
  93. qDebug()<<"消费者等待,直到buffer有消息\n";
  94. prod->nottempty.wait(&prod->lock);
  95. }
  96. //读取buffer里面的消息
  97. data = prod->buffer[prod->readpos];
  98. prod->readpos++;
  99. if(prod->readpos >=BUFFER_SIZE)
  100. prod->readpos = 0;
  101. //触发非满条件变量 告诉生产者可以生产
  102. prod->notfull.wakeAll();
  103. prod->lock.unlock();
  104. return data;
  105. }
  106. int main(int argc, char *argv[])
  107. {
  108. QCoreApplication a(argc, argv);
  109. Producor productor;
  110. Consumer consumer;
  111. productor.start();
  112. consumer.start();
  113. productor.wait();
  114. consumer.wait();
  115. return a.exec();
  116. }
  1. 生产者睡眠 1s...
  2. 消费者睡眠 2s...
  3. 生产信息: 0
  4. 生产者睡眠 1s...
  5. 读取信息: 0
  6. 生产信息: 1
  7. 消费者睡眠 2s...
  8. 生产者睡眠 1s...
  9. 生产信息: 2
  10. 生产者等待生产,直到buffer有空位置
  11. 读取信息: 1
  12. 生产者睡眠 1s...
  13. 消费者睡眠 2s...
  14. 生产信息: 3
  15. 生产者等待生产,直到buffer有空位置
  16. 读取信息: 2
  17. 生产者睡眠 1s...
  18. 消费者睡眠 2s...
  19. 生产信息: 4
  20. 生产者等待生产,直到buffer有空位置
  21. 读取信息: 3
  22. 生产者睡眠 3s...
  23. 消费者睡眠 2s...
  24. 读取信息: 4
  25. 消费者睡眠 2s...
  26. 生产信息: 5
  27. 生产者睡眠 3s...
  28. 读取信息: 5
  29. 消费者睡眠 2s...
  30. 生产信息: 6
  31. 消费者等待,直到buffer有消息
  32. 生产者睡眠 3s...
  33. 读取信息: 6
  34. 消费者睡眠 2s...
  35. 消费者等待,直到buffer有消息
  36. 生产信息: 7
  37. 生产者睡眠 3s...
  38. 读取信息: 7
  39. 消费者睡眠 2s...
  40. 消费者等待,直到buffer有消息
  41. 生产信息: 8
  42. 生产者睡眠 3s...
  43. 读取信息: 8
  44. 消费者睡眠 2s...
  45. 消费者等待,直到buffer有消息
  46. 生产信息: 9
  47. 生产者等待生产,直到buffer有空位置
  48. 读取信息: 9
  49. 消费者睡眠 2s...
  50. 结束生产者!

读写锁QReadWriteLock

        QReadWriterLock 与QMutex相似,但对读写操作访问进行区别对待,可以允许多个读者同时读数据,但只能有一个写,并且写读操作不同同时进行。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。 QReadWriterLock默认模式是NonRecursive

QReadWriterLock类成员函数如下:

  1. QReadWriteLock ( )
  2. QReadWriteLock ( RecursionMode recursionMode )
  3. void lockForRead ()
  4. void lockForWrite ()
  5. bool tryLockForRead ()
  6. bool tryLockForRead ( int timeout )
  7. bool tryLockForWrite ()
  8. bool tryLockForWrite ( int timeout )
  9. boid unlock ()

使用示例:

  1. QReadWriteLock lock;
  2. void ReaderThread::run()
  3. {
  4. lock.lockForRead();
  5. read_file();
  6. lock.unlock();
  7. }
  8. void WriterThread::run()
  9. {
  10. lock.lockForWrite();
  11. write_file();
  12. lock.unlock();
  13. }

QReadLocker和QWriteLocker

        在较复杂的函数和异常处理中对QReadWriterLocklock对象进行lockForRead()/lockForWrite()unlock()操作将会很复杂,进入点要lockForRead()/lockForWrite(),在所有跳出点都要unlock(),很容易出现在某些跳出点未调用unlock(),所以Qt引进了QReadLockerQWriteLocker类来简化解锁操作。在函数需要的地方建立QReadLockerQWriteLocker对象,并把lock指针传给QReadLockerQWriteLocker对象,此时lock已经加锁,等到退出函数后,QReadLockerQWriteLocker对象局部变量会自己销毁,此时lock解锁

  1. QReadWriteLock lock;
  2. QByteArray readData()
  3. {
  4. QReadLocker locker(&lock);
  5. ...
  6. return data;
  7. }

信号量QSemaphore

        QSemaphore 是QMutex一般化,是特殊的线程锁,允许多个线程同时访问临界资源,而一个QMutex只保护一个临界资源。QSemaphore 类的所有成员函数是线程安全的。

QSemaphore 类成员函数:

  1. QSemaphore ( int n = 0 )
  2. void acquire ( int n = 1 )
  3. int available () const
  4. void release ( int n = 1 )
  5. bool tryAcquire ( int n = 1 )
  6. bool tryAcquire ( int n, int timeout )

伪代码:

  1. constint BufferSize = 8192;
  2. QSemaphore production(BufferSize);
  3. QSemaphore consumption;
  4. production.acquire();
  5. //对BufferSize锁着后操作
  6. consumption.release();
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/865462
推荐阅读
相关标签
  

闽ICP备14008679号