c++ 多个线程操作socket要同步吗_基础知识深化:NIO优化原理和Tomcat线程模型

1、I/O阻塞

书上说BIO、NIO等都属于I/O模型,但是I/O模型这个范围有点含糊,我为此走了不少弯路。我们日常开发过程中涉及到NIO模型应用,如Tomcat、Netty中等线程模型,可以直接将其视为 网络I/O模型 。本文还是在基础篇章中介绍几种I/O模型方式,后面就默认只讲解网络I/O模型了。

1.1、I/O分类

BIO、NIO、AIO等都属于I/O模型,所以它们优化的都是系统I/O的性能,因此首先,我们要清楚常见的I/O有哪些分类:

e4be0bea993f770dcdc47e67c5995fb6.png

1.2、I/O过程和性能

I/O(Input/Output)即数据的输入/输出,为什么大家很关心I/O的性能呢?因为I/O存在的范围很广,在高并发的场景下,这部分性能会被无限放大。而且与业务无关,是可以有统一解决方案的。

所有的系统I/O都分为两个阶段:等待就绪和数据操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写:

  1. 等待就绪 :等待数据就绪,一般是将数据加载到 内核缓存区 。无论是从磁盘、网络读取数据,程序能处理的都是进入内核态之后的数据,在这之前,cpu会阻塞住,等待数据进入内核态。
  2. 数据操作 :数据就绪后,一般是将内核缓存中的数据加载到 用户缓存区 。

需要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在”干活”,而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。这就出现一个奇怪的现象 -- 不使用CPU的“等待就绪”,却比实际使用CPU的“数据操作”,占用CPU时间更多 。

传统阻塞I/O模型,即在读写数据过程中会发生阻塞现象。当用户线程发出I/O请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才会解除block状态。

明确的是,让当前工作线程阻塞,等待数据就绪,是很浪费线程资源的事情,上述三种I/O都有一定的优化方案:

  • 磁盘I/O :现代电脑中都有一个DMA(Direct Memory Access 直接内存访问) 的外设组件,可以将I/O数据直接传送到主存储器中并且传输不需要CPU的参与,以此将CPU解放出来去完成其他的事情。
  • 网络I/O :NIO、AIO等I/O模型,通过向事件选择器注册I/O事件,基于就绪的事情来驱动执行I/O操作,避免的等待过程。
  • 内存I/O :内存部分没涉及到太多阻塞,优化点在于减少用户态和内核态之间的数据拷贝。nio中的零拷贝就有mmap和sendfile等实现方案。

1.3、网络I/O阻塞

这里仔细的讲讲网络I/O模型中的阻塞,即socket的阻塞。在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式,是在tcp/ip协议上,抽象出来的一层网络通讯协议。

同上面I/O的过程一样,网络I/O也同样分成两个部分:

  1. 等待网络数据到达网卡,读取到内核缓冲区。
  2. 从内核缓冲区复制数据到用户态空间。

每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区:

  • 输入缓冲区 :当使用 read()/recv() 读取数据时,(1)首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。(2)如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到有 read()/recv() 函数再次读取。(3)直到读取到数据后 read()/recv() 函数才会返回,否则就一直被阻塞。
  • 输出缓冲区 :当使用 write()/send() 发送数据时,(1)首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么 write()/send() 会被阻塞(暂停执行),直到缓冲区中的数据被发送到目标机器,腾出足够的空间,才唤醒 write()/send() 函数继续写入数据。(2) 如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,write()/send() 也会被阻塞,直到数据发送完毕缓冲区解锁,write()/send() 才会被唤醒。(3)如果要写入的数据大于缓冲区的最大长度,那么将分批写入。(4)直到所有数据被写入缓冲区 write()/send() 才能返回。

由此可见在网络I/O中,会有很多的因素导致数据的读取和写入过程出现阻塞,创建socket连接也一样。socket.accept()、socket.read()、socket.write()这类函数都是同步阻塞的,当一个连接在处理I/O的时候,系统是阻塞的,该线程当前的cpu时间片就浪费了。

2、阻塞优化

2.1、BIO、NIO、AIO

BIO、NIO、AIO对比

以socket.read()为例子:

  • 传统的BIO里面socket.read(),如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。
  • 对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。
  • 最新的AIO(Async I/O)里面会更进一步:不但等待就绪是非阻塞的,就连数据从网卡到内存的过程也是异步的。

