2020/01/16 02-同步异步阻塞非阻塞概念和各种IO模型

在这里插入图片描述
函数或方法被调用,调用者是否得到最终结果。
直接能拿到最终结果,就是同步调用;
不直接拿到最终结果,就是异步调用。
跟阻塞是两回事,只看结果

**
同步就是我让你打饭,你不打好给我不走开,直到你打饭给了我。
异步就是我让你打饭,你打着,我不等你,但是我会盯着你,你打完,我会过来拿走的。异步并不保证多长时间最终打完饭(并不保证立即获得结果)。**

之前的currcent.future,这个就是异步,建立future对象,可以观察,得到正确结果没有,一直到了最后才拿正确结果
异步强调的是,并不能在一定时间内拿到一个最终结果,异步一般是比较耗时的操作,可以让调用者去做其他操作

异步和同步调用区别就是,调用者是否得到最终结果在这里插入图片描述
函数和方法调用的时候,是否立即返回。
立即返回就是非阻塞调用。
不立即返回就是阻塞调用

比如queue.get()拿数据

一旦调用就立即判断能否拿到需要的数据,如果能拿到就立即拿走,不能拿到就抛出异常。
阻塞调用其实就是get一直卡者着,不能立即返回

在这里插入图片描述

同步异步强调的是,是否能拿到结果,同步异步的最终差别是拿到最终结果。
阻塞、非阻塞,强调的是时间,是否需要等待

异步往往一开始拿到的结果不是最终结果,同步要求必须拿到最终结果

异步返回一般不是最终结果。调用者不能通过这种调用得到结果,需要通过被调用者的其它方式通知调用者,来取回最终结果。
current.future,异步,调用立马就返回,是异步,最终结果是future对象的results方法拿到,还需要判断down了没有,如果down了就可以调用results,如果没有down,默认方式是阻塞行为,阻塞到最终结果出现。
result是等待最终结果出现,循环判断future.down,是否返回true
在这里插入图片描述
异步这个概念,就是有些时候,计算太耗时,直接傻等相当于这个线程完全阻塞,不方便,但是异步一般不能直接拿到最终结果,所以写代码的时候要判断,先执行下面的,然后回头再判断结果出来没有

**
阻塞和非阻塞的区别在于,调用者是否还能干其他事,
阻塞,调用者只能干等。
非阻塞,调用者可以先去忙会别的,不用一直等。**
在这里插入图片描述
一个是看结果,一个是看是否卡在那里,谁调用谁阻塞

在这里插入图片描述
同步阻塞,我啥事不干,就等你打饭打给我。打到饭是结果,而且我啥事不干一直等,同步加阻塞。queue.get()默认是同步阻塞
(一般开多线程解决,发挥并行优势)

同步非阻塞,我等着你打饭给我,但我可以玩会手机、看看电视。打饭是结果,但是我不一直等,现在学的函数(没有这样的例子)

异步阻塞,我要打饭,你说等叫号,并没有返回饭给我,我啥事不干,就干等着饭好了你叫我。例如,取了号什么
不干就等叫自己的号
(异步可以给一个中间结果,但是调用者就等最终结果好了)

异步非阻塞,我要打饭,你给我号,你说等叫号,并没有返回饭给我,我在旁边看电视、玩手机,饭打好了叫我。
(一般选择)
如何知道最终的结果,有三种方式:
1.被调用者主动通知调用者
2.调用者不断询问
3.回调行为,被调用者好了之后,由它来使用调用方 的动作,比如调用你的吃的动作

current.future有一个callback,就可以好了调用你的方法

同步异步,就是取决于你调度的那刻能否拿到最终结果,
如果拿到的是中间结果就是异步,异步是不能立即拿到中间结果的

同步阻塞最简单,加上多线程即可解决问题,另一种方式是异步非阻塞,非阻塞的时候可以不开多线程,

同步IO、异步IO、IO多路复用

在这里插入图片描述
IO分两个阶段:数据准备阶段,内核空间复制到用户空间进程的缓冲区阶段
CPU有环,是有权限的,和操作系统密切关联,内核是运行在特权的环中,用户空间不再特权里,如果调用系统资源,就需要把你的资源交给操作系统,让它来处理,这时候就有用户空间往内核空间复制数据的过程,大量复制数据还是效率蛮低的

发生IO的时候:
1.内核从输入设备读、写数据
2.进程从内核复制数据

