2020年10月6日星期二

关于多线程的一点小小感悟(2020)

理论知识

构件

  中间件是一个软件集合的名字,这些软件位于操作系统和高层次分布式编程平台之间。中间件有时被分为面向消息的中间件和面向对象的中间件。而然现有的大多数中间件都是这两种类型的混合体。当然,现在也有一种趋势是由传统的操作系统直接支持。操作系统总是包含了对通信协议的支持。WEB服务的推进和程序世界从以城市为中心到以协议为中心的转变,导致两种中间件的价值观:支持合适的协议或者提供简化本地服务构造的结构。

  独立的中间件产品,如消息队列系统、事务处理监控系统或者集线器,已经慢慢地消失了。取而代之的是结合了中间件功能和某个特定构件框架的特殊的服务器。应用服务器结合了应用管理、数据事务、负载均衡和其他的功能。集成服务器结合了协议转换、数据变换、路由和其他功能。工作流和复杂交互服务器结合了事件路由、决策和其他功能。

 

异步问题

  当前的构建互连标准大多使用某种形式的事件传播机制作为实现构件实例装配的手段。其思想是相对简单的:构件实例在被期望监听的状态发生变化时发布出特定的事件对象;事件分发机制负责接收这些事件对象,并把它们发送给对其感兴趣的其他构件实例;构件实例则需要对它们感兴趣的事件进行注册,因为它们可能需根据事件对象所标志的变化改变其自身的状态。

多线程

  "多线程会使你寝食难安。"Swaine在后来的著作中解释,他的一些与此相似的论断具有明显的煽动性,但是他并不认为这些论断是错误的。多线程是指在同一个状态空间内支持并发地进行多个顺序活动的概念。相对于顺序编程,多线程的引入为编程带来了相当大的复杂性。特别是,需要避免对多个线程共享的变量进行并发的读写操作。线程的同步使用某种形式的加锁机制来解决此类问题,但这又带来了一个新的问题:过于保守的加锁或错误的加锁顺序都可能导致死锁。

  多线程主要关注于对程序执行进行更好的分配,发送并发请求的客户端能够很好地观察到这种分配。然而,获取性能最大化的手段却根本不依赖于多线程,而是尽量在第一时间内以最快的速度处理用户的请求。即使能够避免死锁,同步也可能导致一定程度的性能损失。必须避免对经常使用的共享资源进行不必要的加锁。跨线程的异常传播也会导致处理非同步的异常变得更加困难。而且,使用多线程和复杂的互锁机制将使得代码调试变得异常困难。

  显然,在真正并发的环境下,这些问题无一不需要考虑。例如,如果构件实例运行在独立的处理器上,就需要考虑并发请求的问题。可以在处理一个请求时对某个构件实例进行完全的加锁,但这样做可能会导致死锁或者糟糕的响应时间。

 

个人感悟

  回首看5年前代码,我很喜欢写多线程,线程池一类的程序。也是当时技术圈子里对多线程的吹捧,又看了多线程方面的书籍,当时多线程、并发、消息队列、分布式事务的都写进一个项目里去了,一用多线程,发现有并发问题,于是各种加锁,然后发现服务器断电或者中途杀进程后再重启的问题,于是各种记录日志、异常中止回滚操作,还有适应各种配置的服务器,加入了看门狗机制,最后项目是非常非常地复杂了,这无疑增加了程序的维护成本。

  可以通过入口的.NET线程池的代码片段感受一下:

while (CommonQueue.TaskQueue.Count > 0 && ThreadState.CurrTaskThread < ThreadState.MaxTaskThread){   WatchDog.FeedDog();   GenTask item = CommonQueue.TaskQueue.DeQueue();   ThreadState.CurrTaskThread++;//当前线程+1   genTaskServ.HandleTask(item);//处理任务,任务状态到1   ThreadPool.QueueUserWorkItem(o => ProcExecute(item));}

  这里有点炫技的成分。WatchDog.FeedDog();是给看门狗喂食物,每隔一分钟看门狗的饥饿程度会增长,当看门狗快饿死的时候会强制同步线程数和实际线程池中的任务数。

任务队列代码片段:

Loggers.WriteLog("正在队列初始化...");CommonQueue.TaskQueue.Clear();//先获取需要重做的任务加入队列GenTaskService taskServ = new GenTaskService();IList<GenTask> redoList = taskServ.GetRedoTask();foreach (GenTask redo in redoList){ CommonQueue.TaskQueue.EnQueue(redo);}//获取等待任务加入队列IList<GenTask> taskList = taskServ.GetTask();foreach (GenTask task in taskList){ CommonQueue.TaskQueue.EnQueue(task);}

  与消息队列的通信本就是一种异步通信,又何必玩多线程脱了裤子放屁呢?乍一看又是炫技,实际上并不是。这里本可以用一些redis、kafka等消息中间件,并且用它们自带的数据持久化到磁盘来实现服务器断电或重启后的问题。而实际上,为了降低实施和运维成本,改用了数据库轮询的原始方案替代消息队列,并且自己编码对系统故障与恢复进行处理,另一个原因则是oracle这类的商业数据库的故障与恢复的处理显然要比这些开源中间件要靠谱得多。

 

  现在已经大道至简,没事尽量不碰多线程了。前阵子我在搞一个服务做数据清洗,然后发现清洗的效率并不高,无法在短时间内跑出几个月的数据,因为每条数据都调阅了两家第三方公司的服务后还需要一些处理,的确单进程性能到了瓶颈。于是阿里项目经理来催,历史数据怎么办?我说:"当前数据没问题,历史数据让我性能优化一下即可。"

  我发现大家对性能问题都非常热心,项目经理想拉上阿里的技术人员来协助,主程同事更是直接提出用多线程解决。于是我很为难地告诉项目经理说:"我只是反馈了一下项目的情况,至于性能优化我自己会搞定,不要再找来技术外援了,会增加沟通成本。"听完项目经理慌的一批。然后耐心地跟主程同事解说为何不用多线程,当主程同事了解到多线程需要修改很多现有代码并考虑并发和去重的问题后,意识到用多线程的话,这两天就要通宵了,想着单就为了以后面试上写一条多线程的经验而如此折磨自己何必呢,就放弃了多线程的念头。

  最后,用多进程的方式解决问题。最终以不修改现有代码和逻辑,复制N份程序的jar包,每份jar包配置文件指定跑每个月的数据,再通过纵向业务划分,再拆一套,跑完历史数据再关闭这些多余进程,完美。

  现在多线程和异步的使用,主要是在客户端使用异步来减少未响应、提高用户体验。比如Web一般使用ajax的异步请求,而桌面应用程序开个线程异步请求一下,但是很多时候都是可以用一个"正在加载"的窗口解决问题,往往也是考虑成本、可维护性后的最优解。

  程序员的生活,往往就是这么朴实无华,且枯燥。

给技术流程序员的忠告

  当今的软件圈子氛围下,我还是建议程序员在项目里炫技,以上面oracle轮询代替消息队列为例,假如遇到如上问题你炫技了,你就学会了一种消息队列和多线程,别学我那样处处为公司各部门着想,还构建自己的可替代性。另外对于面试会多线程和消息队列也是一个加分项,然后核心项目各种华而不实的炫技,让项目难以移交和难以维护,还能构建自己的不可替代性。

原文转载:http://www.shaoqun.com/a/479843.html

eprice:https://www.ikjzd.com/w/1325

东杰智能:https://www.ikjzd.com/w/1967

卖家网:https://www.ikjzd.com/w/1569


理论知识构件  中间件是一个软件集合的名字,这些软件位于操作系统和高层次分布式编程平台之间。中间件有时被分为面向消息的中间件和面向对象的中间件。而然现有的大多数中间件都是这两种类型的混合体。当然,现在也有一种趋势是由传统的操作系统直接支持。操作系统总是包含了对通信协议的支持。WEB服务的推进和程序世界从以城市为中心到以协议为中心的转变,导致两种中间件的价值观:支持合适的协议或者提供简化本地服务构造
巴克莱银行:https://www.ikjzd.com/w/2775
weebly:https://www.ikjzd.com/w/2486
ShipBob:https://www.ikjzd.com/w/915
揭秘!亚马逊大卖都在悄悄做这个!:https://www.ikjzd.com/home/11573
多站点关键词排名错乱!亚马逊系统持续崩溃中?:https://www.ikjzd.com/home/103668

没有评论:

发表评论