本文主要内容
- 弹出式线程(Pop-up threads)
- 使单线程代码多线程化会产生那些问题
一、弹出式线程(Pop-up threads)
以在一个http到达之后一个Service的处理为例子来介绍弹出式线程。
上面的例子中传统的做法有可能是在Service中有一个线程一直在等待request的到达,等request到达后这个线程会开始检查请求最后在进行处理。当这个线程在处理request的时候,后面来的request会被block,一直到线程处理完当前request为止。如下图所示。
弹出式线程的处理方法:当有新的请求到达时,马上创建一个线程去处理这个请求(弹出pop-up thread).
弹出式线程的优势:
- 线程是全新的,没有历史,创建很快
- request没有被block,请求到达到开始处理之间的延迟非常小
在使用弹出式线程时需要额外考虑一下,这个线程是应该运行在那里比较好。用户空间还是内核空间。将线程放在内核相对会比较容易,但是因为在内核,如果该线程出问题,危害性将比用户空间的线程大。
二、使单线程代码多线程化
一些既有的代码都是基于单线程的,如果将其修改为支持多线程会产生那些后果呢,下面简单一一做一下分析。
2.1 多线程共享变量
图示:
n在t1、t2之间共享:
- T1调用Check程序检查自己的状态,得的n=1这个状态
- 在T1得到状态使用n之前cpu被调度给了T2。
- T2调用Check程序检查自己的状态,得的n=2这个状态
- CPU再次被调度到T2,这个时候n已经等于2了,产生了错误的结果。
上面的问题是可以被优化的,让T1和T2取消这个对N的共享,让其各自维护自己的状态码N1,和N2即可避免上述问题。
2.2 重复进入
类似于上面的共享问题,一个Library提供一个功能,在一个线程进入该Library后没有返回之前另外一个线程又进入了,会产生什么问题呢。
比如这个Library在请求没有返回之前将数据放入buffer,那么这个时候另外一个线程进入这个Library会将原有buffer的数据重置,从而对第一个线程的执行造成不可预估的后果。
这种问题可以让Library提供一个标志位,当这个Library处于调用状态时,设置标志位,这样后续的请求将会被block,从而解决问题,但是这会降低程序的并行执行能力。
2.3 信号(中断处理)
- 比如一个键盘key down信号,应该有那个线程处理,要不要pop-up一个新的线程
- 有多个线程时,如何一个线程修改了信号,要不要同时通知其他线程
- 同一个信号,不同线程的处理可能完全不一样,比如ctrl+c,有些线程用于粘贴,有些用于终止程序。
信号的处理在单线程程序中就已经很复杂了,多线程是复杂度加倍。
2.4 堆管理
在很多系统中,当一个进程的堆栈异常时(stackoverflow),内核自动为这个进程分配堆栈,当一个进程有多个线程时,势必会有多个堆栈,当内核没有完全了解所有堆栈,有可能某些堆栈发生堆栈异常时,内核并不知道,无法为其自动分配堆栈。
2.5 all
如果在没有经过大量分析和设计的前提下将多线程引入一个现有的单线程系统会产生很多不可预知的错误,绝不是一个简单的引入多线程机制那么简单,要从Library等各个方面进行分析和设计,确保在线程安全的情况下再引入多线程。后期引入多线程的成本要比刚开始设计就包含多线程要高很多。