转载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 的执行速度已经快到让人们感觉不到这种切换的顿挫感,就真的好像两个进程在“并行运行”。
image.png
如上图所示,每一个小方格就是一个时间片,大约100ms。假设现在我同时开着 Word、QQ、网易云音乐三个软件,CPU 首先去处理 Word 进程,100ms时间一到 CPU 就会被强制切换到 QQ 进程,处理100ms后又切换到网易云音乐进程上,100ms后又去处理 Word 进程,如此往复不断地切换。我们将其中的 Word 单独拿出来看,如果时间片足够小,那么以人类的反应速度看就好比最后一个处理过程,看上去就会有“CPU 只处理 Word 进程”的幻觉。随着芯片技术的发展,CPU 的处理速度越来越快,在保证流畅运行的情况下可以同时运行的进程越来越多。

1.2 多核CPU

随着运行的进程越来越多,人们发现进程的创建、撤销与切换存在着较大的时空开销,因此业界急需一种轻型的进程技术来减少开销。于是上世纪80年代出现了一种叫 SMP(Symmetrical Multi-Processing)的对称多处理技术,就是我们所知的线程概念。线程切换的开销要小很多,这是因为每个进程都有属于自己的一个完整虚拟地址空间,而线程隶属于某一个进程,与进程内的其他线程一起共享这片地址空间,基本上就可以利用进程所拥有的资源而无需调用新的资源,故对它的调度所付出的开销就会小很多。

3. 线程Thread - 图2
以 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()第五个参数的作用,连接方式:默认,队列,直接
多线程时才有意义
默认的时候
如果是多线程,默认使用队列
如果是单线程, 默认使用直接方式
队列: 槽函数所在的线程和接收者一样
直接:槽函数所在线程和发送者一样
image.png
(1)添加新文件
image.png
(2)创建界面
image.pngimage.png
(3)代码
通过信号和槽启动线程:很明显不是同一个线程
image.png
如果直接调用函数:显示在同一个线程
image.png

mythread.h

  1. #ifndef MYTHREAD_H
  2. #define MYTHREAD_H
  3. #include <QObject>
  4. #include <QThread>
  5. #include <QDebug>
  6. class Mythread : public QObject
  7. {
  8. Q_OBJECT
  9. public:
  10. explicit Mythread(QObject *parent = nullptr);
  11. public slots:
  12. //线程处理函数
  13. void myTimeout();
  14. //设置状态
  15. void setStatus(bool flag = false);
  16. signals:
  17. //数据处理完会发送信号
  18. void mySingal();
  19. public slots:
  20. private:
  21. //设置停止的标志位
  22. bool isStop; //判断线程是否正在运行的标准位
  23. };
  24. #endif // MYTHREAD_H

mythread.cpp

  1. #include "mythread.h"
  2. Mythread::Mythread(QObject *parent) : QObject(parent)
  3. {
  4. //开始状态是关闭的
  5. isStop = false;
  6. }
  7. void Mythread::myTimeout()
  8. {
  9. //子线程一直开启处理数据
  10. while (isStop == false) {
  11. //调用1s的睡眠,相当于数据处理
  12. QThread::sleep(1);
  13. emit mySingal();
  14. //打印线程号,用于查看是否是不同的线程
  15. qDebug() << "子线程号:" << QThread::currentThread();
  16. }
  17. }
  18. void Mythread::setStatus(bool flag)
  19. {
  20. isStop = flag;
  21. }

mywediget.h

  1. #ifndef MYWIDGET_H
  2. #define MYWIDGET_H
  3. #include <QWidget>
  4. #include <QThread>
  5. #include "mythread.h"
  6. namespace Ui {
  7. class MyWidget;
  8. }
  9. class MyWidget : public QWidget
  10. {
  11. Q_OBJECT
  12. public:
  13. explicit MyWidget(QWidget *parent = 0);
  14. ~MyWidget();
  15. void dealSignal();
  16. void dealClose();
  17. private slots:
  18. void on_btnStart_clicked();
  19. void on_btnStop_clicked();
  20. signals:
  21. //启动线程的信号
  22. void startThread();
  23. private:
  24. Ui::MyWidget *ui;
  25. Mythread *myThread;
  26. QThread *newThread;
  27. };
  28. #endif // MYWIDGET_H

mywediget.cpp

  1. #include "mywidget.h"
  2. #include "ui_mywidget.h"
  3. MyWidget::MyWidget(QWidget *parent) :
  4. QWidget(parent),
  5. ui(new Ui::MyWidget)
  6. {
  7. ui->setupUi(this);
  8. //步骤1 :新建自定义线程,将其添加到子线程中
  9. //动态分配空间,注意不能指定父对象
  10. //因为我们要把它分配到一个线程中去
  11. myThread = new Mythread;
  12. //创建子线程
  13. newThread = new QThread(this);
  14. //将我们自定义的线程添加到子线程中
  15. myThread->moveToThread(newThread);
  16. //连接信号和槽
  17. //当线程处理完一次,会触发一次信号
  18. connect(myThread, &Mythread::mySingal, this, &MyWidget::dealSignal);
  19. qDebug() << "主线程号:" << QThread::currentThread();
  20. //连接启动线程的信号和槽
  21. connect(this, &MyWidget::startThread, myThread, &Mythread::myTimeout);
  22. connect(this, &MyWidget::destroyed, this, &MyWidget::dealClose);
  23. }
  24. MyWidget::~MyWidget()
  25. {
  26. delete ui;
  27. delete myThread;
  28. }
  29. void MyWidget::on_btnStart_clicked()
  30. {
  31. //防止重复开启
  32. if(newThread->isRunning() == true) {
  33. return;
  34. }
  35. //步骤2 在开启线程的地方启动线程
  36. newThread->start();
  37. //开启之后设置状态位
  38. myThread->setStatus(false);
  39. //此时我们线程中的函数还没有启动
  40. //注意不能直接调用线程处理函数
  41. //因为直接调用,导致,线程处理函数和主线程是在同一个线程
  42. // myThread->myTimeout();
  43. emit startThread();
  44. }
  45. void MyWidget::dealSignal()
  46. {
  47. static int i = 0;
  48. ui->lcdNumber->display(i);
  49. i++;
  50. }
  51. void MyWidget::dealClose()
  52. {
  53. on_btnStop_clicked();
  54. }
  55. void MyWidget::on_btnStop_clicked()
  56. {
  57. //防止重复关闭
  58. if(newThread->isRunning() == false) {
  59. return;
  60. }
  61. myThread->setStatus(true);
  62. //循环退出,如果没有循环则什么也不做
  63. //但是如果存在没完成的事情,则会完成,不会退出
  64. newThread->quit();
  65. //阻塞线程
  66. newThread->wait();
  67. }

代码下载:
Thread3.rar