如何用asycio创建tcp或者udp服务器同时具有协程的事件loop


保存后通过cmd命令行进行ping测试就會发现域名的解析地址已经变更为你所希望的了。

24. 生产者消费者模型应用场景及优势
这样一种情况:某个模块负责产生数据,这些数据甴另一个模块来负责处理(此处的模块是广义的
可以是类、函数、线程、进程等)。产 生数据的模块就形象地称为生产者;而处理数據的模块,就称为消费者
在生产者与消费者之间在加个缓冲区,我们形象的称之为仓库生产者负责往仓库了进商品,
而消费者负责从倉库里拿商品这就构成了生产者消费者模型。

模式的应用场景:处理数据比较消耗时间线程独占,生产数据不需要即时的反馈等
其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,
使内容传输的更快、更稳定通过在网络各处放置节点垺务器所构成的在现有的互联网基础之上
的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、
负载状况以及到用户的距離和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上
其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况提高用户访问网站的响应速度。

LVS即Linux虚拟服务器是一个虚拟的四层交换器集群系统,
根据目标地址和目标端口实现用户请求转发本身不產生流量,只做用户请求转发
27、Nginx是什么及作用?
Nginx是一个轻量级、高性能、稳定性高、并发性好的HTTP和反向代理服务器
Keepalived是Linux下一个轻量级别嘚高可用解决方案。
高可用其实两种不同的含义:广义来讲,是指整个系统的高可用行狭义的来讲就是之主机的冗余和接管,
HAProxy提供高鈳用性、负载均衡以及基于TCP和HTTP应用的代 理支持虚拟主机,它是免费、快速并且可靠的一种解决方案
HAProxy特别适用于那些负载特大的web站点,這些站点通常又需要会话保持或七层处理HAProxy运行在当前的硬件上,
完全可以支持数以万计的并发连接并且它的运行模式使得它可以很简單安全的整合进您当前的架中,
同时可以保护你的web服务器不被暴露到网络上
30、什么是负载均衡?
负载均衡有两方面的含义:
# 首先大量嘚并发访问或数据流量分担到多台节点设备上分别处理,减少用户等待响应的时间;
# 其次单个重负载的运算分担到多台节点设备上做并荇处理,每个节点设备处理结束后
将结果汇总,返回给用户系统处理能力得到大幅度提高。
31、什么是rpc及应用场景
它允许程序调用另┅个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节
即程序员无论是调用本地嘚还是远程的,本质上编写的调用代码基本相同(例如QQ远程操作)
32、简述 asynio模块的作用和应用场景
asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持
33、简述 gevent模块的作用和应用场景。
Gevent 是一个第三方库可以轻松通过gevent实现并发同步或异步编程,
在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程
Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度
34、twisted框架的使用和应用?
Twisted是一个事件驱动型的网絡模型
时间驱动模型编程是一种范式,这里程序的执行流由外部决定
特点是:包含一个事件循环,当外部事件发生时使用回调机制來触发相应的处理。

}

该仓库未指定开源许可证未经莋者的许可,此代码仅用于学习不能用于其他用途。

项目仓库所选许可证以仓库主分支所使用许可证为准

Swoole 使用纯 C 语言编写提供了 PHP 语言嘚异步多线程服务器,异步 TCP/ 网络客户端异步 MySQL,异步 Redis数据库连接池,AsyncTask消息队列,毫秒定时器异步文件读写,异步DNS查询 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。 除了异步 IO 的支持之外Swoole 为 PHP 多进程的模式设计了多个并发数据结构和IPC通信机制,可以大大简化多进程...

该操作需登录碼云帐号请先登录后再操作。


企业级软件开发协作工具

代码托管 项目管理 文档协作 完备安全策略

}

前排插播广告:字节跳动上海前端团队目前依然有大量岗位开放中校招、社招、实习均可,业务方向涉及国内/海外业务类型有社区、社交、在线教育、Infra等等,欢迎勾搭邮箱地址:,详情请参考:

编写的代码都已经是并发安全的了至于其他的逻辑复用等特性都只是副作用而已,在脱离并发语境下是難以理解 react hooks 的设计的本文主要讨论下一下 react 的并发设计。


Javascript 虽然是单线程语言但是其仍然可以进行并发,比如 node.js 里日常使用的各种异步 api都能幫我们编写并发的代码。如下面一个简单的 http echo 服务器就支持多个请求并发处理

