一周技术思考(第37期)-如何快速的学好并发编程

本文探讨了并发编程中的线程池配置与并发用户的关系,并介绍了Promise作为异步编程模式如何提高并发性,避免线程等待。通过分析Tomcat的NIO模式,展示了其在高并发处理中的设计思想,如LimitLatch、Acceptor、Poller和Executor。学习Promise和Tomcat的设计,有助于提升程序的并发处理能力和性能。
摘要由CSDN通过智能技术生成

555a10f6a9b1eeb8d1ffad09fc720c36.png

图自网络

前言

曾经有一个人在一个 4C8G 的机器上把一个 Tomcat 节点的线程池配置到了 4000。问他为什么要这么配置,他说想支持 4000 的并发用户。

并发是什么,我们一般知道肯定跟线程有关系。

只有一个物理CPU的服务器,如果有多个线程需要执行操作,这个时候CPU就会分出多个CPU时间片,这样让多个线程快速的切换,我们也知道这种切换的速度非常得快,根本感觉不到。

在有多个物理CPU的服务器上,就会发生真正的并行操作,这个也很容易理解,比如有8个CPU的服务器,同时过来8个请求,当然它们就可以并行操作,不过如果同时请求数量再增加,那么结果也会出现上述线程切换的动作,同样速度非常得快,根本感觉不到。

知道这些还远远不够,即使要做个井底之蛙,也要让那个井口开的大些。而上面那位同学就没有学好并发,至少是没有充分理解在线用户、并发用户和TPS之间的关系。

另外我们也都不想当那只青蛙吧。

那如何办,其实,在程序应用技术发展到今天,如果要快和好的学习并发编程技能,我们完全可以先选好一种工具并学会使用它,再找一个优秀的产品来学习它。

接下来,我就先介绍工具再介绍产品。

学习使用Promise

并发的场景中要保持请求操作结果的有序执行往往会用到锁的机制,但是如果大量的请求都被堵在那把锁上,就会造成线程的频繁切换而白白的浪费掉CPU的资源。

那么我们就在想,有没有不使用锁机制,不让线程等待,用异步编程可以”换位“解决这个问题吗?其实是有的,我们这里提到的Promise实际上一种异步编程模式。

它使得我们可以先开始一个任务的执行,并得到一个用于获取该任务执行结果的凭据对象,而不必等待该任务执行完毕就可以继续执行其他操作。等到我们需要该任务的执行结果时,再调用凭据对象的相关方法来获取。这样就避免了不必要的等待,增加了系统的并发性。

Promise的高明之处在于它屏弊了异步编程的复杂性,而又能够让研发人员像同步编程那样操作异步代码。也可以说在一定程度上使用Promise模式让异步编程和同步编程没有多大差异。之所以能达到这样的效果,是因为所有相关异步的操作都被封装在了Promisor参与者实例中,而Promise的客户端代码则无须关心这些细节,这样,我们的异步编码方式与同步编程就基本上无本质上的差别了。

下面是Promise这种异步编程模式的基本结构,一共包含五种角色,分别是Client、Promisor、Result、Promise和TaskExecutor,如下图所示。

32c730381bfabe5878948091483a11d0.png

Promisor:负责对外暴露可以返回Promise对象的异步方法,并启动异步任务的执行。其主要方法及职责如下。compute:启动异步任务的执行,并返回用于获取异步任务执行结果的凭据对象。

Promise:包装异步任务处理结果的凭据对象。负责检测异步任务是否处理完毕、返回和存储异步任务处理结果。其主要方法及职责如下。getResult:获取与其所属Promise实例关联的异步任务的执行结果。setResult:设置与其所属Promise实例关联的异步任务的执行结果。isDone:检测与其所属Promise实例关联的异步任务是否执行完毕。

Result:负责表示异步任务处理结果。具体类型由应用决定。

TaskExecutor:负责真正执行异步任务所代表的计算,并将其计算结果设置到相应的Promise实例。其主要方法及职责如下run:执行异步任务所代表的计算。

向Tomcat学习