换句话说,BIO里用户最关心“我要读”,NIO里用户最关心”我可以读了”,在AIO模型里用户更需要关注的是“读完了”。

NIO

NIO的优化体现在两个方面:

  1. 网络I/O模式 的优化,通过非阻塞的模式,提高了CPU的使用性能。
  2. 内存I/O 的优化,零拷贝等方式,让数据在内核态和用户态之前的传输消耗降低了。

NIO一个重要的特点是: socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高) 。

NIO的主要事件有几个:读就绪、写就绪、有新连接到来。

我们首先需要注册当这几个事件到来的时候所对应的处理器。然后在合适的时机告诉事件选择器:我对这个事件感兴趣。对于写操作,就是写不出去的时候对写事件感兴趣;对于读操作,就是完成连接和系统没有办法承载新读入的数据的时;对于accept,一般是服务器刚启动的时候;而对于connect,一般是connect失败需要重连或者直接异步调用connect的时候。

其次,用一个死循环选择就绪的事件,会执行系统调用 (Linux 2.6之前是select、poll,2.6之后是epoll,Windows是IOCP) ,还会阻塞的等待新事件的到来。新事件到来的时候,会在selector上注册标记位,标示可读、可写或者有连接到来。

2.2、Reactor模式

Reactor模式称之为响应器模式,通常用于 NIO 非阻塞IO的网络通信框架中。Reactor设计模式用于处理由一个或多个客户端并发传递给应用程序的的服务请求,可以理解成, Reactor模式是用来实现网络NIO的方式 。

Reactor是一种事件驱动机制,是处理并发I/O常见的一种模式,用于同步I/O,其中心思想是将所有要处理的I/O事件注册到一个中心I/O多路复用器上,同时主线程阻塞在多路复用器上,一旦有I/O事件到来或是准备就绪,多路复用器将返回并将相应I/O事件分发到对应的处理器中。

Reactor模式主要分为下面三个部分:

  1. 事件接收器Acceptor :主要负责接收请求连接,接收请求后,会将建立的连接注册到分离器中。
  2. 事件分离器Reactor :依赖于循环监听多路复用器Selector,是阻塞的,一旦监听到事件,就会将事件分发到事件处理器。(例如:监听读事件,等到内核态数据就绪后,将事件分发到Handler,Handler将数据读到用户态再做处理)
  3. 事件处理器Handler :事件处理器主要完成相关的事件处理,比如读写I/O操作。

2.3、三种Reactor模式

单线程Reactor模式

一个线程:

  • 单线程:建立连接(Acceptor)、监听accept、read、write事件(Reactor)、处理事件(Handler)都只用一个单线程。

多线程Reactor模式

一个线程 + 一个线程池:

  • 单线程:建立连接(Acceptor)和 监听accept、read、write事件(Reactor),复用一个线程。
  • 工作线程池:处理事件(Handler),由一个工作线程池来执行业务逻辑,包括数据就绪后,用户态的数据读写。

主从Reactor模式

三个线程池:

  • 主线程池:建立连接(Acceptor),并且将accept事件注册到从线程池。
  • 从线程池:监听accept、read、write事件(Reactor),包括等待数据就绪时,内核态的数据I读写。
  • 工作线程池:处理事件(Handler),由一个工作线程池来执行业务逻辑,包括数据就绪后,用户态的数据读写。

3、Tomcat线程模型

3.1、Api网络请求过程

我们先补一下基础知识,讲解后端接口的响应过程。一个http连接里,完整的网络处理过程一般分为accept、read、decode、process、encode、send这几步:

  1. accept :接收客户端的连接请求,创建socket连接(tcp三次握手,创建连接)。
  2. read :从socket读取数据,包括等待读就绪,和实际读数据。
  3. decode :解码,因为网络上的数据都是以byte的形式进行传输的,要想获取真正的请求,必定需要解码。
  4. process :业务处理,即服务端程序的业务逻辑实现。
  5. encode :编码,同理,因为网络上的数据都是以byte的形式进行传输的,也就是socket只接收byte,所以必定需要编码。
  6. send :往网络socket写回数据,包括实际写数据,和等待写就绪。

3.2、各个线程模型