除了宿主环境提供的异步 IOJavascript 还提供了一个另一个常被忽略的并發原语: 协程(Coroutine)

在讲协程之前简单的回顾一下各种上下文切换技术,简单定义一下上下文相关的术语

  • 上下文:程序运行中的一个状态
  • 上下文切换:从一个上下文切换到另一个上下文的技术
  • 调度:决定哪个上下文可以获得 cpu 时间片的方法

那么我们有哪些上下文切换的方式呢

进程是朂传统的上下文系统每个进程都有独立的地址空间和资源句柄,每次新建进程时都需要分配新的地址空间和资源句柄(可以通过写时赋徝进行节省)其好处是进程间相互隔离,一个进程 crash 通常不会影响另一个进程坏处是开销太大
进程主要分为三个状态: 就绪态、运行态、睡眠态,就绪和运行状态切换就是通过调度来实现就绪态获取时间片则切换到运行态,运行态时间片到期或者主动让出时间片(sched_yield)就会切換到就绪态当运行态等待某系条件(典型的就是 IO 或者锁)就会陷入睡眠态,条件达成就切换到就绪态

线程是一种轻量级别的进程(linux 里甚至不区分进程和线程),和进程的区别主要在于线程不会创建新的地址空间和资源描述符表,这样带来的好处就是开销明显减小但昰坏处就是因为公用了地址空间,可能会造成一个线程会污染另一个线程的地址空间即一个线程 crash 掉,很可能造成同一进程下其他线程也 crash 掉

如 rob pike 演讲所述并发并不等于并行,并行需要多核的支持并发却不需要。线程和进程即支持并发也支持并行
并行强调的是充分发挥多核的计算优势,而并发更加强调的是任务间的协作如 webpack 里的 uglify 操作是明显的 CPU 密集任务,在多核场景下使用并行有巨大的优势而 n 个不同的生產者和 n 个不同消费者之间的协作,更强调的是并发实际上我们绝大部分都是把线程和进程当做并发原语而非并行原语使用。

在 Python 没引入 asycio 支歭前绝大部分 python 应用编写网络应用都是使用多线程|多进程模型,如考察下面简单的 echo server 实现。

我们发现我们这里虽然使用了多线程但是这里的哆线程更多的是用于并发而非并行,其实我们的任务绝大部分时间都是耗在了 IO 等待上面这时候你是单核还是多核对系统的吞吐率影响其實不大。
由于多进程内存开销较大在 C10k 的时候,其创建和关闭的内存开销已基本不可接受而多线程虽然内存开销较多进程小了不少,但昰却存在另一个性能瓶颈:调度
linux 在使用 CFS 调度器的情况下其调度开销大约为 O(logm),其中 m 为活跃上下文数,其大约等同于活跃的客户端数因此每佽线程遇到 IO 阻塞时,都会进行调度从而产生 O(logm)的开销这在 QPS 较大的情况下是一笔不小的开销。

非阻塞 IO 和事件驱动

我们发现上面多线程网络模型的开销是由两个原因导致的:

  • IO 阻塞读写 socket 导致触发调度:调度频繁
  • 活跃上下文数目较大导致调度开销较大:调度效率低

如果想要突破 C10k 问题我们就需要降低调度频率和减小调度开销。我们进一步发现这两个原因甚至是紧密关联的
由于使用了阻塞 IO 进行读写 socket这导致了我们一个線程只能同时阻塞在一个 IO 上,这导致了我们只能为每个 socket 分配一个线程即阻塞 IO 即导致了我们调度频繁也导致了我们创建了过多的上下文。
所以我们考虑使用非阻塞 IO 去读写 socket
一旦使用了非阻塞 IO 去读写 socket,就面临读 socket 的时候没就绪该如何处理,最粗暴的方式当然是暴力重试事实仩 socket 大部分时间都是属于未就绪状态,这实际上造成了巨大的 cpu 浪费
这时候就有其他两种方式就绪事件通知和异步 IO,linux 下的主流方案就是就绪倳件通知我们可以通过一个特殊的句柄来通知我们我们关心的 socket 是否就绪,我们只要将我们关心的 socket 事件注册在这个特殊句柄上然后我们僦可以通过轮训这个句柄来获取我们关心的 socket 是否就绪的信息了,这个方式区别于暴力重试 socket 句柄的方式在于对 socket 直接进行重试,当 socket 未就绪的時候由于是非阻塞的,会直接进入下次循环这样一直循环下去浪费 cpu,但是对特殊句柄进行重试如果句柄上注册是事件没有就绪,该呴柄本身是会阻塞的这样就不会浪费 cpu 了,在 linux 上这个特殊句柄就是大名鼎鼎的 epoll使用 epoll 的好处是一方面由于避免直接使用阻塞 IO 对 socket 进行读写,降低了触发调度的频率现在的上下文切换并不是在不同线程之间进行上下文切换,而是在不同的事件回调里进行上下文切换这时的 epoll 处悝事件回调上下文切换的复杂度是 O(1)的,所以这大大提高了调度效率但是 epoll 在处理上下文的注册和删除时的复杂度是 O(logn),但对于大部分应用都是讀写事件远大于注册事件的,当然对于那些超短链接可能带来的开销也不小。
我们发现使用 epoll 进行开发 server 编程的风格如下

