首页 > 其他分享 >常见网络模型/线程模型

常见网络模型/线程模型

时间:2023-02-04 16:57:27浏览次数:53  
标签:reactor 模型 常见 用户 handler 线程 多线程 event

1.连接独占线程或进程

在这个模型中,线程/进程处理来自绑定连接的消息,在连接断开前不退也不做其他事情。当连接数逐渐增多时,线程/进程占用的资源和上下文切换成本会越来越大,性能很差,这就是C10K问题的来源。这种方法常见于早期的web server,现在很少使用。

2.单线程reactor

以libevent, libev等event-loop库和redis网络模型为典型。

这个模型一般由一个event dispatcher等待各类事件,待事件发生后原地调用对应的event handler,全部调用完后等待更多事件,故为"loop"。这个模型的实质是把多段逻辑按事件触发顺序交织在一个系统线程中。

一个event-loop只能使用一个核,故此类程序要么是IO-bound,要么是每个handler有确定的较短的运行时间(比如http server),否则一个耗时漫长的回调就会卡住整个程序,产生高延时(比如Redis的big key遍历)。在实践中这类程序不适合多开发者参与,一个人写了阻塞代码可能就会拖慢其他代码的响应。由于event handler不会同时运行,不太会产生复杂的race condition,一些代码不需要锁。此类程序主要靠部署更多进程增加扩展性(redis的集群切片就是多redis进程)

单线程reactor的运行方式及问题如下图所示:

3.N:1线程库

以GNU Pth, libco协程库等为典型,一般是把N个用户线程映射入一个系统线程。同时只运行一个用户线程,调用阻塞函数(协程yield)时才会切换至其他用户线程。N:1线程库与单线程reactor在能力上等价,但事件回调被替换为了上下文(栈,寄存器,signals)运行回调变成了跳转至上下文。和event loop库一样,单个N:1线程库无法充分发挥多核性能,只适合一些特定的程序。只有一个系统线程对CPU cache较为友好,加上舍弃对signal mask的支持的话,用户线程间的上下文切换可以很快(100~200ns)。N:1线程库的性能一般和event loop库差不多,扩展性也主要靠多进程。

4.多线程reactor

以boost::asio为典型。一般由一个或多个线程分别运行event dispatcher,待事件发生后把event handler交给一个worker线程执行。 这个模型是单线程reactor的自然扩展,可以利用多核。由于共用地址空间使得线程间交互变得廉价,worker thread间一般会更及时地均衡负载,而多进程一般依赖更前端的服务来分割流量,一个设计良好的多线程reactor程序(memcached多线程)往往能比同一台机器上的多个单线程reactor进程(redis多进程集群分片)更均匀地使用不同核心。不过由于cache一致性的限制,多线程reactor并不能获得线性于核心数的性能,在特定的场景中,粗糙的多线程reactor实现跑在24核上甚至没有精致的单线程reactor实现跑在1个核上快。由于多线程reactor包含多个worker线程,单个event handler阻塞未必会延缓其他handler,所以event handler未必得非阻塞,除非所有的worker线程都被阻塞才会影响到整体进展。事实上,大部分RPC框架都使用了这个模型,且回调中常有阻塞部分,比如同步等待访问下游的RPC返回。

5.M:N线程库

即把M个用户线程映射入N个系统线程。M:N线程库可以决定一段代码何时开始在哪运行,并何时结束,相比多线程reactor在调度上具备更多的灵活度。

但实现全功能的M:N线程库是困难的,它一直是个活跃的研究话题。我们这里说的M:N线程库特别针对编写网络服务,在这一前提下一些需求可以简化,比如没有时间片抢占,没有(完备的)优先级等。

M:N线程库可以在用户态也可以在内核中实现,用户态的实现以新语言为主,比如goroutine,这些语言可以围绕线程库设计全新的关键字并拦截所有相关的API。

相比N:1线程库,M:N线程库在使用上更类似于系统线程,需要用锁或消息传递保证代码的线程安全。

多核扩展性

理论上代码都写成事件驱动型能最大化reactor模型的能力,但实际由于编码难度和可维护性,用户的使用方式大都是混合的:回调中往往会发起同步操作,阻塞住worker线程使其无法处理其他请求。

一个请求往往要经过几十个服务,线程把大量时间花在了等待下游请求上,用户得开几百个线程以维持足够的吞吐,这造成了高强度的调度开销,并降低了TLS相关代码的效率