在这里插入图片描述在这里插入图片描述在这里插入图片描述当用户发起请求recvfrom,用户进程希望得到一个数据(因为用户进程没有权限,就要去系统调用,内核中的API接口,让内核来帮忙完成),这时候有两个阶段,
第一阶段把数据从磁盘上把文件获取到内核空间,从内核复制到用户空间的缓存区中,这个就是对应画图的第4步
用户进程发送任务给内核之后,就一直在那里等待,同步阻塞,只能得到任务完成,最终得到数据
用户进程想要工作就需要把3,4步做完了,返回一个成功结果,这样用户进程得到数据了,就可以做别的事情去了,在这期间什么也做不了,是阻塞的状态
在这里插入图片描述在这里插入图片描述在这里插入图片描述

同步非阻塞IO模型在这里插入图片描述

repeatedly,重复调用recvfrom,就是想要返回一个结果,no datagram ready 数据还没有好,会抛出异常给你,EWOILDBLOCK,最后数据好了,内核数据准备好了,搬到用户空间,recvfrom就等到自己想要的结果在这里插入图片描述进程调用read操作,如果IO设备没有准备好,立即返回ERROR,进程不阻塞。用户可以再次发起系统调用,如果内核已经准备好,就阻塞,然后复制数据到用户空间。

第一阶段数据没有准备好,就先忙别的,等会再来看看。检查数据是否准备好了的过程是非阻塞的。
第二阶段是阻塞的,即内核空间和用户空间之间复制数据是阻塞的。

淘米、蒸饭我不等,我去玩会,盛饭过程我等着你装好饭,但是要等到盛好饭才算完事,这是同步的,结果就是盛好饭在这里插入图片描述在这里插入图片描述

IO多路复用、

在这里插入图片描述之前的是每个进程发送请求给系统调用,跑到内核,内核去磁盘上读取数据
每个进程都是直接去访问系统,调用获取数据的,这样对内核来讲,是直接和进程打交道的

不用跑腿,委托代理人即可
IO多路复用是专门找一个代理人叫select,(select相当于一个办事的大屏幕,当内核从磁盘拿到数据后,就通知某个用户进程,任务完成了)
在第一个阶段,用户的进程是阻塞在select这里的
后续的阶段,是用户进程需要等待数据从内核空间中复制到用户空间来完成的
实际上也是一种阻塞状态,只不过阻塞的位置和场景并不一样(阻塞在了select上,select接受了很多进程的调用,总代理)
select,利用IO多路复用,可以并发地响应用户请求,相当于专门的人接待客人,不用去和后面打交道,帮你等数据,数据好了通知你