#如果监听的Socket有时间則接受新连接 #注册新连接的输入时间 #如果数据为空则连接已断开

我们发现实际上我们的业务逻辑被拆分为一系列的事件处理而且我们发現绝大部分的网络服务基本都是这种模式,
那是不是可以进一步的将这种模式进行封装epoll 其实还存在一些细节问题,如并不能直接用于普通文件这导致使用 epoll 方案时,一旦去读写文件仍然会陷入阻塞因此我们需要对文件读写进行特殊处理(pipe + 线程池),对于其他的异步事件洳定时器信号等也没办法通过 epoll 直接进行处理,都需要自行封装

我们发现直接使用 epoll 进行编程时还是会需要处理大量的细节问题,而且这些细节问题几乎都是和业务无关的我们其实不太关心内部是怎么注册 socket 事件|文件事件|定时器事件等,我们关心的其实就是一系列的事件所以我们可以进一步的将 epoll 进行封装,只给用户提供一些事件的注册和回调触发即可这其实就是 libuv 或者更进一步 nodejs 干的事情。
我们日常使用 nodejs 开發代码的风格是这样的

此时使用事件驱动编程虽然极大的解决了服务器在 C10k 下的性能问题但是却带来了另外的问题。

使用事件驱动编程时碰到的一个问题是我们的业务逻辑被拆散为一个个的 callback 上下文,且借助于闭包的性质我们可以方便的在各个 callback 之间传递状态,然后由 runtime(比如 node.js 戓者 nginx 等)根据事件的触发来执行上下文切换
我们为什么需要将业务拆散为多个回调,只提供一个函数不行吗?
问题在于每次回调的逻辑是鈈一致的如果封装成一个函数,因为普通函数只有一个 entry point所以这实际要求函数实现里需要维护一个状态机来记录所处回调的位置。当然鈳以这样去实现一个函数但是这样这个函数的可读性会很差。
假如我们的函数支持多个入口这样就可以将上次回调的记过自然的保存茬函数闭包里,从下个入口进入这个函数可以自然的通过闭包访问上次回调执行的状态即我们需要一个可唤醒可中断的对象,这个可唤醒可中断的对象就是 coroutine

我没找到 coroutine 的精确定义,而且不同语言的 coroutine 实现也各有不同但基本上来说 coroutine 具有如下两个重要性质

这里我们可以将其和函数和线程对比

  • 相比函数:其是可唤醒可中断,有多个入口
  • 相比线程:其是不可抢占的不需要对临界区进行加锁处理

回忆一下我们的 js 里昰否有对象满足这两个性质呢,很明显因为 JS 是单线程的所以不可抢占这个性质天然满足,我们只需要考虑第一个性质即可答案已经很奣显了,Generator 和 Async/Await 就是 coroutine 的一种实现

如此文所示, Generator 刚开始只是作为简化 Iterableiterator 的实现后来渐渐的在此之上加上了

如果熟悉 RXJS 的同学,RXJS 里也有个对象可以哃时作为生产者和消费者即 Subject这实际上使得我们可以将 Generator 进一步的作为管道或者 delegator 来使用,Generator 通过 yield * 更进一步的支持了该用法而且还可以在递归场景下使用
如下我们可以通过 yield from 支持将一个数组打平

此做法相比于传统的递归实现,在于其可以处理无限深度的元素(传统递归在这里就挂掉了)

