一、前言
本文要介绍的是C++中的线程库thread
,当然,也并不仅仅只是介绍它,因为它只有简单的几个函数而已,很简单。
本文更多的是来讨论线程的必要性、底层原理、以及如何保证安全的使用它。
与之相对应的概念叫做进程,如果你不熟悉两者的区别,可以看一看本站的另一篇文章:进程与线程。
二、什么是线程?
如果到目前为止你还只是写过控制台程序的话,可能确实很难感受到线程的作用。
以最常见的微信为例,它是一个GUI
程序,即:带有界面。
那你思考过为什么无论它目前正在干什么(比如发送大文件、登录、发送消息等等),你都能很顺利的拖动、更换它的界面吗?
如果一个程序同一时间只是干一件事,比如发送一个100GB
的超大文件,可能得花费很长的时间对吧,那此时用户界面的响应怎么办呢?
这时如果当你拖动界面、更换聊天框等等操作,就会直接让程序暂停响应,因为它正在忙着发送文件。
不出意外的话,我相信你应该是遇到过这些情况的。
一般来说,GUI程序最重要的就是与用户的交互性,无论程序当前在干什么,用户应该都能实时看到、并对其做出控制,所以基本所有GUI程序的主线程都是用来处理用户界面交互的。
这里的主线程,就是当你启动一个程序时,自带的那个执行代码的线程。
注意:进程本身是不执行代码的,执行代码的是线程。
一个进程可以理解为一个exe
程序(并不绝对),而一个进程中可以有很多个线程,其中默认启动的执行代码的线程一般就叫做主线程。
而至于其它的任务,比如最直观的例子:发送文件,则是交给了其它线程来做,主线程在这一过程只做两件事:
- 将发送文件的任务交给其它线程。
- 如果其它线程在处理过程中发生了什么情况,再反应给主线程,由主线程来决定接下来该怎么做
比如子线程在发送文件时网络出错,就会报告给主线程,再由主线程决定是否将其显示到用户界面上。
注意在一般情况下,为了保证安全性,只会有一个线程来处理用户界面交互,而这个线程一般就是主线程,至少就我目前看到的GUI框架都是这样做的,比如Qt
、MFC
都是如此。
上面用的一个微信GUI程序发送大文件、同时需要处理用户点击、拖动等情况的例子作为讲解,将其更直观的反应到代码中就是下面这样:
当然,上面的也仅仅只是一个示例代码程序,并不存在这些函数,但一个GUI程序的运行流程都大致如此,只是细节不同罢了。
比如获取用户界面操作,现代操作系统都是通过发送消息的形式来的,比如鼠标移动消息、点击消息,按键消息等等。
而上面我也并没有写开启的子线程如何操作,如何发送消息给主线程。
用一个更加简单的控制台程序来说就是像下面这样的:
#include<iostream>
#include<Windows.h>
#include<thread>
using namespace std;
bool f = false; //一个信号,决定文件是否发送成功了
void send_file() {
Sleep(1000 * 30); //假设这是在发送文件,需要30s
f = true; //发送成功,置为true,代表文件发送成功了;
}
int main() {
f = false;//初始化文件没有发送
thread t(send_file); //开一个新的线程去发送文件。
t.detach(); //线程与该对象分离。
while (f == false) { //只要文件没有发送成功,那就继续循环
string s;
cin >> s; //处理用户输入
cout <<"你输入了:"<< s << endl;
}
//如果退出循环了,那就说明文件已经发送成功了
cout << "文件发送成功";
}
这是一个非常简单的程序,模拟了一个多线程的情形,这里我用的休眠30s
作为发送文件操作。
并且在发送文件的同时,你还可以输入字符串,这就像是GUI程序响应你的鼠标点击、拖动一样。
同时还用一个全局变量f
来让两个线程互相通信,只要f
为true
,就代表子线程中的文件发送成功了。
当然,你也可以在添加一些表示进度的信息,让主线程每隔一会就打印一下,可能会更加的直观。