深度解析:同步与异步、阻塞与非阻塞,以及select、poll、epoll在Linux中的IO复用技术
同步与异步,阻塞与非阻塞的区别,以及select,poll和epoll
异步的概念与同步相关。
(1)执行同步调用时,调用者必须等待返回消息(结果)通知才能继续后续执行。
(2)执行异步过程调用时,调用者无法获取。
立即发送返回消息(结果)。
当调用完成时,实际处理调用的组件通过状态、通知和回调通知调用者。
这里我们提到执行组件和调用者通过三个渠道返回结果:状态、通知和回调。
使用的通知机制取决于执行组件的实现,并且不受调用者的控制,除非执行组件提供多种选择。
(A)阻塞调用是指在调用结果返回之前,当前线程暂停,等待消息通知,无法做任何其他事情。
(B)非-blockingcall表示函数在立即返回之前可以立即获得结果,而不会阻塞当前线程。
返回
场景类比:比如我去银行办理业务,可能有两种方式:
在上面的场景中,如果:a)I如果选择了,它已排队(同步),并且排队时不执行任何操作(线程已暂停并且无法执行任何操作)。
这就是同步阻塞。
模型b)如果你选择排队(同步),但在队列中做一些与银行无关的事情,比如吸烟(线程不会被挂起,也可以做其他事情),阻塞模型;c)如果您选择领取小票,请在座位上等待,直到拨打您的电话号码后再入座。
在该位置不取任何操作(线程已暂停并且无法执行任何操作)。
这是一个异步阻塞模型。
d)一旦您选择了该票,您就坐在那里等待您的号码被叫(通知)。
,但你仍然会坐在那里打电话讨论业务(线程不会暂停,仍然可以做其他工作)。
这是一个异步非阻塞模型。
这四种模型总结:1:同步阻塞模型效率最低。
换句话说,专注于排队,不做其他事情。
2:异步阻塞,效率也很低。
也就是说,您持有该号码并等待被呼叫(通知),但什么也不做。
3:同步非阻塞,既然是相关的,那么实际中效率不是很高。
来回切换线程。
换句话说,你可以在排队时打电话或抽烟,但你也必须时不时地在队伍中走动。
程序必须在两个任务之间来回切换,等待和调用,系统开销可想而知。
4:异步非阻塞,非常高效。
您可以拿着收据坐下来,等待我们拨打您的电话号码,同时我们会通过电话讨论您的业务。
Linux的一些基本概念1:用户控制和内核空间。
所有现代操作系统都使用虚拟内存。
在32位操作系统中,寻址空间(虚拟存储空间)为4G(2的32次方)。
为了让用户进程能够直接操作内核并保证内核的安全,操作系统将虚拟空间分为两部分:一部分是内核空间,另一部分是用户空间。
对于Linux操作系统来说,最高的1G字节空间分配给内核,称为内核空间,较低的3G字节空间划分为用户空间。
2:进程切换消耗资源。
为了控制进程的执行,内核必须能够暂停在CPU上运行的进程并恢复先前暂停的进程的执行。
这称为进程切换。
每次切换时,您都需要保存之前的上下文等。
也就是说,请记住流程转换会消耗资源。
3:文件描述符:文件描述符是非负整数类型。
实际上,它是一个索引,指向每个进程由内核管理的进程打开的文件的记录表。
当程序打开文件时,内核向进程返回一个非负整数文件描述符。
然而,文件描述符通常只在Unix和Linux系统上讨论。
CacheIO,大多数系统的基本IO操作,是Linux的缓存IO机制,操作系统将IO数据缓存在系统的页缓存(pagecache)中。
数据首先被复制到操作系统内核的缓冲区中,然后从操作系统内核的缓冲区复制到应用程序的地址空间中。
CachedIO的缺点:在数据传输过程中,需要跨应用程序、地址空间和内核进行多次数据复制操作。
这种数据复制操作会被锁定,因此CPU和内存消耗非常高。
LINUXIO模型网络IO的核心是读取socket。
套接字在Linux系统中被抽象为流,因此网络IO操作可以理解为对流操作。
对于IO访问,以读操作为例,数据首先被复制到操作系统内核中的缓冲区中,然后从内核缓冲区到进程的用户层。
即应用程序的地址空间。
所以当一个读操作发生时,实际上会经历两个阶段。
1:内核缓冲区中的数据就位。
2:数据从内核缓冲区复制到用户程序地址空间。
因此,对于专门针对Socketio的读操作,接下来的两个步骤是:1:等待网络。
数据包到达并被复制到内核缓冲区。
2:数据从内核缓冲区复制到用户程序的地址空间(缓冲区)。
因此,对于网络应用来说,只有两个问题。
我们将要处理的内容:网络IO和数据计算网络IO通常对延迟有更大的影响。
网络IO模型大致如下。
你熟悉吗?我们平常所说的select、poll、epoll只是同步模型中复用IO的不同实现。
下面分别介绍同步阻塞、同步非阻塞、同步IO复用。
1:同步阻塞是最简单也是最常用的网络IO模型。
Linux中的本机套接字被阻止。
如图所示,用户进程在调用recvfrom系统调用后被阻塞。
然后内核开始数据准备,这是IO的第一步。
第一阶段准备好后,内核启动第二阶段,从内核缓冲区中提取用户。
将数据复制到程序缓冲区(这可能需要一些时间)。
然后内核返回结果(准确的说是recvfrom系统调用函数返回结果),用户进程结束阻塞,重新开始执行。
总结:同步阻塞模型中,用户程序在内核运行io的两个阶段都被阻塞。
但它也有优点:可以按时返回数据,无延迟,程序模型简单。
二:同步和非阻塞同步和非阻塞是我们有时会看到的轮询方法。
同步非阻塞模式实际上可以认为是短周期的同步阻塞模式。
第三:IO复用。
同步非阻塞需要持续轮询,因此光是轮询就占了进程的很大一部分,并且消耗CPU。
资源。
并且这个用户进程不仅可以读取这个套接字,还可以对其他套接字进行读或写操作。
那么人们就想到了轮询时查询socketfd,以及轮询时查询socketfd多个任务的完成状态。
完成后处理它。
此外,轮询器并不处于进程的用户状态,但如果有人可以提供帮助,那就太好了。
这称为IO复用。
这就是Linux中众所周知的select、poll和epoll的作用。
。
。
select调用是在内核级别进行的。
select轮询与同步非阻塞模式下的轮询的区别在于,前者可以等待多个socket,同时监听多个IO端口。
当其中一个套接字数据准备好时,它会以可读状态返回。
调用select或poll后进程会阻塞。
BlockingIO与阻塞不同,此选择将特定的套接字数据返回给用户进程进行处理,而不是等待所有套接字数据到达后再进行处理。
事实上,对于单个操作来说,select的效果可能比同步非阻塞更差。
这是因为这里调用了两个系统调用select和recvfrom,而在非阻塞中只调用了一个recvfrom。
之所以使用select,是因为它可以同时处理多个socketfd。
在IO复用模型中,每个socket通常都设置为Non-Blocking,但实际中整个用户进程始终是阻塞的,只是SocketIO并不阻塞用户进程。
IO多路复用相对于多进程、多线程技术的最大优势是系统开销低。
第二:pollpoll本质上和select是一样的。
我们将用户传递的数组复制到内核空间,然后查询每个fd的状态。
,当特定fd的状态就绪时,我们将这个fd添加到等待队列中并继续遍历。
如果遍历完所有fd后发现没有一个就绪,则暂停当前进程,直到设备就绪或主动超时。
醒来后又要经过fd。
特点:1:poll是基于链表存储的,不像Sellct直接监听fd,所以没有最大连接数的限制。
2:在用户模式和内核地址空间之间全局复制相同数量的fd数组。
3:另一个版本的轮询具有水平触发功能。
如果某个fd上报后没有得到处理,则下次轮询时该fd将再次上报。
4:与select一样,即使在轮询返回后,我们也需要遍历fdset来准备好套接字。
随着fd的增加,效率线性下降。
epoll支持水平触发和边缘触发。
最大的特点是边沿触发。
它只是告诉进程这个fd刚刚准备好了。
另一个特点是epoll使用“事件”就绪通知方法通过epoll_ctl注册fd。
当fd准备好后,内核可以使用类似于callback的回调机制来激活fd并epoll_wait得到通知。
最大同时连接数没有限制,可打开的FD上限远大于1024个(1G内存约可监控10万个端口)。
效率的提升不是轮询,增加FD数量并不会降低效率。
只有活动且可用的FD才会调用回调函数。
也就是说,Epoll最大的优点是它只关心“活跃”连接,与连接总数无关。
epoll的效率远高于选择和调查的效率。
mmap()内存复制,使用文件映射内存来加速消息在内核空间的传递,即epoll使用mmap来减少复制开销。
Java分布式系统高并发万字总结!
对于网站流量较大的情况,我们需要注意同时访问的问题。并发问题往往是程序员难以面对的问题,但正因为它是不可避免的,所以我们需要面对并解决它。
本文将探讨并发和同步的常见概念和方法。
同步和异步是理解并发和同步的基本概念。
同步是指执行完一个函数或方法后,程序会阻塞等待系统返回值或消息后才继续执行。
异步允许执行函数或方法而无需等待返回值或通知。
通过委托异步流程,系统在收到返回值或通知时自动触发该流程。
在实际应用中,同步可以认为是单线程操作,而异步则允许多线程并行操作,以提高程序执行效率。
例如,吃饭和说话,同步意味着一个接一个地做,而吃饭和听音乐则是异步活动,互不影响。
在Java编程中,synchronized关键字synchronized用于保证同一时刻只有一个线程访问类中的同步方法。
如果多个线程尝试访问同一个同步方法,则其他线程可能会在前一个线程完成执行后继续执行。
这有助于防止数据争用和错误。
并发问题还与数据一致性和完整性有关。
脏数据和不可重复读是需要注意的概念。
脏数据是指在事务提交之前被另一个事务读取,这会致数据不一致。
不可重复读是指在同一个事务中多次读取相同的数据时得到不同的结果。
解决同步和并发问题往往是通过锁机制来完成的,包括代码级同步(如Java中的synchronized关键字)和引擎级锁(如悲观锁和乐观锁)。
悲观锁假设并发操作会修改数据,因此数据会在操作之前被锁定;而最优锁则基于版本控制机制,通过比较数据版本号来判断是否有更新。
在处理并发时,乐观锁和悲观锁各有优缺点。
最优锁适用于数据修改频率较低、并发操作较少的情况,因为它避免了长事务期间锁定数据库的开销。
但也可能导致脏数据问题,应充分考虑和解决在系统设计阶段就采取措施,例如在数据库存储过程中实施最优的锁定策略。
最后,对于高并发场景,还可以通过使用缓存技术(如Redis)或者创建静态页面来提高访问效率。
同时,代码优化也是减少资源浪费的关键,例如避免频繁新建对象、使用高效的数据结构等。