转载https://my.oschina.net/laopiao/blog/88158
转载https://zhuanlan.zhihu.com/p/52612180
线程和进程
https://blog.csdn.net/zaishuiyifangxym/article/details/89415155
1.线程基础
1.1单核CPU
在早期的单核 CPU 时代还没有线程的概念,只有进程。操作系统作为一个大的“软件”,协调着各个硬件(如CPU、内存,硬盘、网卡灯)有序的工作着。在双核 CPU 诞生以前,我们用的 Windows 操作系统依然可以一边用 word 写文档一边听着音乐,作为整个系统唯一可以完成计算任务的 CPU 是如何保证两个进程“同时进行”的呢?时间片轮转调度!
注意这个关键字「轮转」。每个进程会被操作系统分配一个时间片,即每次被 CPU 选中来执行当前进程所用的时间。时间一到,无论进程是否运行结束,操作系统都会强制将 CPU 这个资源转到另一个进程去执行。为什么要这样做呢?因为只有一个单核 CPU,假如没有这种轮转调度机制,那它该去处理写文档的进程还是该去处理听音乐的进程?无论执行哪个进程,另一个进程肯定是不被执行,程序自然就是无运行的状态。如果 CPU 一会儿处理 word 进程一会儿处理听音乐的进程,起初看起来好像会觉得两个进程都很卡,但是 CPU 的执行速度已经快到让人们感觉不到这种切换的顿挫感,就真的好像两个进程在“并行运行”。
如上图所示,每一个小方格就是一个时间片,大约100ms。假设现在我同时开着 Word、QQ、网易云音乐三个软件,CPU 首先去处理 Word 进程,100ms时间一到 CPU 就会被强制切换到 QQ 进程,处理100ms后又切换到网易云音乐进程上,100ms后又去处理 Word 进程,如此往复不断地切换。我们将其中的 Word 单独拿出来看,如果时间片足够小,那么以人类的反应速度看就好比最后一个处理过程,看上去就会有“CPU 只处理 Word 进程”的幻觉。随着芯片技术的发展,CPU 的处理速度越来越快,在保证流畅运行的情况下可以同时运行的进程越来越多。
1.2 多核CPU
随着运行的进程越来越多,人们发现进程的创建、撤销与切换存在着较大的时空开销,因此业界急需一种轻型的进程技术来减少开销。于是上世纪80年代出现了一种叫 SMP(Symmetrical Multi-Processing)的对称多处理技术,就是我们所知的线程概念。线程切换的开销要小很多,这是因为每个进程都有属于自己的一个完整虚拟地址空间,而线程隶属于某一个进程,与进程内的其他线程一起共享这片地址空间,基本上就可以利用进程所拥有的资源而无需调用新的资源,故对它的调度所付出的开销就会小很多。
以 QQ 聊天软件为例,上文我们一直都在说不同进程如何流畅的运行,此刻我们只关注一个进程的运行情况。如果没有线程技术的出现,当 QQ 这个进程被 CPU “临幸”时,我是该处理聊天呢还是处理界面刷新呢?如果只处理聊天,那么界面就不会刷新,看起来就是界面卡死了。有了线程技术后,每次 CPU 执行100ms,其中30ms用于处理聊天,40ms用于处理传文件,剩余的30ms用于处理界面刷新,这样就可以使得各个组件可以“并行”的运行了。于是乎我们可以提炼出两点关于多线程的适用场景:
- 通过使用多核 CPU 提高处理速度。
- 保证 GUI 界面流畅运行的同时可以执行其他计算任务。
1.3基础理解
线程与并行处理任务息息相关,就像进程一样。那么,线程与进程有什么区别呢?当你在电子表格上进行数据结算的时候,在相同的桌面上可能有一个播放器正在播放你最喜欢的歌曲。这是一个两个进程并行工作的例子:一个进程运行电子表格程序;另一个进程运行一个媒体播放器。这种情况最适合用多任务这个词来描述。进一步观察媒体播放器,你会发现在这个进程内,又存在并行的工作。当媒体播放器向音频驱动发送音乐数据的时候,用户界面上与之相关的信息不断地进行更新。这就是单个进程内的并行线程。
每个程序启动后就会拥有一个线程。该线程称为”主线程”(在Qt应用程序中也叫”GUI线程”)。Qt GUI必须运行在此线程上。所有的图形元件和几个相关的类,如QPixmap,不能工作于非主线程中。非主线程通常称为”工作者线程”,因为它主要处理从主线程中卸下的一些工作。
基本上,对线程来讲,有两种使用情形:
· 利用多核处理器使处理速度更快。
· 将一些处理时间较长或阻塞的任务移交给其他的线程,从而保证GUI线程或其他对时间敏感的线程保持良好的反应速度。
1.4 线程的生命周期
- 这里简单了解一下线程从创建到退出的过程。首先是「创建」一个新线程,等待 CPU 来执行;当 CPU 来执行时,如果该线程需要等待另外某个事件被执行完后才能执行,那该线程此时是处于「阻塞」状态;如果不需要等待其他事件,线程就可以被「运行」了,也可以称为正在占用时间片;时间片用完后,线程会处于「就绪」状态,等待下一次时间片的到来;所有任务都完成后,线程就会进入「退出」状态,操作系统就会释放该线程所分配的资源。
2.Qt 提供的多线程操作及其适用场景
2.1 线程类
首先就是 QThread 类,它是所有线程类的基础,该类提供了很多低级的 API 对线程进行操作,每一个 QThread 对象都代表一个线程。使用该类开新线程并运行某段代码的方式一般有两种:(1)调用 QObject 的 moveToThread() 函数将 QObject 对象移到新开的 QThread 线程对象中,这样 QObject 对象中所有的耗时操作都会在新线程中被执行;(2)继承 QThread 并重写 run() 函数,将耗时操作的代码放入这个函数里执行就可以了。
上文我们说过“进程的创建、撤销与切换存在着较大的时空开销”,因此出现了线程这种轻量级进程技术。如果还想进一步的降低系统资源开销,人们想出了一个办法,就是让执行完所有任务的线程不被销毁,让它处于“待命”的状态等待新的耗时操作“进驻”进来。Qt 提供了 QThreadPool 和 QRunnable 这两个类来对线程进行重复的使用。使用的方法一般是将耗时操作放入 QRunnable 类的 run() 函数中,然后整体把 QRunnable 对象扔到 QThreadPool 对象中就可以了。
3.代码示例及步骤
使用QT5的线程创建方式
代码示例
注意:线程处理函数内部,不允许操作图形界面
connect()第五个参数的作用,连接方式:默认,队列,直接
多线程时才有意义
默认的时候
如果是多线程,默认使用队列
如果是单线程, 默认使用直接方式
队列: 槽函数所在的线程和接收者一样
直接:槽函数所在线程和发送者一样
(1)添加新文件
(2)创建界面
(3)代码
通过信号和槽启动线程:很明显不是同一个线程
如果直接调用函数:显示在同一个线程
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
#include <QThread>
#include <QDebug>
class Mythread : public QObject
{
Q_OBJECT
public:
explicit Mythread(QObject *parent = nullptr);
public slots:
//线程处理函数
void myTimeout();
//设置状态
void setStatus(bool flag = false);
signals:
//数据处理完会发送信号
void mySingal();
public slots:
private:
//设置停止的标志位
bool isStop; //判断线程是否正在运行的标准位
};
#endif // MYTHREAD_H
mythread.cpp
#include "mythread.h"
Mythread::Mythread(QObject *parent) : QObject(parent)
{
//开始状态是关闭的
isStop = false;
}
void Mythread::myTimeout()
{
//子线程一直开启处理数据
while (isStop == false) {
//调用1s的睡眠,相当于数据处理
QThread::sleep(1);
emit mySingal();
//打印线程号,用于查看是否是不同的线程
qDebug() << "子线程号:" << QThread::currentThread();
}
}
void Mythread::setStatus(bool flag)
{
isStop = flag;
}
mywediget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include <QThread>
#include "mythread.h"
namespace Ui {
class MyWidget;
}
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
~MyWidget();
void dealSignal();
void dealClose();
private slots:
void on_btnStart_clicked();
void on_btnStop_clicked();
signals:
//启动线程的信号
void startThread();
private:
Ui::MyWidget *ui;
Mythread *myThread;
QThread *newThread;
};
#endif // MYWIDGET_H
mywediget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
//步骤1 :新建自定义线程,将其添加到子线程中
//动态分配空间,注意不能指定父对象
//因为我们要把它分配到一个线程中去
myThread = new Mythread;
//创建子线程
newThread = new QThread(this);
//将我们自定义的线程添加到子线程中
myThread->moveToThread(newThread);
//连接信号和槽
//当线程处理完一次,会触发一次信号
connect(myThread, &Mythread::mySingal, this, &MyWidget::dealSignal);
qDebug() << "主线程号:" << QThread::currentThread();
//连接启动线程的信号和槽
connect(this, &MyWidget::startThread, myThread, &Mythread::myTimeout);
connect(this, &MyWidget::destroyed, this, &MyWidget::dealClose);
}
MyWidget::~MyWidget()
{
delete ui;
delete myThread;
}
void MyWidget::on_btnStart_clicked()
{
//防止重复开启
if(newThread->isRunning() == true) {
return;
}
//步骤2 在开启线程的地方启动线程
newThread->start();
//开启之后设置状态位
myThread->setStatus(false);
//此时我们线程中的函数还没有启动
//注意不能直接调用线程处理函数
//因为直接调用,导致,线程处理函数和主线程是在同一个线程
// myThread->myTimeout();
emit startThread();
}
void MyWidget::dealSignal()
{
static int i = 0;
ui->lcdNumber->display(i);
i++;
}
void MyWidget::dealClose()
{
on_btnStop_clicked();
}
void MyWidget::on_btnStop_clicked()
{
//防止重复关闭
if(newThread->isRunning() == false) {
return;
}
myThread->setStatus(true);
//循环退出,如果没有循环则什么也不做
//但是如果存在没完成的事情,则会完成,不会退出
newThread->quit();
//阻塞线程
newThread->wait();
}
代码下载:
Thread3.rar