如果你想站在巨人的肩膀上来学习一些高并发编程的话,那么Tomcat无疑是巨人中的佼佼者。随着学习的深入,你会发现Tomcat用到了很多Java的高级技术,比如Java多线程编程,socket网络编程等等,而我们要想设计一个高性能的程序,你将不得不考虑到如何高效的利用好CPU、网络,当然还有内存以及磁盘。

我们一起来看Tomcat中在NIO模式下的整体结构,如下图所示。

44099a00d9580cd2f8cac42eca872083.png

LimitLatch 是连接控制器,它负责控制最大连接数,NIO 模式下默认是 10000,达到这个阈值后,连接请求被拒绝。

Acceptor 跑在一个单独的线程里,它在一个死循环里调用 accept 方法来接收新连接,一旦有新的连接请求到来,accept 方法返回一个 Channel 对象,接着把 Channel 对象交给 Poller 去处理。

Poller 的本质是一个 Selector,也跑在单独线程里。Poller 在内部维护一个 Channel 数组,它在一个死循环里不断检测 Channel 的数据就绪状态,一旦有 Channel 可读,就生成一个 SocketProcessor 任务对象扔给 Executor 去处理。

Executor 就是线程池,负责运行 SocketProcessor 任务类,SocketProcessor 的 run 方法会调用 Http11Processor 来读取和解析请求数据。我们知道,Http11Processor 是应用层协议的封装,它会调用容器获得响应,再把响应通过 Channel 写出。

你会发现,我们自己的应用程序结构跟它是如此的相似,比如我们有Nginx做代理,然后转发到下游服务器,在服务器中我们的应用程序开启多个线程池处理用户从前台发过来的请求,然后将请求的操作结果再返回给前台。因为这套流程本质上其实Tomcat自己就在做。

总结

学好并发编程很难吗,实际上是一件较为困难的事情,这中间会涉及不少操作系统层面的知识,诸如CPU、内存、网络等等知识点。这里面如果你欠缺哪一块知识,都不能够很顺畅的把并发的内容贯穿起来。

如何高效的利用CPU、内存、网络等服务器硬件资源,是我们编写高性能程序时候的必修课。我们总是想让我们的程序能够在短时间内处理大量的请求,这期间就总会遇到并发的问题。

我们要尽量的减少线程的阻塞,从而减少线程的上线文切换,因为这个切换太耗费CPU资源了,所有我们可以使用异步编程模型比如Promise来实现无锁的并发编程。Tomcat中的设计用到了大量的高性能、高并发的设计,这些都是我们学习的参照范本。也就是我们文章中说的站在这些巨人的肩膀上来学习。

巨人已经给我们准备好了I/O 和线程模型、减少系统调用、池化、零拷贝、高效的并发编程的实践案例,拿来主义,多多参研。

-------

提前祝大家1024节日快乐。

正值双十一大促,程序员应该学会关爱自己。记得以前冯唐写过一篇文章《如何避免成为一个油腻的中年猥琐男》,文中提到了八个方法,大家可以搜索一下来读读。这第八个方法就是不要停止购物。

第八,不要停止购物。

不要环顾四周,很冲动地说,断舍离,太多衣服了,车也有了,冰箱里吃的吃不完,实在没什么想买的东西了。完全没了欲望,失去对美好事物的贪心,生命也就没有乐趣。一个老麦肯锡,八十多岁了还在教麦肯锡年轻的项目经理如何管理自己、管理团队、管理事情。他偷偷告诉我保持年轻的诀窍,不能常换年轻女友,一定要常买最新的电子产品,比如最新的电脑、最新的手机、最新版的《VR女友》。

这里记录,我每周读到的技术书籍、专栏、文章以及遇到的工作上的技术经历的思考,不见得都对,但开始思考总是好的。 

参考资料:

李号双.《Tomcat和Jetty的高性能、高并发之道》

https://time.geekbang.org/column/article/103660?utm_term=pc_interstitial_1267

Promise模式 黄文海.《Java多线程编程实战指南(设计模式篇)》Java多线程编程模式实战指南:https://zhuanlan.zhihu.com/p/30688216

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值