任务的分发大都是使用全局mutex + condition保护的队列,当所有线程都在争抢时,效率显然好不到哪去。

更好的办法也许是使用更多的任务队列,并调整调度算法以减少全局竞争。比如每个系统线程有独立的runqueue,由scheduler把用户线程分发到不同的runqueue(比如轮流分配),每个系统线程优先运行自己runqueue中的用户线程,然后再考虑其他线程的runqueue。这当然更复杂,但比全局mutex + condition有更好的扩展性。这种结构也更容易支持NUMA。

异步编程

异步编程中的流程控制对于专家也充满了陷阱。

任何挂起操作,如sleep一会儿或等待某事完成,都意味着用户需要显式地保存状态,并在回调函数中恢复状态。

异步代码往往得写成状态机的形式。(比如使用libco协程需要控制yield和resume的时机和状态)

另外如果唤醒可由多种事件触发(比如fd有数据或超时了),yield和resume的过程容易出现race condition,对多线程编码能力要求很高。

共享指针在异步编程中很普遍,这看似方便,但也使内存的ownership变得难以捉摸,如果内存泄漏了,很难定位哪里没有释放;如果segment fault了,也不知道哪里多释放了一下。

大量使用引用计数的用户代码很难控制代码质量,容易长期在内存问题上耗费时间。如果引用计数还需要手动维护,保持质量就更难了,维护者也不会愿意改进。

没有上下文会使得RAII无法充分发挥作用, 有时需要在callback之外lock,callback之内unlock,实践中很容易出错。

相关阅读

brpc threading_overview

标签:reactor,模型,常见,用户,handler,线程,多线程,event
From: https://www.cnblogs.com/lygin/p/17091869.html

相关文章

  • Java基于枚举类的线程池
    线程池定义(可防序列化攻击)packagecom.yang.utils;importjava.util.concurrent.ArrayBlockingQueue;importjava.util.concurrent.ThreadPoolExecutor;importjava......
  • ThinkPHP6.0 模型搜索器的使用
    搜索器用于封装查询条件表达式,必须在模型中定义,只有使用模型操作数据时才能用搜索器。调用搜索器时使用的是数据表字段,可以不用定义搜索器方法,默认是=条件;如果不是数据......
  • 热点面试题:常见的http code 及含义?
    前言极度投入,深度沉浸,边界清晰前端小菜鸡一枚,分享的文章纯属个人见解,若有不正确或可待讨论点可随意评论,与各位同学一起学习~欢迎关注​​『前端进阶圈』​​公众号,一起探......
  • 【算法】二分法 ① ( 二分法基本原理简介 | 二分法与哈希表对比 | 常见算法对应的时间
    文章目录​​一、二分法基本原理简介​​​​1、二分法与哈希表对比​​​​2、二分法具体步骤​​​​二、常见算法对应的时间复杂度​​一、二分法基本原理简介二分法算......
  • Qt 中多线程的使用
    前言在进行桌面应用程序开发的时候,假设应用程序在某些情况下需要处理比较复杂的逻辑,如果只有一个线程去处理,就会导致窗口卡顿,无法处理用户的相关操作。这种情况下就需要使......
  • 解读测试能力素质模型
    软件测试的能力素质模型(JobModel),是对不同层级测试工程的能力要求进行明确的定义。目的是为了对每位工程师的能力进行科学的评估,然后分配合理的工作,也帮助大家明确职......
  • AspNet Core两种托管模型
    AspNetCore两种托管模型ASPNETCore运行机制......
  • 常见端口号
    1、Hadoop目录Hadoop3.x访问HDFS端口50070访问MR执行情况端口8088历史服务器19888客户端访问集群端口90002、Hive10002:hiveservice2服务......
  • 8个你可能不知道答案的常见JavaScript面试问题
    不管你喜不喜欢,棘手的问题仍然会被野外的面试官问到。 原因是,这些问题可以告诉你很多关于你对语言的核心理解,因此你是否适合这份工作。这些问题中涉及的常见概念包括......
  • 数字三角形模型
    AcWing1015.摘花生理解属于数字三角形问题中最简单的一种,即给出矩阵然后计算从起始点到最终点(最大/最小)价值此题是计算最大值,只需把f数组初始化为0,然后根据最后一步是......