通常情况下,应用程序都是在一个线程中执行操作。但是,当调用一个耗时操 作(例如,大批量I/O或大量矩阵变换等CPU密集操作)时,用户界面常常会冻结。而使用多线程可解决这一问题。
多线程具有以下几点优势。
(1) 提高应用程序的响应速度。这对于开发图形界面的程序尤为重要,当一个操作耗时很长时,整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等的操作,而使用多线程技术可将耗时长的操作置于一个新的线程,从而避免以上 的问题。
(2) 使多CPU系统更加有效。当线程数不大于CPU数目时,操作系统可以调度不同的线程运行于不同的CPU上。
(3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于代码的理解和维护。
多线程程序有以下几个特点。
(1) 多线程程序的行为无法预期,当多次执行上述程序时,每一次的运行结果都可能不同。
(2) 多线程的执行顺序无法保证,它与操作系统的调度策略和线程优先级等因素有关。
(3) 多线程的切换可能发生在任何时刻、任何地点。
(4) 多线程对代码的敏感度高,因此对代码的细微修改都可能产生意想不到的结果。
ThreadDlg.cpp
#include "ThreadDlg.h"
#include
ThreadDlg::ThreadDlg(QWidget *parent)
:QDialog(parent)
{
setWindowTitle (tr ( "线程"));
startBtn = new QPushButton(tr("开始"));
stopBtn = new QPushButton (tr("停止"));
quitBtn = new QPushButton (tr ( "退出"));
QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->addWidget(startBtn);
mainLayout->addWidget(stopBtn);
mainLayout->addWidget(quitBtn);
connect(startBtn,SIGNAL(clicked()),this,SLOT(slotstart ()));
connect(stopBtn,SIGNAL(clicked()),this, SLOT(slotstop()));
connect(quitBtn, SIGNAL(clicked()),this,SLOT(close ()));
}
void ThreadDlg::slotstart()
{
for (int i=0; i<MAXSIZE; i++)
{
workThread[i]= new WorkThread();
}
for (int i=0;i<MAXSIZE;i++)
{
workThread[i]->start();
}
startBtn->setEnabled(false);
stopBtn->setEnabled(true);
}
void ThreadDlg::slotstop()
{
for (int i=0; i<MAXSIZE; i++)
{
workThread[i]->terminate();
workThread[i]->wait();
}
startBtn->setEnabled(true);
stopBtn->setEnabled(false);
}
ThreadDlg.h
#ifndef TEST_THREADDLG_H
#define TEST_THREADDLG_H
#include
#include
#include "workthread.h"
#define MAXSIZE 1
class ThreadDlg : public QDialog
{
Q_OBJECT
public:
ThreadDlg(QWidget *parent = 0);
~ThreadDlg() override = default;
protected slots:
void slotstart();
void slotstop();
private:
QPushButton *startBtn;
QPushButton *stopBtn;
QPushButton *quitBtn;
WorkThread *workThread[MAXSIZE];
};
#endif //TEST_THREADDLG_H
WorkThread.cpp
#include "WorkThread.h"
#include
WorkThread::WorkThread()
{
}
void WorkThread::run()
{
while (true)
{
for (int n=0;n<10;n++)
std::cout <
WorkThread.h
#ifndef TEST_WORKTHREAD_H
#define TEST_WORKTHREAD_H
#include
class WorkThread : public QThread
{
Q_OBJECT
public:
WorkThread();
protected:
void run ();
};
#endif //TEST_WORKTHREAD_H
main.cpp
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ThreadDlg tt;
tt.show();
return a.exec ();
}
程之间存在着互相制约的关系, 具体可分为互斥和同步这两种关系。
实现线程的互斥与同步常使用的类有 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;
};
这是实现生成从0开始递增并且不允许重复的值的Key类。
在多线程环境下,这个类是不安全的,因为存在多个线程同时修改私有成员 key,其结果是不可预知的。
虽然类Key产生主键的函数creatKey()只有一条语句执行修改成员变量key的 值,但是C++的"++”操作符并不是原子操作,通常编译后它将被展开成为以下三 条机器命令:
•将变量值载入寄存器;
•将寄存器中的值加1:
•将寄存器中的值写回主存。
假设当前的key值为0,如果线程1和线程2同时将0值载入寄存器,执行加 1操作并将加1后的值写回主存,则结果是两个线程的执行结果将互相覆盖,实际 上仅进行了一次加1操作,此时的key值为1。
为了保证类Key在多线程环境下正确执行,上面的三条机器指令必须串行执行 且不允许中途被打断(原子操作),即线程1在线程2 (或线程2在线程1)之前完 整执行上述三条机器指令。
实际上,私有变量key是一个临界资源(Critical Resource, CR)。临界资源一 次仅允许一个线程使用,它可以是一块内存、一个数据结构、一个文件或者任何其 他具有排他性使用的东西。在程序中通常竞争使用临界资源。这些必须互斥执行的 代码段称为“临界区(Critical Section, CS)”,临界区(代码段)实施对临界资源 的操作,为了阻止问题的产生,一次只能有一个线程进入临界区。通常有相关的机 制或方法在程序中加上“进入”或“离开”临界区等操作,如果一个线程已经进入 某个临界区,则另一个线程就绝不允许在此刻再进入同一个临界区。
互斥量可通过QMutex或者QMutexLocker类实现。
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;
};
上述的代码段中,虽然creatKey()函数中使用mutex进行了互斥操作,但是 unlock()操作却不得不在return之后,从而导致unlock()操作永远无法执行。同样, value。函数也存在这个问题。
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解锁。
在实际应用中,一些互斥量锁定和解锁逻辑通常比较复杂,并且容易出错,而使用 QMutexLocker类后,通常只需要这一条语句,从而大大简化了编程的复杂程度。
信号量可以理解为对互斥量功能的扩展,互斥量只能锁定一次而信号量可以获 取多次,它可以用来保护一定数量的同种资源。信号量的典型用例是控制生产者/ 消费者之间共享的环形缓冲区。
生产者/消费者实例中对同步的需求有两处:
(1) 如果生产者过快地生产数据,将会覆盖消费者还没有读取的数据。
(2) 如果消费者过快地读取数据,将越过生产者并且读取到一些过期数据。
针对以上问题,可以有两种解决方法:
(1) 首先使生产者填满整个缓冲区,然后等待消费者读取整个缓冲区,这是- 个比较笨拙的方法。
(2) 使生产者和消费者线程同时分别操作缓冲区的不同部分,这是一种比较高效 的方法。
main.cpp
#include
#include
#include
const int DataSize=1000;
const int BufferSize=80;
int buffer[BufferSize];
QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes(0);
class Producer : public QThread
{
public:
Producer()
{
}
void run()
{
for (int i = 0; i < DataSize; i++)
{
freeBytes.acquire();
buffer[i % BufferSize] = (i % BufferSize);
usedBytes.release();
}
}
};
class Consumer : public QThread
{
public:
Consumer()
{
}
void 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");
}
};
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 ();
}
对生产者和消费者问题的另一个解决方法是使用QWaitCondition类,允许线程 在一定条件下唤醒其他线程。
#include
#include
#include
#include
#include
const int DataSize=1000;
const int BufferSize=80;
int buffer[BufferSize];
QWaitCondition bufferEmpty;
QWaitCondition bufferFull;
QMutex mutex;
int numUsedBytes = 0;
int rIndex=0;
class Producer : public QThread
{
public:
Producer()
{
}
void run()
{
for (int i=0; i < DataSize; i++)
{
mutex.lock();
if(numUsedBytes==BufferSize)
bufferEmpty.wait(&mutex);
buffer[i%BufferSize]=numUsedBytes;
++numUsedBytes;
bufferFull.wakeAll();
mutex.unlock();
}
}
};
class Consumer : public QThread
{
public:
Consumer()
{
}
void run ()
{
forever
{
mutex.lock();
if(numUsedBytes==0)
bufferFull.wait(&mutex);
printf("%ul::[%d]=%d\n",currentThreadId(),rIndex,buffer[rIndex]);
rIndex=(++rIndex)%BufferSize;
--numUsedBytes;
bufferEmpty.wakeAll();
mutex.unlock();
}
printf("\n");
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Producer producer;
Consumer consumerA;
Consumer consumerB;
producer.start ();
consumerA.start ();
consumerB.start ();
producer.wait ();
consumerA.wait();
consumerB.wait ();
return a.exec ();
}
后续会在日常专栏中更新对应的应用代码,请注意日常专栏的更新。