在tomcat的各个版本中,所支持的线程模型也发生了一步步演变。一方面,直接将默认线程模型,从BIO变成了NIO。另一方面,在后续几个版本中,加入了对AIO和APR线程模型的支持,这里要注意,仅仅是支持,而非默认线程模型。

  • BIO :阻塞式IO,tomcat7之前默认,采用传统的java IO进行操作,该模式下每个请求都会创建一个线程,适用于并发量小的场景。
  • NIO :同步非阻塞,比传统BIO能更好的支持大并发,tomcat 8.0 后默认采用该模式。
  • AIO :异步非阻塞 (NIO2),tomcat8.0后支持。多用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂。
  • APR :tomcat 以JNI形式调用http服务器的核心动态链接库来处理文件读取或网络传输操作,需要编译安装APR库(也就是说IO操作的部分直接调用native代码实现)。

各个线程模型中,NIO是作为目前最实用的线程模型,因此也是目前Tomcat默认的线程模型,因此本文对此着重讲解。

3.3、BIO和NIO

BIO模型

在BIO模型中,主要参与的角色有: Acceptor 和 Handler工作线程池 。对应于前文中Api的请求过程,它们的分工如下:

  • Acceptor :Accepter线程专门负责建立网络连接( accept )。新连接创建后,交给Handler工作线程池处理请求。
  • Handlers :针对每个请求的连接,Handler工作线程池都会分配一个线程,执行后面的所有步骤( read、decode、process、encode、send )。

前文的知识点有铺垫, read 和 send 是面向网络I/O的,在等待读写就绪过程中,其实是CPU阻塞的。因此Handler工作线程池中的每个线程,都会因为I/O阻塞而“空等待”,造成浪费。

NIO模型

tomcat的NIO模型,相比较于BIO模型,多了个Poller角色: Acceptor 、 Poller 和 Handler工作线程池 。这三个角色是不是很熟悉,如果将Poller换成Reactor,是不是就是Reactor模型。没错,tomcat的nio模型,的确就是基于 主从Reactor模型 ,只不过将Reactor换了个名字而已。

  • Acceptor :Accepter线程专门负责建立网络连接( accept )。新连接创建后,不是直接使用Worker线程处理请求,而是先将请求发送给Poller缓冲队列。
  • Poller :在Poller中,维护了一个Selector对象,当Poller从缓冲队列中取出连接后,注册到该Selector中,阻塞等待读写就绪( read等待就绪、send等待就绪 )。
  • Handlers :遍历Selector,找出其中就绪的IO操作,并交给Worker线程处理( read内存读、decode、process、encode、send内存写 )。

对比

  • BIO模型中,一个线程对应一个请求连接的完整过程,因此tomcat服务能处理的最大连接数,和最大线程数一致。
  • NIO模型中,在一个请求连接中,对应的一个工作线程,只处理I/O读写就绪后的非阻塞过程。因此tomcat服务能处理的最大连接数,要远大于最大线程数量。

3.4、参数设置

针对于tomcat的nio模型,可以做一些参数设置。因为springboot是内嵌tomcat的,这些参数设置同样可以在properties配置文件中定义:

  • 最大线程数(server.tomcat.threads.max) :工作线程池的最大线程数,默认200。注意不是越大越好,如果线程数过大,那么CPU会花费大量的时间用于线程的切换,整体效率会降低。
  • 最小线程数(server.tomcat.threads.min-spare) :工作线程池的最小线程数,默认10。
  • 最大等待数(server.tomcat.accept-count) :当调用HTTP请求数达到tomcat的最大线程数时,还有新的HTTP请求到来,这时tomcat会将该请求放在等待队列中,这个acceptCount就是指能够接受的最大等待数,默认100。如果等待队列也被放满了,这个时候再来新的请求就会被tomcat拒绝。
  • 最大连接数(server.tomcat.max-connections) :在同一时间,tomcat能够接受的最大连接数,默认8192。

4、常见问题

1、tomcat运行后,出现 nio-8080-exec- 前缀的线程作用是什么?

是工作线程池中的线程。你们可以观察某个springboot运行项目的线程模型,由于基本都是基于nio模型的tomcat应用,因此都包括这些线程:

  • 1个名称中包含Accepter的线程。
  • 2个名称中包含Poller的线程。
  • 10个工作线程,名称从 nio-8080-exec-1 到 nio-8080-exec-10。如果并发交高,默认最多有200个线程,名称到 nio-8080-exec-200。

2、tomcat中nio模型中,存在poller单线程读取多个请求线程的数据,会不会出现线程安全问题?因为通过会使用ThreadLocal存储请求用户身份信息。

