QT之多线程
1. 线程同时进行
QT提供了QThread来定义一个线程,我们通过定义类thread来重新实现它。
class Thread:public QThread
{
Q_OBJECT
public:
Thread();
void setMessage(const QString &Message);
void stop();
protected:
void run();
private:
QString MessageStr;
volatile bool stopped;
};
线程通过判断stopped变量来选择打印信息,重新实现了run函数和stop函数,用于线程运行和终止。
void Thread::run(){
while(!stopped)
std::cerr<<qPrintable(MessageStr);
stopped=false;
std::cerr<<std::endl;
}
void Thread::stop(){
stopped=true;
}
接下来制作一个简单的对话框,实现两个按钮,可以调用线程。这两个线程可以同时运行,互不相关,有各自的stopped判别变量。
public:
threadDialog(QWidget *parent = 0);
private slots:
void startOrStopThreadA();
void startOrStopThreadB();
protected:
void closeEvent(QCloseEvent *closeEvent);
private:
Thread threadA;
Thread threadB;
QPushButton *threadButtonA;
QPushButton *threadButtonB;
QPushButton *quitButton;
这是类的定义,包括定义了三个按钮,分别用于启动线程A和B以及停止两个线程的按钮。
当同时按下A 和B线程的按钮,会交替输出A和B的信息。
2. 互斥锁
QT通过QMutex来建立保护多线程共同访问区,通过lock和unlock分别来锁住和解锁一段变量或者代码。当引用lock时,其后边的代码都被当前进程占用,其他进程不能访问,直到调用unlock后,其他程序才可以调用。上面代码可以修改为:
void Thread::run(){
forever{
mutex.lock();
if(stopped){
stopped=false;
mutex.unlock();
break;
}
mutex.unlock();
std::cerr<<qPrintable(MessageStr);
}
std::cerr<<std::endl;
}
void Thread::stop(){
mutex.lock();
stopped=true;
mutex.unlock();
}
3. 信号量
信号量可以用于当两个线程之间传递大量数据时,两个线程通过信号量来确定缓冲区大小,以及自己是否可以进行读写操作。读写缓冲区是典型的生产者和消费者类型,生产者向缓冲区写数据,消费者读取数据。现在定义两个信号量,一个检测缓冲区可以写的空间大小,一个记录可以读的空间大小,这类似于FIFO。
QSemaphore freeSpace(BufferSize);
QSemaphore usedSpace(0);
信号量通过acquire来获得可用区域,而通过release来释放已经完成的区域。
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
freeSpace.acquire();
buffer[i % BufferSize] = "ACGT"[uint(std::rand()) % 4];
usedSpace.release();
}
}
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
usedSpace.acquire();
std::cerr << buffer[i % BufferSize];
freeSpace.release();
}
std::cerr << std::endl;
}
生产者每次写入一个数据,usedSpace就增加一个可读数据。而消费者每次读一个数据,freeSpace就增加一个自由空间。
4. 等待条件
除了使用信号量,还可以通过QWaitCondition定义等待变量,用于线程开启和等待。
QWaitCondition bufferIsNotFull;
QWaitCondition bufferIsNotEmpty;
QMutex mutex;
int usedSpace = 0;
以上bufferIsNotFull和bufferIsNotEmpty可以来唤起和阻塞进程。而usedSpace则用于计算可用空间。
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
while (usedSpace == BufferSize)
bufferIsNotFull.wait(&mutex);
buffer[i % BufferSize] = "ACGT"[uint(std::rand()) % 4];
++usedSpace;
bufferIsNotEmpty.wakeAll();
mutex.unlock();
}
}
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
while (usedSpace == 0)
bufferIsNotEmpty.wait(&mutex);
std::cerr << buffer[i % BufferSize];
--usedSpace;
bufferIsNotFull.wakeAll();
mutex.unlock();
}
std::cerr << std::endl;
}
生产者如果检测到空间已经写满,就会被bufferIsNotFull阻塞等待,直到消费者通过消费空间,使得usedSpace不满为止。然后生产者写入一个数据,可用空间就增加一个。消费者正好与生产者相反。