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

理论知识

构件

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

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

 

异步问题

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

多线程

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

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

  显然,在真正并发的环境下,这些问题无一不须要考虑。例如,若是构件实例运行在独立的处理器上,就须要考虑并发请求的问题。能够在处理一个请求时对某个构件实例进行彻底的加锁,但这样作可能会致使死锁或者糟糕的响应时间。编程

 

我的感悟

  回首看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轮询代替消息队列为例,假如遇到如上问题你炫技了,你就学会了一种消息队列和多线程,别学我那样到处为公司各部门着想,还构建本身的可替代性。另外对于面试会多线程和消息队列也是一个加分项,而后核心项目各类华而不实的炫技,让项目难以移交和难以维护,还能构建本身的不可替代性。

相关文章
相关标签/搜索