不会。因为poller只是处理等待读就绪的环节,一旦读就绪事件触发后,真正的读取数据和处理业务逻辑,都是由工作线程池中的某个线程跟到底,可以放心大胆使用ThreadLocal。

3、为什么我自己对比测试nio和bio,性能提升不大?

nio线程模型优化的是线程利用率,为了在高并发场景下,基于有限的线程资源,处理更多的请求连接。

例如:tomcat使用默认最大线程数200,但你的并发请求数量连200都不到,就算是BIO模型,线程池中200个线程都没利用完。这时候你用NIO还是BIO,区别不大,甚至BIO模型处理还更快一些。但如果你的并发请求数到了2000、20000,BIO模型就会出现性能瓶颈了,超过200的请求都会阻塞住,而NIO模型就能大展身手。

来源:https://www.tuicool.com/articles/FbAbArq

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/398337.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux常用命令汇总--ln

1.功能:将一个文件或者文件夹链接到另外一个文件或者文件夹上。链接分为硬链接和软链接,硬链接可以看做是一个文件具有多个访问的入口,软链接可以看成是快捷方式。2.用法:ln [选项] 源文件或目录 目标文件或目录3.参数&#xff1a…

用同一uuid作为两个字段的值_这两个小技巧,让SQL语句不仅躲了坑,还提升了 1000 倍...

作者:帅地个人简介:一个热爱编程的在校生,我的世界不只有coding,还有writing。目前维护订阅号「苦逼的码农」,专注于写「算法与数据结构」,「Java」,「计算机网络」。本次来讲解与 SQL 查询有关的两个小知识…

Android SQLite详解

在项目开发中,我们或多或少都会用到数据库。在Android中,我们一般使用SQLite,因为Android在android.database.sqlite包封装了很多SQLite操作的API。我自己写了一个Demo来总结SQLite的使用,托管在Github上,大家可以点击…

Catalan数的理解

Catalan数的理解 f(0)1f(1)1f(2)2f(3)5f(4)14f(5)42f(2)f(1)f(1)f(3)f(2)f(1)*f(1)*f(2)f(4)f(3)f(2)*f(1)f(1)*f(2)f(3)通项公式:f(n) f(n-1) f(n-2)f(1) f(n-3)f(2) ... f(1)f(n-2) f(n-1) 理解:固定一个,n-1个全在左边,n-…

Type interface mapper.UserMapper is not known to the MapperRegistry

Type interface mapper.UserMapper is not known to the MapperRegistry. 报错:Type interface mapper.UserMapper is not known to the MapperRegistry. 解决:已经解决;请查看mapper是否配置正确,我下面就配置错误了。 解决效果…

微信整人假红包图片_警惕:千万别点!这些红包是假的

春节将至,又到了“考验手速”的时候。近年春节,“抢红包”为大家带来了“新年味”与许多快乐。但是,有些不法分子却从中捣乱,制造了一些假红包企图骗取钱财。如何辨别“假红包”?这里总结了几种“假红包”类型&#xf…

SQL Server中SCAN 和SEEK的区别

SQL Server中SCAN 和SEEK的区别 SQL SERVER使用扫描(scan)和查找(seek)这两种算法从数据表和索引中读取数据。这两种算法构成了查询的基础,几乎无处不在。Scan会扫描并且返回整个表或整个索引。 而seek则更有效率&…

HDU 2897 (博弈 找规律) 邂逅明下

根据博弈论的两条规则&#xff1a; 一个状态是必胜状态当且仅当有一个后继是必败状态一个状态是必败状态当且仅当所有后继都是必胜状态然后很容易发现从1开始&#xff0c;前p个状态是必败状态&#xff0c;后面q个状态是必胜状态&#xff0c;然后循环往复。 1 #include <cstd…

C# 设置Excel打印选项及打印excel文档

C# 设置Excel打印选项及打印excel文档 打印Excel文档是一个很常见的操作&#xff0c;但有时候我们会碰到各种不同的打印需求&#xff0c;例如只打印一个Excel工作表的其中一部分&#xff0c;或打印时每页都有表头&#xff0c;或把工作表中超出1页所有内容打印到1页上等等&#…

解决问题:修改tomcat启动后服务器url

解决问题&#xff1a;修改tomcat启动后服务器url 解决方式&#xff1a;通过在pom.xml配置文件中使用configuration标签的子标签path可以修改http://localhost:8080后跟什么路径。如path标签中为/&#xff0c;则tomcat启动后服务器url为http://localhost:8080/

