存档

2012年2月 的存档

进程和线程[翻译]

2012年2月2日 没有评论

进程和线程是OS基本的概念,也是面试热门题目。以前看过这个文章,介绍进程与线程的区别与选择,讲的较浅显,又有一定覆盖面。前两天在某群中鸟哥又推荐了一遍,顺手翻译如下。原文请猛击这里

 

===============华丽的分割线===============

 

什么是Fork?

Fork就是产生一个新的进程, 它和原进程看起来完全一样, 除了有一个新的进程ID, 拥有自己的内存地址空间. 新进程(子进程)和老进程(父进程)共享代码段, 各自独立运行.

Fork最常见的例子就是在shell下, 每当你运行一个命令, shell就会fork一个子进程来执行你的命令(严格来说, 是fork以后紧接着exec)

执行fork系统调用时,操作系统将拷贝父进程的所有page,并加载进独立的内存区域。但某些情况不需要这些page拷贝,比如exec系列的系统调用,因为execv将替换掉父进程的地址空间。

fork需要注意:

  • 子进程有自己的独立进程id
  • 子进程持有父进程的文件描述符
  • 子进程不会继承父进程的文件锁
  • 父进程中打开的信号量,在子进程中一样处于开启状态
  • 子进程持有父进程的消息队列描述符
  • 子进程有自己的地址与内存空间

相对而言fork被更普遍的使用,大致原因如下:

  • 基于fork的开发实现更容易
  • 更容易维护
  • 因为进程在自己独立的虚拟地址空间中运行,所以fork更安全。如果一个进程crash或者缓冲区溢出,不会影响到其他的进程
  • 基于线程的代码更难于debug
  • fork的移植性更好
  • fork在单核cpu上更快,因为没有锁和上下文切换的开销

使用fork的应用有: telnetd(freebsd), vsftpd, proftpd, Apache13, Apache2, thttpd, PostgreSQL.

fork的陷阱

  • 每个新进程拥有独立内存地址空间,它带来了更长的启停时间
  • 如果使用fork,需要考虑两个进程可能需要交互,而进程间通信的成本很高
  • 如果父进程在子进程前退出,子进程将变成孤儿进程。而线程模型中可以简单的结束线程、挂起线程和恢复线程。如果程序退出,所有线程也会自动结束
  • 存储空间不足可能导致fork失败

什么是线程?

线程是轻量级进程(LWPS)。一般认为,线程只是CPU状态(和其他一些minimal state),而进程包括其他的数据、堆栈、IO和信号等等。线程的overhead比fork要小,因为系统不需要初始化新的虚拟内存空间。在多核系统上,可以将执行流分配到其他的处理器上,通过并行和分布式处理得到速度提升;在单处理器的系统上,由于存在IO延迟和其他挂起执行的功能,使用线程一样可以获得收益。

同一进程内的线程将共享:

  • 进程指令
  • 绝大多数数据
  • 打开的文件(描述符)
  • 信号与信号处理函数
  • 当前工作目录
  • 用户和组id

每个线程有独立的:

  • 线程id
  • 寄存器、栈指针
  • 栈(本地变量,返回地址)
  • 信号mask
  • 优先级
  • errno返回值(errno是tls存储的)

线程需要注意:

  • 在多处理器/多核系统中,线程效率最高
  • 只占用一张进程表和一个schedule
  • 进程中的所有线程共享相同的地址空间
  • 线程不维护创建的线程列表,也不知道是哪个线程创建的自己
  • 线程通过共享基本部件来减少overhead
  • 线程在内存管理方面效率更高,因为它们使用相同的内存块而不是创建新的

线程的陷阱

  • Race conditions:多个线程同时读写相同的数据,但却不知道其他线程存在,这可能导致数据混乱。这种情况我们称之为竞态条件(race conditions)。操作系统会调度线程,而不能确定其运行方式。线程可能不会按照创建的顺序运行;它们也可能以不同的速度运行。当线程执行时,可能会给出预期外的结果。在线程模型下,必须使用锁和join来获取可预测的执行顺序和产出。
  • 线程安全代码:在多线程中运行的代码需要是线程安全的。这意味着使用静态变量和全局变量时,不能假设其他线程不会对它进行访问。如果代码使用了静态变量或全局变量,必须使用锁,或者重写函数,以避免使用这些变量。在C语言中,局部变量在栈上动态分析,因此,不使用静态数据和其他共享资源的函数都是线程安全的。程序中,非线程安全函数同一时间只能被一个线程调用,这点必须得到保证。很多不可重入函数返回了指向静态数据的指针,这可以通过返回动态分配的数据或由调用者提供空间的方式来避免。非线程安全函数的一个例子就是strtok,它也是不可重入的。它的可重入版本strtok_r是线程安全的。

线程的优势:

  • 线程共享相同的内存空间,因此线程之间共享数据速度很快。
  • 如果设计实现较好,使用线程将获得较大的速度提升,因为在多线程程序中没有进程级别的上下文切换。
  • 线程启动和结束很快

使用线程的程序:MySQL, Firebird, Apache2, MySQL 323

FAQs

1. 我该使用哪个?

回答:取决于很多因素。fork比thread更重量级,有更高的启停成本。进程间通信(IPC)比线程间通信更困难,也更慢。实际上在通信方面,线程优势很大。但另一方面,一个线程crash后,将导致所有其他线程停止;并且只要一个线程出现缓冲区溢出,就会为所有的线程带来安全问题。

2. 哪个更好?

回答:这完全取决于需要。在当前的linux(2.6.x),进程和线程的切换成本没有太大区别(区别只有thread的MMU)。由于共享地址空间,线程还存在一个问题:一个线程的错误指针,可能导致其他线程的数据错误。

3. 什么时候要用线程或进程?

回答:
如果你想用多线程,那么正确的问题应该是:程序的哪些部分可以/不可以被线程化。以下是一些经验法则:

  • 是否存在互不依赖的耗时操作(比如画窗体、打印文档、响应鼠标事件、计算表格的列、信号处理等等)?
  • 数据锁不多(指共享数据的量小)?
  • 准备好了考虑锁、死锁和竞态条件?
  • 任务能否划分?比如是否可以一个线程处理信号,另一个处理GUI?

结论

  • 用线程或者fork,取决于应用需求
  • 线程更强大,但并不是万能的
  • 基于线程比基于fork更难写(也更难维护),只适用于熟手
  • 只在极其注重性能的程序中使用线程
分类: Linux 标签: