同步、异步、并发、并行、线程、进程及多线程(一)

例一:

#include<iostream>
using namespace std;
void a()
{
    cout<<" Hello! Lilei "<<endl;
}

int main()
{
	cout<<" Hello! "<<endl;
    a();
    cout<<" Hello! Leili "<<endl;
    return 0;
}

并行:指两个或多个事件在同一时间段内发生。
并发:指两个或多个事件在同一时刻发生。(同时发生)
同步:即程序的顺序执行。调用者依赖于被调用者。当一个请求被发送给被调用者时,调用者需要等待被调用者执行完毕才能继续往下执行自己的程序。在被调用者执行完自己的代码块之前,调用者一直处于等待状态,不能做其他的事情。
比如上面的例一中的main()函数和函数a()就是一个同步调用关系,在a()执行完之后,才会输出”Hello!Leili”。如果函数a()运行了很长时间,假设运行了10秒,那么在输出”Hello!”后要等待十秒才会输出”Hello!Leili”。
异步:与同步相对。调用者与被调用者相互独立,不存在依赖关系。当一个请求被发送给被调用者时,调用者在等待被调用者执行的同时,可以继续往下执行自己的程序。调用者无需等待,可以做其他的事情。当被调用者做完自己的事后,只需要告诉调用者自己已经做完了即可。
进程:是指内存中正在运行的应用程序(即可执行程序),当一个程序被加载到内存中之后就变成了进程。资源(CPU、内存)分配的基本单位,它是程序执行时的一个实例。程序运行时,系统就会创建一个进程,并为它分配资源。比如,我们将上面的例一另存为hello.cpp,编译为可执行文件hello,然后运行该可执行文件。这时,我们就说创建了一个进程。而里面的main()函数的代码就是一个线程。
线程:进程中的一条可执行代码路径。一个进程可以并发多条线程,每条线程执行不同的任务。比如,上面的例一中,只有一条线程,就是主线程。main函数是主线程入口,主线程先输出”Hello!”,然后调用函数a(),输出”Hello!Lilei”,最后再输出”Hello!Leili”。
多线程:多条可执行的代码路径同时(并发)执行。多线程看起来和异步很相似,但是它们并不相等,异步是最终目的,而多线程只是实现异步的一种方式。换句话说,我们通过创建多线程来达到异步的效果

在这里插入图片描述
如上,例二左侧代码编译运行结果如下:
输出a1 –> 等候10秒 -> 输出a2 -> 等候5秒 -> 输出a3 -> 等候2秒 -> 程序结束
右侧代码编译运行结果如下:
输出a1 -> 等候10秒 -> 提示” terminate called without an active exception”,退出程序
分析一下右侧代码,主线程在运行完a1()后,创建两个线程t2和t3分别运行函数a2()和a3(),最后返回0。之所以提示异常,是因为主线程已经没有别的任务了,它不会等待t2和t3跑完,而是会直接结束进程。这时候线程t2和t3就会因为被强制中断而出现异常。如下图:
图一 线程运行简图在这里插入图片描述
这个时候我们可以想到,如果我让主线程再做点别的事情,让它运行的时间比t2和t3长,让t2和t3运行完不就可以了?
为此,我们在例二右侧代码的”thread t3(a3);”下面加上一行”a1();”,让主线程再运行一次a1(),来看看情况如何。实际运行如下:
输出a1 -> 等候10秒 -> 输出a1,换行,a2,换行,a3(或者a1,换行,a3,换行,a2) -> 等候10秒 -> 程序结束。
经过多次运行,发现线程t2和t3的输出顺序是随机的,而主线程第二次运行的a1()的输出总是快于t2和t3。(可能和线程的优先级有关,暂不讨论)。

而实际上,线程库中提供了一个函数join(),用于等待线程执行完毕。方法join()的作用是等待线程对象(即调用该方法的线程对象,谁调用join,就得等谁)销毁。回到例二(没有在”thread t3(a3);”下面加一行”a1();”),我们可以通过t2.join()让主线程等待t2运行完毕,这时候主线程虽然没有别的任务了,但是它必须等待,不能结束进程。
join()的调用只需要出现在想要调用它的线程的定义之后即可,比如创建线程 t后下一行调用join和在最后面return前一行调用没有区别,join只是告诉主线程,必须等我执行完了你才能结束;如果t是嵌套在其他线程(非主线程)里,那么就是告诉创建t的线程(父线程)。
例三:

#include<iostream>
#include<thread>
#include<unistd.h>
using namespace std;
void a1()
{
	cout<<"a1"<<endl;
	sleep(10);
}

void a2()
{
	cout<<"a2"<<endl;
	sleep(5);
}

void a3()
{
	cout<<"a3"<<endl;
	sleep(2);
}

int main()
{
	a1();
	thread t2(a2);
	thread t3(a3);
	t2.join();
	t3.join();
	return 0;
}

例三编译运行结果如下:(运行十几次出现先输出a3)
输出a1 -> 等候十秒 -> 输出a2,换行,a3(或者a3,换行,a2) -> 程序结束

join()函数详解
join()函数英文解释:The function returns when the thread execution has completed. This synchronizes the moment this function returns with the completion of all the operations in the thread: This blocks the execution of the thread that calls this function until the function called on construction returns (if it hasn’t yet).
当线程执行完的时候join()函数返回(返回值)。这会让该时刻join()函数的返回值与线程里完成所有的操作进行同步(也就是说,join()函数会和调用它的线程对象同步。根据上面的同步的定义,只有调用join()函数的线程对象执行完了,join()才能得到返回):这将阻塞调用这个函数(指join()函数)的线程的执行,直到在构造线程对象时调用的函数(指作为参数传给所创建的线程对象的函数)返回,前提是它还没有返回。

上面的翻译可能比较凝练,我们通过对语句拆分进行理解,结合简单的例子以及一系列问答来加以说明。
例四:

#include<iostream>
#include<thread>
#include<unistd.h>
using namespace std;
void Func()
{
	cout<<"this is function Func!"<<endl;
	sleep(3);
}

int main()
{
	cout<<"start!"<<endl;
	thread t(Func);
	t.join();
	cout<<"end!"<<endl;
	return 0;
}

①问:join()函数等待谁执行完毕再返回?
答:等待线程t执行完毕。因为构造线程对象t时把函数Func()作为参数传入,t会在自己的线程内运行完函数Func()后自行销毁,所以也可以说join()函数等待t调用的Func()执行完毕。
②问:谁和谁同步?
答:join()函数和调用它的线程对象同步。
在上面的例子中,join()函数和线程对象t同步。另外,虽然主线程和t是异步运行的关系,但是因为t调用了join(),主线程最后面在t运行完才会输出”end!”,这就相当于主线程和t在异步的基础上又多了一层同步的关系。
③问:谁被阻塞了?
答:调用join()函数的线程对象所属的线程(也就是创建该线程对象的线程)。
在上面的例子中,t是在主线程中被创建,因此属于主线程。所以,程序在运行到join()时,主线程被阻塞(或挂起),并不会输出”end!”。等t运行完(在①中说过,线程t运行完也就是函数Func()运行完),主线程才会继续运行。
④问:“前提是它还没返回”是什么意思?
答:“它”是指作为参数传给t的函数Func()。如果Func()运行速度很快,在程序运行到t.join()这一行时,Func()已经返回了返回值,那么t.join()并不会阻塞主线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值