centos6开始支持新的模型,EPOLL,epoll效率比select和poll高很多,提供了回调方式,提高了性能,python现在还不支持IOCP,不支持windows在这里插入图片描述
以select为例,将关注的IO操作告诉select函数并调用,进程阻塞,内核“监视”select关注的文件描述符fd(socket),被关注的任何一个fd对应的IO准备好了数据,select返回。再使用read将数据复制到用户进程。在这里插入图片描述在这里插入图片描述
虽然性能上没有提高,但是有专门的select模块,来并发响应用户请求
可以多路请求复用这个select(实际上并不是select才能实现这个功能,还有poll,也能使用系统调用功能,能够实现类似的功能
IO复用模型应用场景主要在如下环境中使用

epoll是有菜已经准备好了,告诉你放在哪里,直接去拿,这个效率比较高,实现了回调机制,回调避免了你自己去遍历,谁好了自己回调
在这里插入图片描述
select是有管理上线的,一般1024个fd,这就是一个数组,谁好了,自己去看下在这里插入图片描述在这里插入图片描述

异步IO

在这里插入图片描述
用户发起请求,用户进程,告诉内核需要数据,内核从磁盘获取数据,从内核空间把数复制到用户空间,这个用户程序一直在持续运行,一直没有阻塞,一直在忙自己的,所有的工作交由内核做
用户程序发些很不操心,内核做的事情较多一点,进程做的时候越少,同时就能响应更多用户请求

在这里插入图片描述
进程发起异步IO请求,立即返回。内核完成IO的两个阶段,内核给进程发一个信号。
举例:来打饭,跟师傅说饭好了叫你,饭菜好了,服务于把饭盛好了,打电话叫你。两阶段都是异步的。
(在整个过程中,进程都可以忙别的,等好了才过来)
叫外卖也是个异步非阻塞,是一种非常高效的方案

在这里插入图片描述在这里插入图片描述在这里插入图片描述

面试一般问同步阻塞,和异步非阻塞。
同步阻塞用比较多
同步非阻塞没什么用
IO多路复用,经常在高效编程使用的方案,是用select这个代理人来帮你监控这么多人IO
异步IO效率更高,等于叫外卖

python中IO多路复用

在这里插入图片描述
同步阻塞和异步非阻塞,在开发的时候用的比较多。
IO多路复用肯定要使用,有select,就可以让操作系统 帮助你监视关心的IO即可,谁好了就可以告诉你

在这里插入图片描述
python的select库是一个低级的库

如果是高并发的IO,select,poll并不是很好的选择,建议epoll
在这里插入图片描述
select维护一个文件描述符数据结构,可以作为一个数组,线性结构,单个进程使用有上限,通常是1024,线性扫描这个数据结构。效率低。每次要遍历所有的才知道谁完成了,效率低

poll和sleetc的 区别是内部数据结构使用链表,没有这个最大限制,但是依然是线性遍历才知道哪个设备就绪了

epoll使用事件通知机制,使用回调机制提高效率。epoll时候一部分闲置的一部分完成,这样用遍历效率太低,如果大部分都完成的,其实select和epoll性能差距不是很大

slecet/poll还要从内核空间复制消息到用户空间,
而epoll通过内核空间和用户空间共享一块内存来减少复制(MMAP内存映射,又是提高效率的地方)

(0拷贝,从用户空间到内核空间数据拷贝次数越少越好)
在这里插入图片描述
select/poll没有解决,线性访问的时候,遍历消耗时间太大的问题。
epoll有通知和回调机制,采用一些减少拷贝的技术,让内存和内核共用一片内存

3.4提供了高级的IO复用操作
在这里插入图片描述
把能封装的类都封装了,除了IOCP
在这里插入图片描述
register注册的关注的
select阻塞到register注册的编程ready即可
在这里插入图片描述
看一下为什么用这么一个东西就能解决

尽量给你选择最好的一种实现,也告诉你了select不能大于1024
在这里插入图片描述
poll都不支持,那就只有select在这里插入图片描述
defaultselector就是取当前操作系统支持的最高的模型,构造一个select对象,register让它关心哪些文件描述符
select阻塞,就在等哪个ok了

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这个问题比较复杂,需要涉及到多方面的知识。我会尽可能详细地解答你的问题。 首先需要明确的是,C++11 标准中自带的网络库只支持同步阻塞式的网络编程。如果需要实现异步非阻塞的网络编程,需要使用第三方库。这里我们选择 Boost.Asio 库进行实现。 Boost.Asio 是 Boost 库中的一个模块,提供了一种基于回调函数的异步 I/O 模型。它支持多种协议,包括 TCP、UDP、SSL 等。在 C++11 标准发布之前,Boost.Asio 就已经存在了,并且为 C++11 标准中网络库的实现提供了很多启示。 下面是一个简单的使用 Boost.Asio 实现异步非阻塞的前端和后端的示例代码: 前端代码: ```cpp #include <boost/asio.hpp> #include <iostream> using boost::asio::ip::tcp; int main() { try { boost::asio::io_service io_service; tcp::socket socket(io_service); tcp::resolver resolver(io_service); // 连接到后端服务器 boost::asio::connect(socket, resolver.resolve({"127.0.0.1", "8080"})); // 向后端发送消息 std::string message = "Hello from frontend!"; boost::asio::write(socket, boost::asio::buffer(message)); // 异步等待后端响应 char reply[1024]; socket.async_read_some(boost::asio::buffer(reply, 1024), [&](const boost::system::error_code& error, std::size_t bytes_transferred) { if (!error) { std::cout << "Received reply from backend: " << reply << std::endl; } }); // 启动事件循环 io_service.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } return 0; } ``` 后端代码: ```cpp #include <boost/asio.hpp> #include <iostream> using boost::asio::ip::tcp; int main() { try { boost::asio::io_service io_service; tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 8080)); tcp::socket socket(io_service); // 异步等待前端连接 acceptor.async_accept(socket, [&](const boost::system::error_code& error) { if (!error) { std::cout << "Frontend connected!" << std::endl; // 异步等待前端发送消息 char message[1024]; socket.async_read_some(boost::asio::buffer(message, 1024), [&](const boost::system::error_code& error, std::size_t bytes_transferred) { if (!error) { std::cout << "Received message from frontend: " << message << std::endl; // 向前端发送响应 std::string reply = "Hello from backend!"; boost::asio::write(socket, boost::asio::buffer(reply)); } }); } }); // 启动事件循环 io_service.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } return 0; } ``` 需要注意的是,这里使用了 lambda 表达式来定义回调函数。在实际使用中,可以使用函数指针或者函数对象来替代 lambda 表达式。 另外需要注意的是,这里的前端和后端都是运行在同一台机器上的示例程序。如果需要在不同的机器上运行,需要将 IP 地址和端口号修改为正确的值。 希望这个回答能够对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值