Qt 多线程同步与通信

1 多线程同步

Qt提供了以下几个类来完成这一点:QMutex、QMutexLocker、QSemphore、QWaitCondition。
当然可能还包含QReadWriteLocker、QReadLocker、QWriteLocker,但线程同步是应用很少,这里只做简单的讲解!
 
QMutex、QMutexLocker
QMutex类提供了一个保护一段临界区代码的方法,他每次只允许一个线程访问这段临界区代码。QMutex::lock()函数用来锁住互斥量,如果互斥量处于解锁状态,当前线程就会立即抓住并锁定它;否则当前线程就会被阻塞,直到持有这个互斥量的线程对其解锁。线程调用lock()函数后就会持有这个互斥量直到调用unlock()操作为止。QMutex还提供了一个tryLock()函数,如果互斥量已被锁定,就立即返回。
在问题出来了,如果要在stop()函数中使用mutex进行互斥操作,但unlock()操作写在那里?unlock()操作却不得不再return之后,从而导致unlock()操作永远也无法执行...
Qt提供了QMutexLocker类何以简化互斥量的处理,它在构造函数中接受一个QMutex对象作为参数并将其锁定,在析构函数中解锁这个互斥量。
 
  bool Thread::stop()  
         {  
             QMutexLocker locker(&mutex);  
             m_stop = true;  
             return m_stop;  
        } 
QReadWriteLocker、QReadLocker、QWriteLocker
下面是一段对QReadWriteLocker类的对象进行,读写锁的操作,比较简单,这里也不多做讲解了,自己看吧
MyData data;
QReadWriteLock lock;
void ReaderThread::run()
{
...
lock.lockForRead();
access_data_without_modifying_it(&data);
lock.unlock();
...
}
void WriterThread::run()
{
...
lock.lockForWrite();
modify_data(&data);
lock.unlock();
...
}

QSemphore

Qt中的信号量是由QSemaphore类提供的,信号量可以理解为互斥量功能的扩展,互斥量只能锁定一次而信号量可以获取多次,它可以用来保护一定数量的同种资源。
acquire(n)函数用于获取n个资源,当没有足够的资源时调用者将被阻塞直到有足够的可用资源。release(n)函数用于释放n个资源。
QSemaphore类还提供了一个tryAcquire(n)函数,在没有足够的资源是该函数会立即返回。
一个典型的信号量应用程序是在两个线程间传递一定数量的数据(DataSize),而这两个线程使用一定大小(BufferSize)的共享循环缓存。
 
      const int DataSize = 100000;  
       const int BufferSize = 4096;  
       char buffer[BufferSize]; 
生产者线程向缓存中写入数据,直到它到达终点,然后在起点重新开始,覆盖已经存在的数据。消费者线程读取前者产生的数据。
生产者、消费者实例中对同步的需求有两处,如果生产者过快的产生数据,将会覆盖消费者还没有读取的数据,如果消费者过快的读取数据,将越过生产者并且读取到一些垃圾数据。
解决这个问题的一个有效的方法是使用两个信号量:
QSemaphore freeSpace(BufferSize);  
QSemaphore usedSpace(0); 
freeSpace信号量控制生产者可以填充数据的缓存部分。usedSpace信号量控制消费者可以读取的区域。这两个信号量是互补的。其中freeSpace信号量被初始化为BufferSize(4096),表示程序一开始有BufferSize个缓冲区单元可被填充,而信号量usedSpace被初始化为0,表示程序一开始缓冲区中没有数据可供读取。
对于这个实例,每个字节就看作一个资源,实际应用中常会在更大的单位上进行操作,从而减小使用信号量带来的开销。
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
freeSpace.acquire();
buffer[i % BufferSize] = "MING"[uint(rand()) % 4];
usedSpace.release();
}
}

在生产者中,我们从获取一个“自由的”字节开始。如果缓存被消费者还没有读取的数据填满,acquire()的调用就会阻塞,直到消费者已经开始消耗这些数据为止。一旦我们已经获取了这个字节,我们就用一些随机数据("M"、"I"、"N"或"G")填充它并且把这个字节释放为“使用的”,所以它可以被消费者线程使用。

void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
usedSpace.acquire();
cerr << buffer[i % BufferSize];
freeSpace.release();
}
cerr << endl;
}
   int main()
{
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}
QWaitCondition
对生产者和消费者问题的另一个解决方法是使用QWaitCondition,它允许线程在一定条件下唤醒其他线程。其中wakeOne()函数在条件满足时随机唤醒一个等待线程,而wakeAll()函数则在条件满足时唤醒所有等待线程
下面重写生产者和消费者实例,以QMutex为等待条件,QWaitCondition允许一个线程在一定条件下唤醒其他线程
   const int DataSize = 100000;  
   const int BufferSize = 4096;  
   char buffer[BufferSize];  
   QWaitCondition bufferIsNotFull;  
   QWaitCondition bufferIsNotEmpty;  
   QMutex mutex;  
   int usedSpace = 0;

在缓存之外,我们声明了两个QWaitCondition、一个QMutex和一个存储了在缓存中有多少个“使用的”字节的变量。
   void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
if (usedSpace == BufferSize)
bufferIsNotFull.wait(&mutex);
buffer[i % BufferSize] = "MING"[uint(rand()) % 4];
++usedSpace;
bufferIsNotEmpty.wakeAll();
mutex.unlock();
}
}
在生产者中,我们从检查缓存是否充满开始。如果是充满的,我们等待“缓存不是充满的”条件。当这个条件满足时,我们向缓存写入一个字节,增加usedSpace,并且在唤醒任何等待这个“缓存不是空白的”条件变为真的线程
for循环中的所有语句需要使用互斥量加以保护,以保护其操作的原子性。
        bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX ); 
这个函数做下说明,该函数将互斥量解锁并在此等待,它有两个参数,第一个参数为一个锁定的互斥量,第二个参数为等待时间。如果作为第一个参数的互斥量在调用是不是锁定的或出现递归锁定的情况,wait()函数将立即返回。
调用wait()操作的线程使得作为参数的互斥量在调用前变为锁定状态,然后自身被阻塞变成为等待状态直到满足以下条件:
其他线程调用了wakeOne()或者wakeAll()函数,这种情况下将返回"true"值。
第二个参数time超时(以毫秒记时),该参数默认情况是ULONG_MAX,表示永不超时,这种情况下将返回"false"值。
wait()函数返回前会将互斥量参数重新设置为锁定状态,从而保证从锁定状态到等待状态的原则性转换。
void Consumer::run()
{
forever {
mutex.lock();
if (usedSpace == 0)
bufferIsNotEmpty.wait(&mutex);
cerr << buffer[i % BufferSize];
--usedSpace;
bufferIsNotFull.wakeAll();
mutex.unlock();
}
cerr << endl;
}
消费者做的和生产者正好相反,他等待“缓存不是空白的”条件并唤醒任何等待“缓存不是充满的”的条件的线程
main()函数与上面的基本相同,这个不再多说。
在QThread类的静态函数currentThread(),可以返回当前线程线程ID。在X11环境下,这个ID是一个unsigned long类型的值。
自己总结:
原子类型  QAtomicInt QAtomicPointer 
 
2.多线程通信
  a . 全局变量 即共享内存
  b.  管道
  c.  信号槽
3.多进程通信
  信号槽  socket 文件映射

Qt 多线程同步与通信的更多相关文章

  1. QT多线程同步之QWaitcondition

    使用到多线程,无可避免的会遇到同步问题,qt提供几种同步线程的方法,在这里讲一下QWaitcondition的简单使用. 一.QWaitcondition,是通过一个线程达到某种条件来唤起另一个线程来 ...

  2. Qt多线程同步总结

    1.QMutex   QMutex mutex;   void func() { mutex.lock(); ........ mutex.unlock(); }   2.QMutex联手QMutex ...

  3. C#多线程的同步与通信

    C#中使用lock和Monitor控制多线程对资源的使用,最常见的生产者和消费者问题就是多线程同步和通信的经典例子.了解C#多线程的同步与通信. 一.关于lock和Monitor lock可以把一段代 ...

  4. C# 多线程同步和线程通信

    多线程通信 1. 当线程之间有先后的依赖关系时,属于线程之间的通信问题.也就是后一个线程要等待别的一个或多个线程全部完成,才能开始下一步的工作.可以使用: WaitHandle Class WaitH ...

  5. Win32多线程编程(3) — 线程同步与通信

      一.线程间数据通信 系统从进程的地址空间中分配内存给线程栈使用.新线程与创建它的线程在相同的进程上下文中运行.因此,新线程可以访问进程内核对象的所有句柄.进程中的所有内存以及同一个进程中其他所有线 ...

  6. Python 多线程、多进程 (二)之 多线程、同步、通信

    Python 多线程.多进程 (一)之 源码执行流程.GIL Python 多线程.多进程 (二)之 多线程.同步.通信 Python 多线程.多进程 (三)之 线程进程对比.多线程 一.python ...

  7. Java精选笔记_多线程(创建、生命周期及状态转换、调度、同步、通信)

    线程概述 在应用程序中,不同的程序块是可以同时运行的,这种多个程序块同时运行的现象被称作并发执行. 多线程可以使程序在同一时间内完成很多操作. 多线程就是指一个应用程序中有多条并发执行的线索,每条线索 ...

  8. Qt QThread 线程创建,线程同步,线程通信 实例

    1.  继承QThread, 实现run()方法, 即可创建线程. 2. 实例1 代码 myThread.h #ifndef MYTHREAD_H #define MYTHREAD_H #includ ...

  9. java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)

    本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ...

随机推荐

  1. zedboard学习(1)OLED驱动显示图像

    1. 干点啥?驱动一下上面的屏吧 2. 找个代码研究一下,cat命令用于读取文件(普通文件或设备文件)的内容并进行输出.据说板子已经做好OLED的驱动了,驱动映射为/dev/zed_oled,所以直接 ...

  2. Qt 利用XML文档,写一个程序集合 三

    接上一篇https://www.cnblogs.com/DreamDog/p/9214052.html 滚动区域实现, 滚动区域可以三成分层 第一层,显示内容 中间层,滚动层 第三层,爸爸层 把我们要 ...

  3. sql 命令使用简单记录

    半个月前就想记下用过的SQL命令的!!!     主题: 按时间查询: https://blog.csdn.net/hejpyes/article/details/41863349   左关联: se ...

  4. CentOS 6.8 安装JDK8

    JDK安装 1.查看环境是否有默认jdk,输入命令: rpm -qa | grep jdk 如果有默认jdk,可以使用 yum remove 删除 2.进入系统根目录,创建developer文件夹 3 ...

  5. 【转】lvs、nginx、haproxy转发模式优缺点总结

    原文地址: https://yq.aliyun.com/ziliao/78374 一.LVS转发模式 LVS是章文嵩博士写的一个工作于四层的高可能性软件.不像后两者支持七层转发,不过也正因为其简单,所 ...

  6. win7下配置spark

    1.安装jdk(配置JAVA_HOME,CLASSPATH,path) 2.安装scala(配置SCALA_HOME,path) 3.安装spark Spark的安装非常简单,直接去Download ...

  7. Redis5.0:在这些场景使用,高效率还低成本!

    很多大型电商网站.视频直播和游戏应用等,存在大规模数据访问,对数据查询效率要求高,且数据结构简单,不涉及太多关联查询. 这种场景使用Redis,在速度上对传统磁盘数据库有很大优势,能够有效减少数据库磁 ...

  8. 在GPT格式的硬盘上,使用EFI启动的方式,安装Win7 64位系统

    Win7 sp1 原装系统,用UltraISO(软碟通) 把U 盘制成Win7 安装的启动U盘 将bootmgfw.efi和shell.efi 加到已制好启动U盘的根目录,并在efi/boot/路径下 ...

  9. LeetCode 36. Valid Sudoku (C++)

    题目: Determine if a 9x9 Sudoku board is valid. Only the filled cells need to be validated according t ...

  10. 第六次作业psp

    psp 进度条 代码累积折线图 博文累积折线图 psp饼状图