药品网站如何建设,有没有专门学做婴儿衣服的网站,从事软件开发,江门市网站建设 熊掌号Qt5开发及实例V2.0-第十二章-Qt多线程 第12章 Qt 5多线程12.1 多线程及简单实例12.2 多线程控制12.2.1 互斥量12.2.2 信号量12.2.3 线程等待与唤醒 12.3 多线程应用12.3.1 【实例】#xff1a;服务器编程12.3.2 【实例】#xff1a;客户端编程 本章相关例程源码下载1.Qt5开发… Qt5开发及实例V2.0-第十二章-Qt多线程 第12章 Qt 5多线程12.1 多线程及简单实例12.2 多线程控制12.2.1 互斥量12.2.2 信号量12.2.3 线程等待与唤醒 12.3 多线程应用12.3.1 【实例】服务器编程12.3.2 【实例】客户端编程 本章相关例程源码下载1.Qt5开发及实例_CH1201.rar 下载2.Qt5开发及实例_CH1202.rar 下载3.Qt5开发及实例_CH1203.rar 下载4.Qt5开发及实例_CH1204.rar 下载5.Qt5开发及实例_CH1205.rar 下载 第12章 Qt 5多线程
多线程具有以下几点优势。 1提高应用程序的响应速度。这对于开发图形界面的程序尤为重要当一个操作耗时很长时整个系统都会等待这个操作程序就不能响应键盘、鼠标、菜单等的操作而使用多线程技术可将耗时长的操作置于一个新的线程从而避免以上的问题。 2使多CPU系统更加有效。当线程数不大于CPU数目时操作系统可以调度不同的线程运行于不同的CPU上。 3改善程序结构。一个既长又复杂的进程可以考虑分为多个线程成为独立或半独立的运行部分这样有利于代码的理解和维护。
多线程程序有以下几个特点。 1多线程程序的行为无法预期当多次执行上述程序时每一次的运行结果都可能不同。 2多线程的执行顺序无法保证它与操作系统的调度策略和线程优先级等因素有关。 3多线程的切换可能发生在任何时刻、任何地点。 4多线程对代码的敏感度高因此对代码的细微修改都可能产生意想不到的结果。
12.1 多线程及简单实例
【例】难度一般CH1201如图12.1所示单击“开始”按钮将启动数个工作线程工作线程数目由MAXSIZE宏决定各个线程循环打印数字0~9直到单击“停止”按钮终止所有线程为止。 实现步骤如下。 1在头文件“threaddlg.h”中声明用于界面显示所需的控件其具体代码如下
#include QDialog
#include QPushButton
class ThreadDlg : public QDialog
{Q_OBJECT
public:ThreadDlg(QWidget *parent 0);~ThreadDlg();
private:QPushButton *startBtn;QPushButton *stopBtn;QPushButton *quitBtn;
};2在源文件“threaddlg.cpp”的构造函数中完成各个控件的初始化工作其具体代码如下
#include threaddlg.h
#include QHBoxLayout
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);
}3此时运行程序界面显示如图12.1所示。 以上完成了界面的设计下面的内容是具体的功能实现。 1在头文件“workthread.h”中工作线程WorkThread类继承自QThread类。重新实现run()函数。其具体代码如下
#include QThread
class WorkThread : public QThread
{Q_OBJECT
public:WorkThread();
protected:void run();
};2在源文件“workthread.cpp”中添加具体实现代码如下
#include workthread.h
#include QtDebug
WorkThread::WorkThread()
{
}run()函数实际上是一个死循环它不停地打印数字0~9。为了显示效果明显程序将每一个数字重复打印8次。
void WorkThread::run()
{while(true){for(int n0;n10;n)qDebug()nnnnnnnn;}
}3在头文件“threaddlg.h”中添加以下内容
#include workthread.h
#define MAXSIZE 1 //MAXSIZE宏定义了线程的数目
public slots:void slotStart(); //槽函数用于启动线程void slotStop(); //槽函数用于终止线程
private:
WorkThread *workThread[MAXSIZE]; //(a)4在源文件“threaddlg.cpp”中添加以下内容。 其中在构造函数中添加如下代码
connect(startBtn,SIGNAL(clicked()),this,SLOT(slotStart()));
connect(stopBtn,SIGNAL(clicked()),this,SLOT(slotStop()));
connect(quitBtn,SIGNAL(clicked()),this,SLOT(close()));槽函数slotStart()当用户单击“开始”按钮时此函数将被调用。这里使用两个循环目的是为了使新建的线程尽可能同时开始执行其具体实现代码如下
void ThreadDlg::slotStart()
{for(int i0;iMAXSIZE;i){workThread[i]new WorkThread(); //(a)}for(int i0;iMAXSIZE;i){workThread[i]-start(); //(b)}startBtn-setEnabled(false);stopBtn-setEnabled(true);
}
槽函数slotStop()当用户单击“停止”按钮时此函数将被调用。其具体实现代码如下
void ThreadDlg::slotStop()
{for(int i0;iMAXSIZE;i){workThread[i]-terminate();workThread[i]-wait();}startBtn-setEnabled(true);stopBtn-setEnabled(false);
}
5运行结果如图12.2所示。
12.2 多线程控制
实现线程的互斥与同步常使用的类有QMutex、QMutexLocker、QReadWriteLocker、QReadLocker、QWriteLocker、QSemaphore和QWaitCondition。 下面举一个例子来说明问题
class Key
{
public:Key() {key0;}int creatKey() {key; return key;}int value()const {return key;}
private:int key;
};虽然类Key产生主键的函数creatKey()只有一条语句执行修改成员变量key的值但是C的“”操作符并不是原子操作通常编译后它将被展开成为以下三条机器命令 将变量值载入寄存器。 将寄存器中的值加1。 将寄存器中的值写回主存。 假设当前的key值为0如果线程1和线程2同时将0值载入寄存器执行加1操作并将加1后的值写回主存则结果是两个线程的执行结果将互相覆盖实际上仅进行了一次加1操作此时的key值为1。
12.2.1 互斥量
1QMutex类 QMutex类是对互斥量的处理。它被用来保护一段临界区代码即每次只允许一个线程访问这段代码。 QMutex类的lock()函数用于锁住互斥量。如果互斥量处于解锁状态则当前线程就会立即抓住并锁定它否则当前线程就会被阻塞直到持有这个互斥量的线程对它解锁。线程调用lock()函数后就会持有这个互斥量直到调用unlock()操作为止。 QMutex类还提供了一个tryLock()函数。如果互斥量已被锁定则立即返回。 例如
class Key
{
public:Key() {key0;}int creatKey() { mutex.lock(); key; return key; mutex. unlock();}int value()const { mutex.lock(); return key; mutex.unlock();}
private:int key;QMutex mutex;
};2QMutexLocker类 Qt提供的QMutexLocker类可以简化互斥量的处理它在构造函数中接收一个QMutex对象作为参数并将其锁定在析构函数中解锁这个互斥量这样就解决了以上问题。 例如
class Key
{
public:Key() {key0;}int creatKey() { QmutexLocker locker(mutex); key; return key; }int value()const { QmutexLocker locker(mutex); return key; }
private:int key;QMutex mutex;
};12.2.2 信号量
生产者/消费者实例中对同步的需求有两处 1如果生产者过快地生产数据将会覆盖消费者还没有读取的数据。 2如果消费者过快地读取数据将越过生产者并且读取到一些过期数据。 针对以上问题可以有两种解决方法 1首先使生产者填满整个缓冲区然后等待消费者读取整个缓冲区这是一种比较笨拙的方法。 2使生产者和消费者线程同时分别操作缓冲区的不同部分这是一种比较高效的方法。
【例】难度一般CH1202基于控制台程序实现。 1源文件“main.cpp”中添加的具体实现代码如下
#include QCoreApplication
#include QSemaphore
#include QThread
#include stdio.h
const int DataSize1000;
const int BufferSize80;
int buffer[BufferSize]; //(a)
QSemaphore freeBytes(BufferSize); //(b)
QSemaphore usedBytes(0); //(c)2Producer类继承自QThread类作为生产者类其声明如下
class Producer : public QThread
{
public:Producer();void run();
};Producer构造函数中没有实现任何内容
Producer::Producer()
{
}Producer::run()函数的具体实现代码如下
void Producer::run()
{for(int i0;iDataSize;i){freeBytes.acquire(); //(a)buffer[i%BufferSize](i%BufferSize); //(b)usedBytes.release(); //(c)}
}3Consumer类继承自QThread类作为消费者类其声明如下
class Consumer : public QThread
{
public:Consumer();void run();
};Consumer构造函数中没有实现任何内容
Consumer::Consumer()
{
}Consumer::run()函数的具体实现代码如下
void Consumer::run()
{for(int i0;iDataSize;i){usedBytes.acquire(); //(a)fprintf(stderr,%d,buffer[i%BufferSize]); //(b)if(i%160i!0)fprintf(stderr,\n);freeBytes.release(); //(c)}fprintf(stderr,\n);
}4main()函数的具体内容如下
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最终运行结果如图12.3所示。
12.2.3 线程等待与唤醒
【例】难度一般CH1203使用QWaitCondition类解决生产者和消费者问题。 源文件“main.cpp”的具体内容如下
#include QCoreApplication
#include QWaitCondition
#include QMutex
#include QThread
#include stdio.h
const int DataSize1000;
const int BufferSize80;
int buffer[BufferSize];
QWaitCondition bufferEmpty;
QWaitCondition bufferFull;
QMutex mutex; //(a)
int numUsedBytes0; //(b)
int rIndex0; //(c)生产者线程Producer类继承自QThread类其声明如下
class Producer : public QThread
{
public:Producer();void run();
};Producer构造函数无须实现
Producer::Producer()
{
}Producer::run()函数的具体内容如下
void Producer::run()
{for(int i0;iDataSize;i) //(a){mutex.lock();if(numUsedBytesBufferSize) //(b)bufferEmpty.wait(mutex); //(c)buffer[i%BufferSize]numUsedBytes; //(d)numUsedBytes; //增加numUsedBytes变量bufferFull.wakeAll(); //(e)mutex.unlock();}
}其中 (a) for(int i0;iDataSize;i) { mutex.lock(); … mutex.unlock();}for循环中的所有语句都需要使用互斥量加以保护以保证其操作的原子性。 (b) if(numUsedBytesBufferSize)首先检查缓冲区是否已经填满。 © bufferEmpty.wait(mutex)如果缓冲区已经填满则等待“缓冲区有空位”bufferEmpty变量条件成立。wait()函数将互斥量解锁并在此等待其原型如下
bool QWaitCondition::wait
(QMutex * mutex,unsigned long time ULONG_MAX
)(d) buffer[i%BufferSize]numUsedBytes如果缓冲区未被填满则向缓冲区中写入一个整数值。 (e) bufferFull.wakeAll()最后唤醒等待“缓冲区有可用数据”bufferEmpty变量条件为“真”的线程。
消费者线程Consumer类继承自QThread类其声明如下
class Consumer : public QThread
{
public:Consumer();void run();
};Consumer构造函数中无须实现内容
Consumer::Consumer()
{
}Consumer::run()函数的具体内容如下
void Consumer::run()
{forever{mutex.lock();if(numUsedBytes0)bufferFull.wait(mutex); //(a)printf(%ul::[%d]%d\n,currentThreadId(),rIndex,buffer[rIndex]);//(b)rIndex(rIndex)%BufferSize; //将rIndex变量循环加1--numUsedBytes; //(c)bufferEmpty.wakeAll(); //(d)mutex.unlock();}printf(\n);
}main()函数的具体内容如下
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();
}程序最终的运行结果如图12.4所示。
12.3 多线程应用
12.3.1 【实例】服务器编程
【例】难度中等CH1204服务器编程。 首先建立服务器端工程“TimeServer.pro”。文件代码如下。 1在头文件“dialog.h”中定义服务器端界面类Dialog继承自QDialog类其具体代码如下
#include QDialog
#include QLabel
#include QPushButton
class Dialog : public QDialog
{Q_OBJECT
public:Dialog(QWidget *parent 0);~Dialog();
private:QLabel *Label1; //此标签用于显示监听端口QLabel *Label2; //此标签用于显示请求次数QPushButton *quitBtn; //退出按钮
};2在源文件“dialog.cpp”中Dialog类的构造函数完成了初始化界面其具体代码如下
#include dialog.h
#include QHBoxLayout
#include QVBoxLayout
Dialog::Dialog(QWidget *parent): QDialog(parent)
{setWindowTitle(tr(多线程时间服务器));Label1 new QLabel(tr(服务器端口));Label2 new QLabel;quitBtn new QPushButton(tr(退出));QHBoxLayout *BtnLayout new QHBoxLayout;BtnLayout-addStretch(1);BtnLayout-addWidget(quitBtn);BtnLayout-addStretch(1);QVBoxLayout *mainLayout new QVBoxLayout(this);mainLayout-addWidget(Label1);mainLayout-addWidget(Label2);mainLayout-addLayout(BtnLayout);connect(quitBtn,SIGNAL(clicked()),this,SLOT(close()));
}3此时运行服务器端工程“TimeServer.pro”界面显示如图12.5所示。
4在服务器端工程“TimeServer.pro”中添加C Class文件“timethread.h”及“timethread.cpp”。在头文件“timethread.h”中工作线程TimeThread类继承自QThread类实现TCP套接字其具体代码如下
#include QThread
#include QtNetwork
#include QTcpSocket
class TimeThread : public QThread
{Q_OBJECT
public:TimeThread(int socketDescriptor,QObject *parent0);void run(); //重写此虚函数
signals:void error(QTcpSocket::SocketError socketError); //出错信号
private:int socketDescriptor; //套接字描述符
};5在源文件“timethread.cpp”中TimeThread类的构造函数只是初始化了套接字描述符其具体代码如下
#include timethread.h
#include QDateTime
#include QByteArray
#include QDataStream
TimeThread::TimeThread(int socketDescriptor,QObject *parent):QThread(parent),socketDescriptor(socketDescriptor)
{
}TimeThread::run()函数是工作线程TimeThread的实质所在当在TimeServer:: incomingConnection()函数中调用了thread-start()函数后此虚函数开始执行其具体代码如下
void TimeThread::run()
{QTcpSocket tcpSocket; //创建一个QTcpSocket类if(!tcpSocket.setSocketDescriptor(socketDescriptor)) //(a){emit error(tcpSocket.error()); //(b)return;}QByteArray block;QDataStream out(block,QIODevice::WriteOnly);out.setVersion(QDataStream::Qt_5_8);uint time2u QDateTime::currentDateTime().toTime_t();//(c)outtime2u;tcpSocket.write(block); //将获得的当前时间传回客户端tcpSocket.disconnectFromHost(); //断开连接tcpSocket.waitForDisconnected(); //等待返回
}6在服务器端工程“TimeServer.pro”中添加C Class文件“timeserver.h”及“timeserver.cpp”。在头文件“timeserver.h”中实现了一个TCP服务器端类TimeServer继承自QTcpServer类其具体代码如下
#include QTcpServer
class Dialog; //服务器端的声明
class TimeServer : public QTcpServer
{Q_OBJECT
public:TimeServer(QObject *parent0);
protected:void incomingConnection(int socketDescriptor); //(a)
private:Dialog *dlg; //(b)
};7在源文件“timeserver.cpp”中构造函数只是用传入的父类指针parent初始化私有变量dlg其具体代码如下
#include timeserver.h
#include timethread.h
#include dialog.h
TimeServer::TimeServer(QObject *parent):QTcpServer(parent)
{dlg (Dialog *)parent;
}重写的虚函数incomingConnection()的具体代码如下
void TimeServer::incomingConnection(int socketDescriptor)
{TimeThread *thread new TimeThread(socketDescriptor,0); //(a)connect(thread,SIGNAL(finished()),dlg,SLOT(slotShow())); //(b)connect(thread,SIGNAL(finished()),thread,SLOT(deleteLater()),Qt::DirectConnection); //(c)thread-start(); //(d)
}8在服务器端界面的头文件“dialog.h”中添加的具体代码如下
class TimeServer;
public slots:void slotShow(); //此槽函数用于界面上显示的请求次数
private:TimeServer *timeServer; //TCP服务器端timeServerint count; //请求次数计数器count9在源文件“dialog.cpp”中添加的头文件如下
#include QMessageBox
#include timeserver.h其中在Dialog类的构造函数中添加的内容用于启动服务器端的网络监听其具体实现如下
count0;
timeServer new TimeServer(this);
if(!timeServer-listen())
{QMessageBox::critical(this,tr(多线程时间服务器),tr(无法启动服务器%1.).arg(timeServer-errorString()));close();return;
}Label1-setText(tr(“服务器端口%1.”).arg(timeServer-serverPort())); 在源文件“dialog.cpp”中槽函数slotShow()的具体内容如下
void Dialog::slotShow()
{Label2-setText(tr(第%1次请求完毕。).arg(count));
}10在服务器端工程文件“TimeServer.pro”中添加如下代码
QT network11最后运行服务器端工程“TimeServer.pro”结果如图12.6所示。
12.3.2 【实例】客户端编程
【例】难度中等CH1205客户端编程。界面效果如图12.7所示。
操作步骤如下。 1建立客户端工程“TimeClient.pro”。在头文件“timeclient.h”中定义了客户端界面类TimeClient继承自QDialog类其具体代码。 2在源文件“timeclient.cpp”中TimeClient类的构造函数完成了初始化界面其具体代码。 在源文件“timeclient.cpp”中enableGetBtn()函数的具体代码如下
void TimeClient::enableGetBtn()
{getBtn-setEnabled(!serverNameLineEdit-text().isEmpty()!portLineEdit-text().isEmpty());
}在源文件“timeclient.cpp”中getTime()函数的具体代码如下
void TimeClient::getTime()
{getBtn-setEnabled(false);time2u 0;tcpSocket-abort();tcpSocket-connectToHost(serverNameLineEdit-text(),portLineEdit-text().toInt());
}
在源文件“timeclient.cpp”中readTime ()函数的具体代码如下
void TimeClient::readTime()
{QDataStream in(tcpSocket);in.setVersion(QDataStream::Qt_5_8);if(time2u0){if(tcpSocket-bytesAvailable()(int)sizeof(uint))return;intime2u;}dateTimeEdit-setDateTime(QDateTime::fromTime_t(time2u));getBtn-setEnabled(true);
}在源文件“timeclient.cpp”中showError()函数的具体代码如下
void TimeClient::showError(QAbstractSocket::SocketError socketError)
{switch (socketError){case QAbstractSocket::RemoteHostClosedError:break;case QAbstractSocket::HostNotFoundError:QMessageBox::information(this, tr(时间服务客户端),tr(主机不可达));break;case QAbstractSocket::ConnectionRefusedError:QMessageBox::information(this, tr(时间服务客户端),tr(连接被拒绝));break;default:QMessageBox::information(this, tr(时间服务客户端),tr(产生如下错误: %1.).arg(tcpSocket-errorString()));}getBtn-setEnabled(true);
}3在客户端工程文件“TimeClient.pro”中添加如下代码
QT network4运行客户端工程“TimeClient.pro”显示界面如图12.7所示。 最后同时运行服务器和客户端程序单击客户端“获取时间”按钮从服务器上获得当前的系统时间如图12.8所示。 本章相关例程源码下载
1.Qt5开发及实例_CH1201.rar 下载
Qt5开发及实例_CH1201.rar
2.Qt5开发及实例_CH1202.rar 下载
Qt5开发及实例_CH1202.rar
3.Qt5开发及实例_CH1203.rar 下载
Qt5开发及实例_CH1203.rar
4.Qt5开发及实例_CH1204.rar 下载
Qt5开发及实例_CH1204.rar
5.Qt5开发及实例_CH1205.rar 下载
Qt5开发及实例_CH1205.rar