多进程和多线程 #
定义
多进程
进程是资源分配的最小单位,线程是CPU调度的最小单位。
进程:经典定义是一个执行中的程序的实例。
进程与应用程序的区别:程序是一堆代码和数据的集合,可以作为目标模块存在于磁盘,或作为段存在于地址空间中。进程是程序的一次具体执行过程,它是动态地创建和消亡的,具有一定的生命周期,是暂时存在的。程序总是运行在某个进程的上下文中。
系统中每个程序都是运行在某个进程的上下文中的。上下文是由程序正确运行所需的状态组成,包括存放在存储器中的代码和数据、栈、通用目的寄存器的内容、程序计数器、环境变量和打开文件描述符的集合。进程给应用程序提供了两个关键抽象:
- 独立的逻辑控制流,提供程序独占处理器的假象。
- 私有的地址空间:进程为每一个程序提供它自己的私有地址空间,和这个空间中某地址相关联的存储器字节不能被其他进程读写。和私有地址空间关联的存储器内容一般不同,但空间有相同的结构,比如下图是x86Linux进程的地址空间的组织结构,这个私有的地址空间最上部是内核保留的,包含内核在代表进程执行指令时使用的代码、数据和栈。最下部是预留给用户程序的,包括通常的文本、数据、堆和栈。代码段始终是从0x08048000处开始(32位系统),0x00040000处开始(64位系统)。
内核为每个进程维护一个上下文。上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括包含程序计数器、用户栈、状态寄存器等
当调度进程时,使用一种称为上下文切换的机制来将控制转移到新的进程。上下文切换包括: - 保存当前进程的上下文
- 恢复某个先前被抢占的进程的上下文
- 将控制传递给这个新恢复的进程
切换到另一个进程的时候,会载入已保存的对应于将要执行的进程的寄存器值,但是由于需要重新为高速缓存热身,会存在切换开销。
多进程优点:
1.每个进程互相独立,有独立的虚拟地址空间,子程序不影响主程序的稳定性,子进程崩溃没关系,比如谷歌浏览器;
2.尽量减少数据共享的安全问题和线程加锁/解锁的影响;
3.可用地址空间比较大,4GB(32位,2^32),2^64(64位)。
缺点:
1.独立的地址空间使得进程间共享信息也很困难,必须使用显式的IPC(进程间通信)机制。
2.往往比较慢,因为创建销毁进程,系统都要为之分配和回收较多的资源,同时IPC的开销也比较大。
多线程
线程:线程(thread) 就是运行在进程上下文中的逻辑流。是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。每个线程都有它自己的线程上下文(thread context),包括一个唯一的整数线程ID(Thread ID,TID)、栈、程序计数器和局部变量等。所有运行在该进程里的线程共享该进程的整个虚拟地址空间,因此这个共享虚拟地址空间的整个内容,包括它的代码,数据,堆,共享库和打开的文件。
线程的上下文切换开销比进程小得多,也比进程的上下文切换快得多。和一个进程相关的线程组成一个线程池(pool),独立于其他线程创建的线程。主线程和其他线程的区别仅仅是:他总是进程中第一个运行的线程。线程在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
多线程优点:
1.同一进程下线程之间由于使用相同的地址空间,共享大部分数据,所以交换数据非常方便;
2.线程的创建销毁、切换都比较简单,速度较快。
3.使用多线程可以减少程序的响应时间。如果某个操作很耗时或者陷入长时间的等待,比如发送邮件,等待网络响应,在单线程下,此时程序不会响应鼠标和键盘等操作。使用多线程后,可以将耗时的操作分配到一个单独的线程后台执行,保证更好的交互体验。
4.发挥多处理器的强大能力。如果在程序中只有一个线程,那么最多同时只能在一个 处理器上运行。在双处理器系统上,单线程的程序只能使用一半的CPU资源,而在拥有100个 处理器的系统上,将有99%的资源无法使用。同时,对于单处理器系统,如果程序是单线程的,那么 当程序等待某个同步I/O操作完成时,处理器将处于空闲状态。而在多线程程序中,如果一个 线程在等待I/O操作完成,另一个线程可以继续运行,使程序能够在I/O阻塞期间继续运行。
缺点:
1.一个线程的崩溃可能影响到整个程序的稳定性;
2.线程之间的同步和加锁控制比较麻烦;
3.所有线程共用进程的地址空间,受限于4GB地址空间限制(32位),当然64位限制就会很小;
线程安全:一般说来,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果。
要确保函数线程安全,主要需要考虑的是线程之间的共享变量。属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈、程序计数器。因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些共享变量进行访问时,如果要保证线程安全,一般通过加锁的方式。
使用场景
1)需要频繁创建销毁的优先用线程。
实例:web服务器。来一个建立一个线程,断了就销毁线程。要是用进程,创建和销毁的代价是很难承受的。
2)需要进行大量计算的优先使用线程。
所谓大量计算,当然就是要消耗很多cpu资源,为最大限度利用cpu,并且交换数据方便,这种情况选线程是最合适的。
实例:图像处理、算法处理
3)强相关的处理用线程,弱相关的处理用进程。
什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。
一般的server需要完成如下任务:消息收发和消息处理。消息收发和消息处理就是弱相关的任务,而消息处理里面可能又分为消息解码、业务处理,这两个任务相对来说相关性就要强多了。因此消息收发和消息处理可以分进程设计,消息解码和业务处理可以分线程设计。
4)可能扩展到多机分布的用进程,多核分布的用线程。
5)都满足需求的情况下,用你最熟悉、最拿手的方式。
联系
- 一个程序至少有一个进程,一个进程至少有一个线程。
- 一个线程可以创建和销毁另一个线程;同一个进程中的多个线程之间可以并发执行。
- 每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在进程中,同样由内核调度。
linux下进程间通信IPC的几种主要手段简介:
- 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
- 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;
- 报文队列(消息队列):消息队列是消息的链接表,包括Posix消息队列,system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
- 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
- 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。
Chrome浏览器:
采用独立进程设计,浏览器、标签页、插件和扩展都是独立进程,所以Chrome内核浏览器即使网页崩溃,其他功能都可以正常使用,整个浏览器也不会受到影响。为了安全、稳定、性能。
1、浏览器主进程
2、渲染进程:对页面的HTML、JavaScript和CSS等部分内容进行渲染,一般一个进程渲染一个站点的多个标签页
3、插件进程:浏览器中如AdobeFlash Player等插件的进程
4、扩展进程:各种用户自己添加的扩展程序,比如比较出名的Adblock Plus
多进程需要面对的问题包括:
- 内存占用大,因为无法像多线程模型共享公共的内存开销,比如使用的库,或者某些全局的数据缓存等。
- 进程间通讯的成本大。特别是使用共享内存交换数据的成本。
- 进程启动的开销大。