同步、异步、阻塞、非阻塞,这四个概念很少人可以说清楚,不信的话,你可以先自己试着写下来
同步和异步
关注维度:消息的通信机制(synchronous communication/asynchronous communication)
判断标准:调用者是否主动等待被调用者的返回结果
同步的理论说明:任务A的执行过程中调用了任务B。任务A对任务B发起调用后,主动等待调用结果。
同步的生活举例:你去书店问老板,是否有《操作系统》这本书,老板说:稍等,我查一下。然后开始查啊查,等查好了,告诉你结果(主动等待被调用方返回结果)。
异步的理论说明:任务A的执行过程中调用了任务B。任务A对任务B发起调用后,继续执行后续工作。任务B完成后通过状态、通知来通知调用者。
异步的生活举例:你去书店问老板,是否有《操作系统》这本书,查好了打电话给你,然后直接挂电话了(此时被调用方不返回结果)。过了几天,查好了,老板主动打电话给你(被调用方回调调用方,告知结果)。
阻塞和非阻塞
关注维度:任务在等待调用结果时的状态
判断标准:调用方在等待被调用方的返回结果时,是否可以做其他事(是否被挂起)
阻塞的理论说明:任务A对任务B发起调用后,任务B需要执行一段时间才可返回结果,任务A选择等待任务B的返回结果(暂时挂起)。
阻塞的生活举例:你去书店问老板,是否有《操作系统》这本书,你会一直把自己挂起,什么是后不干,一直在那等,直到得到返回结果。
非阻塞的理论说明:任务A对任务B发起调用后,与此同时,任务A在任务B执行的过程中去完成别的工作,等待任务B结果返回后再继续(不挂起,而是继续执行自己的任务)。
非阻塞的生活举例:你去书店问老板,是否有《操作系统》这本书,不管老板有没有告诉你,你自己都先去玩了(继续执行自己的任务而不是干等),但是也要偶尔也要check一下老板是否有了结果。
我们把用户线程当做调用者,把内核线程当做被调用者,用几张图和简单的示例代码描述一下当前流程的几种I/O模型:
同步阻塞IO:

read(socket, buffer)
process(buffer)
同步非阻塞IO:

while(read(socket,buffer) != SUCCESS);
process(buffer);
IO多路复用:

select(socket);
while(1){
sockets = select();
for(socket in sockets){
if(canRead(socket)){
read(socket,buffer);
process(buffer);
}
}
}
虽然这种方式允许在单个线程中处理多个IO请求,但是每个IO请求的过程还是阻塞的,平均时间甚至比同步阻塞IO模型要更长
Reactor模式:

UserEventHandler handleEvent(){
if(canRead(socket)){
read(socket,buffer);
process(buffer);
}
}
Reactor.register(new UserEventHandler(socket));
Reactor.epollHandleEvents(){
while(1){
sockets = select();
for(socket in sockets){
getEventHandler(socket).handleEvent();
}
}
}
IO多路复用还是使用了会阻塞线程的select系统调用,最多只能算异步阻塞IO,而非真正的异步IO
异步非阻塞IO:
在异步阻塞IO中,用户线程收到通知后自行读取数据、处理数据。而在异步非阻塞IO中,用户线程收到通知时,数据已经被准备好,用户线程可以直接使用(省略了读取数据这一过程)

aioRead(socket, new UserCompeletionHandler());
进程是什么:
计算机最初发明的初衷是用于解决耗时耗力的复杂计算,是一个计算器
最原始的计算机执行程序的过程如下:等待用户输入指令->用户输入->计算机操作->等待用户输入指令->用户输入->计算机操作。在用户思考或者输入的过程中,计算机就空闲下来。
后来有了批处理系统,用户可以把许多指令(如输入1,输入2)写在磁盘中,计算机的执行过程变为:用户输入指令集合->取指令->执行->取指令->执行。
批处理系统大大提高了便捷性,但是还是存在一个问题,假设指令集合中有A,B两个程序,当程序A进行I/O处理时,程序B只能等待程序A直到其运行完。也就是说,内存中只能有一个程序在执行。
|程序One|程序Two| 计算机中有两个程序
Time1: |内存| 内存中装载程序One
Time2: |内存| 内存中装载程序Two
那么,如何在内存中装入多个程序呢?于是人们发明了进程,每个在运行的程序都看做一个进程,给每个进程分配合适大小的对应的内存地址空间,进程之间的空间互不干扰,并且保存每个进程的运行状态。通过进程之间的相互切换,使计算机看起来在一段时间内有几个程序在同时执行。
|程序One|程序Two| 计算机中有两个程序
Time1: |部分内存|部分内存| 内存中装载程序One,Two,分别在不同的部分。
进程让程序之间的并发成为了可能,从宏观上看,某个时间段内有多个程序在同时执行,但实际上在某一时刻只有一个程序(一部分的内存)会得到CPU,进行执行。
线程是什么:
进程让程序之间的并发成为了可能,我们可以在电脑上同时听歌,打字了(两个不同的程序之间切换)。
但是人们对程序实时性的要求越来越高。比如对QQ音乐来说,它不仅要处理用户所发送的交互请求,还要播放歌曲。假设某一时刻QQ音乐在播放歌曲,你点击了“暂停”按钮,需要等待播放歌曲完毕之后才能处理“暂停”操作,这种程序肯定是不合格的。
于是人们把QQ音乐这个程序所对应的进程拆分成了多个线程,有播放歌曲的线程,处理交互请求的线程,每个线程负责一个独立的子任务,这样的话,我们点击了“暂停”按钮,QQ音乐会释放暂停播放歌曲的线程,让交互请求的线程处理用户的请求,响应完之后再切换回来,让播放歌曲的线程得到CPU。具体过程如下:
播放线程---------------------挂起`````````````````播放线程----------
用户点击暂停
交互线程--------处理完毕
线程让进程内的子任务并发成为了可能。
3.程序,进程,线程:
程序是我们写的代码,需要对应到一个具体的进程来运行,进程间有独立的内存地址,互不干扰。线程是进程的子任务,属于同一进程的线程共享相同的内存。
进程让程序之间的并发成为了可能,线程让进程内的子任务并发成为了可能。