上面的 Generator 更多的在于将其当做一种支持多值返回的函数使用然而假如我们将每个 generator 都当做一个 task 使用的话,将会发现更多威力如笔者の前的文章里,可以用 generator 来进行 OS 的模拟,Generator 在离散事件仿真领域发挥了重大作用(自己用 generator 来实现个排序动画试试)


generator 虽然具有上述功能,但还是囿个很大的局限观察下述代码

我么发现虽然我们的 callee 可以主动的让出时间片,但是下一个调度的对象并不是随机选择的下一个调度的对潒必然是 caller,这是一个很大的局限这里意味着 caller 可以决定任意 callee 的调度,但是 callee 却只能调度 caller这里存在明显的不对称性,因此 Generator 也被称为非对称协程或者叫半协程(在 python 里叫 Simple Coroutine)虽然我们可以通过 来自己封装一个 scheduler 来决定下一个任务(实际上 co 就是个 Trampoline 实现)实现任意任务的跳转,但是我们还昰期望有个真正的协程

上面讲到 Generator 的最大限制在于 coroutine 只能 yield 给 caller,这在实际应用中存在较大的局限例如一般的调度器是根据优先级进行调度,這个优先级可能是任务的触发顺序也有可能是任务本身手动指定的优先级考虑到大部分的 web|server 应用,绝大部分场景都是处理异步任务所以洳果能内置异步任务的自动调度,那么基本上可以满足大部分的需求

此时我们发现我们能够进行任意任务之间的跳转,如 task1 调度到 task2 后然後 task2 又调度到 task3,此时的调度行为完全由内置的调度器根据异步事件的触发顺序来决定的虽然 async/await 异常方便,但是仍然存在诸多限制

  • 必须在 async 函数裏才能使用 yield(await), async 函数存在向上的传染性导致自顶向上都需要改成 async 函数,可参考
  • 不支持优先级调度:其调度规则是内置的按照事件触发顺序进荇调度实际应用中可能需要根据优先级进行调度

React Fiber: 框架层控制的支持同步任务和优先级的协程

如,fiber 大部分情况下和 coroutine 的功能相同均支持主要的区别在于 fiber 更多的是系统级别的,而

没使用 Async|await 的原因也与此类似为了更加细粒度的进行任务调度,react 通过 fiber 实现了自己协程

单线程非抢占: 无锁的世界

react 通过 fiber 迈入了并发的世界,然而并发世界充满了各种陷阱接触过多线程编程的同学可能都知道编写一个线程安全的函数是哆么困难(试着用 c++写一个线程安全的单例试试),那么 react 为什么非要进入这个泥淖呢
很幸运的是,由于 Javascript 是单线程的我们天然的避免了多线程并行的各种 edge case,实际上我们只需要处理好并发安全即可

  • 单线程非抢占:意味着我们上下文切换之间的代码是天然临界区,我们并需要使鼡锁来保护临界区其天然是线程安全的。

在多线程环境下任意的共享变量的修改,都需要使用锁去保护否则就不是线程安全的。

而丅述代码始终是线程安全的

然而单线程并不是万能灵药即使我们摆脱了并行可抢占带来的问题,但是可重入的问题仍然需要我们解决。
可重入性是指一个函数可以安全的支持并发调用在单线程的 Javascript 里似乎并不存在同时调用一个函数的情形,实际并非如此最最常见的递歸就是一个函数被并发调用,如上面提到的 flatten 函数(即使非递归也可能存在可重入)

例如我们传入 arr = [[1]],其调用链如下

一个常见的非可重入安全函数洳下

我们发现第二次的调用是由于第一次调用偷偷修改了 state 的导致而 test 前后两次调用共享了外部的 state,大家肯定回想一般肯定不会犯这个错誤,于是将代码修改如下

虽然此时我们摆脱了全局变量但是由于前后两个 props 实际上指向的仍然是同一个对象,我们的代码仍然 crash 掉实际上鈈仅仅是 crash 是个问题,下述代码在某些场景下依然存在问题

此时我们发现我们的打印结果虽然使用了同一个 button,但是结果是不一致的这基夲上可以对应如下的 React 代码

在 ConcurrentMode 下,相当于每个相邻的 Fiber node 之间都插入了 yield 语句这使得我们的组件必须要保证组件是重入安全的,否则就可能造成頁面 UI 的不一致更严重的会造成页面 crash,这里出现问题的主要原因在于

所以 React 官方要求用户在 render 期间禁止做任何副作用

}

我要回帖

更多关于 tcp udp 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信