什么是并发、进程、线程?
一、基本概念(通俗理解)
1.1并发
一个程序同时执行多个独立的任务,就是一种并发。采用并发技术的目的是同时进行多个任务,提高性能。
1.2进程
首先介绍一下可执行程序,在windows系统下,如后缀名为.exe的文件(如图1中的第一个文件)。
图 1Windows下文件
在linux系统下,如gcc -o 命令生成的文件(如图2种绿色的文件)
图2 linux某目录下的文件
一个可执行程序运行起来,就叫创建了一个进程。在Windows下可以在任务管理器中查看正在运行的进程
图3 Windows下的进程
注意,计算机的CPU是多核的,但并不是所有的进程都是同时进行的,因为进程的数量远远高于cpu的核数,cpu的每个核就可以执行多个进程,但这些进程是在时间上穿插完。
1.3线程
每个进程都有一个主线程,这个主线程是唯一的。主线程随着进程默认启动,与进程唇齿相依,有进程就有主线程,主线程结束,进程也就结束。运行程序时,实际上是进程的主线程来执行main函数中的代码,线程可以理解为一条代码的执行通路,每创建一个新线程,就可以在同一时刻,多干一个不同的事情。线程并不是越多越好,每个线程,都需要一个独立的堆栈空间(1M左右),线程中的切换要保存很多中间状态。
二、并发的实现方法
实现并发的手段:(1) 通过多个进程;(2) 在单独一个进程中创建多个线程。
2.1多进程并发
进程之间的通讯:同一个电脑上,管道,文件,消息队列,共享内存;不同电脑上:socket通信技术。
2.2多线程并发
每个线程都已自己独立的运行路线,但是一个进程中的不同线程共享地址空间(一个线程中定义的变量可以在另一个线程中调用)
使用多线程并发的开销远远小于多进程并发的开销。
共享内存带来数据一致性问题:多个线程在同一时刻往所共享的内存空间写入数据,会出现覆盖问题。
多进程和多线程虽然可以混合使用,但优先考虑多线程手段。
2.3c++11中创建线程
2.3.1通过全局函数
c++11通过thread类模板创建线程,
第一步,使用时要帮头文件
#include<thread>
第二步,创建初始函数:下图中的thread函数。
int mythread()
{
std::cout << "子线程开始执行,线程ID为" << std::this_thread::get_id() << std::endl;
std::chrono::microseconds dur(7000);
std::this_thread::sleep_for(dur);
std::cout << "子线程结束执行,线程ID为" << std::this_thread::get_id() << std::endl;
return 15;
}
第三步,main中写代码:定义thread类的对象。
thread myobj(my_print); //创建了一个线程对象,是子线程执行起点。
myobj.join(); //阻塞主线程,让主线程等待子线程执行完毕,然后主线程和子线程汇合,主线程再继续。
2.3.2通过函数对象(仿函数)
class TA
{
void operator()()
{
//......
}
};
主函数中程序
TA a;
thread myobj(a);
myobj.join();
2.3.3通过类成员函数
class A
{
public:
void Receive()
{
for (int i = 0; i < 100000; i++)
{
cout << "接收数据的线程开始工作了:正在接收第" << i << "个数据\n";
ReMessages.push_back(i);
}
}
void Send()
{
for (int i = 0; i < 100000; i++)
{
if(!ReMessages.empty())
{
cout << "发送数据的线程开始工作了:正在发送第" << i << "个数据\n";
int command = ReMessages.front();
ReMessages.pop_front();
}
else
{
cout << "数据为空" << endl;
}
}
}
private:
list<int> ReMessages;
};
主函数中程序
A a;
std::thread myobj(&A::Receive, &a);
std::thread myobj1(&A::Send, &a);
myobj1.join();
myobj.join();
2.3.4使用lambda表达式
auto mylambda=[]
{
//.....
}
thread myobj(mylambda);
myobj.join();
2.2thread中的detach()成员函数
创建玩thread类对象后,如果调用的不是join()成员函数,而是detach()成员函数,这样的话主线程不会等子线程,但主线程的结束不影响子线程的执行。
为什么引入detach,因为如果创建了很多子线程,让主线程等待子线程结束,这种编程方法不太好。
一旦一个线程detach后,该线程就会和主线程分离,此时子线程会驻留在后台运行,即被c++运行时库接管,而不被我们控制,当这个子线程执行完成后,由运行时库负责清理线程相关的资源(linux里面的守护线程)。
主线程结束后,子线程如果没有执行完会继续执行,但不会在往终端上输出东西了。
join和detach成员函数,thread对象只能调用其中的一个。
2.3 joinable()成员函数
判断是否可以成功使用join或者detach。
返回值为 true或者false。