QT学习:多线程控制

实现线程的互斥与同步常使用的类有QMutex、QMutexLocker、QReadWriteLocker、QReadLocker、
QWriteLocker、QSemaphore和QWaitCondition。
下面举一个例子加以说明:

class Key 
{
public: 
Key() {key=0;} 
int creatKey() {++key; return key;} 
int value()const {return key;} 
private: 
int key; 
}; 

在多线程环境下,这个类是不安全的,因为存在多个线程同时修改私有成员key,其结果是不可预知
的。
虽然Key类产生主键的函数creatKey()只有一条语句执行修改成员变量key的值,但是C++的“++”操作符
并不是原子操作,通常编译后,它将被展开成为以下三条机器命令:
(1)将变量值载入寄存器。
(2)将寄存器中的值加1。
(3)将寄存器中的值写回主存。

一、互斥量

1、QMutex类

QMutex类的lock()函数用于锁住互斥量。如果互斥量处于解锁状态,则当前线程就会立即抓住并锁定它,否则当前线程就会被阻塞,直到持有这个互斥量的线程对它解锁。线程调用lock()函数后就会持有这个互斥量,直到调用unlock()操作为止。 QMutex类还提供了一个tryLock()函数。如果互斥量已被锁定,则立即返回。 例如:

class Key 
{
public: 
Key() {key=0;} 
int creatKey() { mutex.lock(); ++key; return key; mutex. unlock();} 
int value()const { mutex.lock(); return key; mutex.unlock();} 
private: 
int key; 
QMutex mutex; 
};

2、QMutexLocker类

Qt提供的QMutexLocker类可以简化互斥量的处理,它在构造函数中接收一个QMutex对象作为参数并将其锁定,在析构函数中解锁这个互斥量,这样就解决了以上问题。
例如:

class Key 
{
public: 
Key() {key=0;} 
int creatKey() { QmutexLocker locker(&mutex); ++key; return key; } 
int value()const { QmutexLocker locker(&mutex); return key; } 
private: 
int key; 
QMutex mutex; 
}; 

locker()函数作为局部变量会在函数退出时结束其作用域,从而自动对互斥量mutex解锁。

二、信号量

信号量可以理解为对互斥量功能的扩展,互斥量只能锁定一次而信号量可以获取多次,它可以用来保护一定数量的同种资源。信号量的典型用例是控制生产者/消费者之间共享的环形缓冲区。 生产者/消费者实例中对同步的需求有两处:
(1)如果生产者过快地生产数据,将会覆盖消费者还没有读取的数据。
(2)如果消费者过快地读取数据,将越过生产者并且读取到一些过期数据。
针对以上问题,有两种解决方法:
(1)首先使生产者填满整个缓冲区,然后等待消费者读取整个缓冲区,这是一种比较笨拙的方 法。
(2)使生产者和消费者线程同时分别操作缓冲区的不同部分,这是一种比较高效的方法。
具体使用方法见如下代码:
(1)在源文件“main.cpp”中添加的具体实现代码如下:

#include <QCoreApplication> 
#include <QSemaphore> 
#include <QThread> 
#include <stdio.h> 
const int DataSize=1000; 
const int BufferSize=80; 
int buffer[BufferSize]; //首先,生产者向buffer中写入数据,直到它到达终点,然后从起点重新开始覆盖已经存在的数据。消费者读取前者生产的数据,在此处每个int字长都被看成一个资源,实际应用中常会在更大的单位上进行操作,从而减少使用信号量带来的开销。 
 
QSemaphore freeBytes(BufferSize); // freeBytes信号量控制可被生产者填充的缓冲区部分,被初始化为BufferSize(80),表示程序一开始有BufferSize个缓冲区单元可被填充。 
QSemaphore usedBytes(0); // usedBytes信号量控制可被消费者读取的缓冲区部分,被初始化为0, 表示程序一开始时缓冲区中没有数据可供读取。 

(2)Producer类继承自QThread类,作为生产者类,其声明如下:

class Producer : public QThread 
{
public: 
Producer(); 
void run(); 
}; 

Producer构造函数中没有实现任何内容:

Producer::Producer() 
{
} 

Producer::run()函数的具体实现代码如下:

void Producer::run() 
{ 
for(int i=0;i<DataSize;i++) 
{ 
freeBytes.acquire(); //生产者线程首先获取一个空闲单元,如果此时缓冲区被消费者尚未读取的数据填满,对此函数的调用就会阻塞,直到消费者读取了这些数据为止。 
 
buffer[i%BufferSize]=(i%BufferSize); //一旦生产者获取了某个空闲单元,就使用当前的缓冲区单元序号填写这个缓冲区单元。 
usedBytes.release(); //调用该函数将可用资源加1,表示消费者此时可以读取这个刚刚填写的单元了。 
} 
} 

release(n)函数用于释放n个资源。
(3)Consumer类继承自QThread类,作为消费者类,其声明如下:

class Consumer : public QThread 
{
public: 
Consumer(); 
void run(); 
}; 

Consumer构造函数中没有实现任何内容:

Consumer::Consumer() 
{
} 

Consumer::run()函数的具体实现代码如下:

void Consumer::run() 
{ 
for(int i=0;i<DataSize;i++) 
{ 
  usedBytes.acquire(); //消费者线程首先获取一个可被读取的单元,如果缓冲区中没有包含任何可以读取的数据,对此函数的调用就会阻塞,直到生产者生产了一些数据为止。
  fprintf(stderr,"%d",buffer[i%BufferSize]); //一旦消费者获取了这个单元,会将这个单元的内容打印出来。 
  if(i%16==0&&i!=0) 
  fprintf(stderr,"\n"); 
  freeBytes.release(); //调用该函数使得这个单元变为空闲,以备生产者下次填充。 
}
  fprintf(stderr,"\n"); 
} 

(4)main()函数的具体内容如下:

int main(int argc, char *argv[]) 
{ 
QCoreApplication a(argc, argv); 
Producer producer; 
Consumer consumer; 
/* 启动生产者和消费者线程 */ 
producer.start(); 
consumer.start(); 
/* 等待生产者和消费者各自执行完毕后自动退出 */ 
producer.wait(); 
consumer.wait(); 
return a.exec(); 
} 

(5)最终运行结果如下图所示:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值