内容提要
程序运行即进程,多任务通过进程分时执行实现。进程有运行、就绪、阻塞三种状态,切换代价较大。服务器多采用单进程多线程模型,线程共享进程内存但各有私有栈。多线程修改共享堆数据会引发线程安全问题,需用锁保护临界区。锁和IO阻塞可能耗尽资源导致系统崩溃,可通过限流、降级、异步IO及Actor模型避免。
延伸解读
进程切换代价高,服务器为何偏爱多线程
文章指出,进程轮流在CPU上执行时,每次都要进行进程间CPU切换,代价非常大。因此服务器应用通常采用单进程多线程模型。线程共享进程的内存地址空间,创建和切换开销相对较小,更适合处理大量并发请求。这也解释了为什么Java Web应用普遍基于多线程运行。
线程私有栈与共享堆:理解线程安全的前提
每个线程拥有自己私有的内存地址范围,其他线程不能访问,栈溢出常因栈空间开小或递归调用导致。但线程共享进程的堆内存,当多个线程同时修改堆中数据时,就可能出现同时修改的情况,例如两个线程同时对同一数据执行加1。这是线程安全问题产生的根源。
阻塞为何会拖垮高并发系统
锁和IO都会引起线程阻塞。阻塞导致线程既不能继续执行,也不能释放资源,进而造成资源耗尽,最终可能使系统崩溃。文章给出的应对思路包括:限流以减少创建的线程数,降级以尽早释放线程,以及采用异步IO或Actor模型来避免阻塞和临界区。
Q&A
进程和程序有什么区别?
程序是静态的,而程序运行起来以后被称为进程。
进程有哪几种状态?分别代表什么?
进程有三种状态:运行状态(进程在CPU上执行)、就绪状态(获得了除CPU外的所有资源,等待CPU)、阻塞状态(等待某事件如I/O完成而暂停运行)。
为什么服务器应用通常采用单进程多线程模型?
因为不同进程轮流在CPU上执行时,进程间CPU切换代价非常大,而线程共享进程内存,切换开销小,所以服务器应用通常是单进程多线程。
线程之间是如何共享和私有内存的?
所有线程共享进程的内存地址空间(堆),但每个线程拥有自己私有的内存地址范围(如栈),其他线程不能访问。
什么是线程安全问题?如何解决?
当多个线程同时修改堆中的共享数据时,可能出现同时修改导致数据不一致的问题。解决方法主要是使用锁保护临界区,只有获得锁的线程才能执行临界区代码。
高并发下阻塞为什么会导致系统崩溃?如何避免?
锁或IO会引起线程阻塞,阻塞导致线程既不能继续执行也不能释放资源,进而资源耗尽系统崩溃。避免方法包括:限流(控制请求数减少线程创建)、降级(关闭部分功能尽早释放线程)、避免阻塞(使用异步IO或无临界区的Actor模型)。