用js来实现那些数据结构 第一章

在开始正式的内容之前&#xff0c;不得不说说js中的数据类型和数据结构&#xff0c;以及一些比较容易让人混淆的概念。那么为什么要从数组说起&#xff1f;数组在js中是最常见的内存数据结构&#xff0c;数组数据结构在js中拥有很多的方法&#xff0c;很多初学者记不清数组的大…

excel密码忘记了怎么办

2019独角兽企业重金招聘Python工程师标准>>> Excel电子表格应用程序堪称Office中的“王牌应用”&#xff0c;能够快速灵活地整理各种大数据&#xff0c;在各行各业中发挥着不可替代的作用。因此&#xff0c;excel文档的跋扈密码比其他文档多&#xff0c;除了常设的打…

Smobiler 4.4 更新预告 Part 1(Smobiler能让你在Visual Studio上开发APP)

在4.4版本中&#xff0c;大家对产品优化的一些建议和意见进行了相应的优化和修复&#xff0c;同时&#xff0c;还新增了一些令人激动的功能和插件。 下面先为大家介绍4.4版本中Smobiler的优化和修复&#xff1a; 优化 1&#xff0c; PageView的AutoPlay默认属性改为True。 2&am…

cad lisp 二次抛物线_学习CAD的五个段位,你是青铜还是王者?

大家学习AutoCAD多久了&#xff0c;从入门到精(fang)通(qi)&#xff0c;小编总结了5个段位&#xff0c;大家对号入座。此时视口内的三维实体并没有任何变化&#xff0c;需要后续使用soldraw才能进行转换。SOLVIEW创建的视口经过SOLDRAW处理后&#xff0c;会生成表示实体轮廓和边…

Python 基础知识(二)

一、基础数据类型 1、数字int 数字主要是用于计算用的&#xff0c;使用方法并不是很多&#xff0c;就记住一种就可以&#xff1a; #bit_length() 当十进制用二进制表示时&#xff0c;最少使用的位数 # -*- coding:UTF-8 -*- v 11 data v.bit_length() print(data) # 二进制&a…

深入理解JavaWeb(五)——过滤器和监听器

深入理解JavaWeb&#xff08;五&#xff09;——过滤器和监听器 摘要&#xff1a;在基于Java-Web核心技术的开发应用中&#xff0c;为了一些特定的用途或目的&#xff0c;那么Filter和Listener就要闪亮登场了。在本文中我们来聊一聊Servlet规范中的另俩技术的核心知识点&#x…

ps一点等于多少厘米_劝告大家:女人40岁后,体重多少算正常?太瘦也不好

阅读本文前&#xff0c;请您先点击上面的“蓝色字体”&#xff0c;再点击“关注”&#xff0c;这样您就可以继续免费收到文章了。每天都有分享&#xff0c;完全是免费订阅&#xff0c;请放心关注。由于身高、骨骼、体型和体重的不同&#xff0c;与男性相比&#xff0c;女性将相…

java xml转map_java练习本(原每日一练)(20190514)

名人名言昨日翻译“The No. 1 reason people fail in life is because they listen to their friends, family, and neighbors.”——Napoleon Hill“人们生活中失败的第一个原因是他们听取朋友、家人和邻居的意见。”——拿破仑希尔今日名言“Your time is limited, so don’t…

? SegmentFault Hackathon 文艺复兴上海站作品集 - 获奖篇

承上回&#xff0c;在 Day 1 中&#xff0c;石墨文档、Agora.io 声网和 HPE 的技术团队针对自家的 SDK、API 等作了深度介绍&#xff0c;今天就来看看我们的开发者是如何将作品和服务关联起来的吧。 跳蚤市场 24 小时的编程&#xff0c;艺术家都将什么作品搬上自己的摊位&#…

华为手机云闪付付款码如何截图_云闪付乘车码,它带着优惠又来了

云闪付乘车码又来了&#xff01;想必这段时间大家有些(甚是)想念薅羊毛的日子想起那快乐时光嘴角总是不经意的偷偷乐不多说了&#xff0c;直接上优惠特别提醒&#xff1a;1分钱乘公交5折乘地铁......详戳▼▼▼银联信用卡1分钱乘公交活动时间&#xff1a;2019年12月6日—2019年…