转载请说明出处:http://blog.csdn.net/cywosp/article/details/27316803

一、fork()函数
    在操作系统的基本概念中进程是程序的一次运行,且是拥有资源的最小单位和调度单位(在引入线程的操作系统中,线程是最小的调度单位)。在Linux系统中创建进程有两种方式:一是由操作系统创建,二是由父进程创建进程(通常为子进程)。系统调用函数fork()是创建一个新进程的唯一方式,当然vfork()也能够创建进程,可是实际上其还是调用了fork()函数。fork()函数是Linux系统中一个比較特殊的函数,其一次调用会有两个返回值,以下是fork()函数的声明:
#include <unistd.h>

// On success, The PID of the process is returned in the parent, and 0 is returned in
the child. On failure,
// -1 is returned in the parent, no child process is created, and errno is set appropriately.
pid_t fork (void);

    当程序调用fork()函数并返回成功之后,程序就将变成两个进程,调用fork()者为父进程,后来生成者为子进程。这两个进程将运行同样的程序文本,但却各自拥有不同的栈段、数据段以及堆栈拷贝。子进程的栈、数据以及栈段開始时是父进程内存对应各部分的全然拷贝,因此它们互不影响。从性能方面考虑,父进程到子进程的数据拷贝并非创建时就拷贝了的,而是採用了写时拷贝(copy-on
-write)技术来处理。调用fork()之后,父进程与子进程的运行顺序是我们无法确定的(即调度进程使用CPU),意识到这一点极为重要,由于在一些设计不好的程序中会导致资源竞争,从而出现不可预知的问题。下图为写时拷贝技术处理前后的示意图:
          


    在Linux系统中,经常存在很多对文件的操作,fork()的运行将会对文件操作带来一些小麻烦。因为子进程会将父进程的大多数数据拷贝一份,这样在文件操作中就意味着子进程会获得父进程全部文件描写叙述符的副本,这些副本的创建方式类似于dup()函数调用,因此父、子进程中相应的文件描写叙述符均指向同样的打开的文件句柄,并且打开的文件句柄包括着当前文件的偏移量以及文件状态标志,所以在父子进程中处理文件时要考虑这样的情况,以避免文件内容出现混乱或者别的问题。下图为运行fork()调用后文件描写叙述符的相关处理及其变化:

二、线程
    与进程类似,线程(thread)是同意应用程序并发运行多个任务的一种机制。一个进程中能够包括多个线程,同一个程序中的全部线程均会独立运行,且共享同一份全局内存区域,当中包括初始化数据段(initialized data),未初始化数据段(uninitialized
data),以及堆内存段(heap segment)。在多处理器环境下,多个线程能够同一时候运行,假设线程数超过了CPU的个数,那么每一个线程的运行顺序将是无法确定的,因此对于一些全局共享数据据须要使用同步机制来确保其的正确性。
    在系统中,线程也是稀缺资源,一个进程能同一时候创建多少个线程这取决于地址空间的大小和内核參数,一台机器能够同一时候并发运行多少个线程也受限于CPU的数目。在进行程序设计时,我们应该精心规划线程的个数,特别是依据机器CPU的数目来设置工作线程的数目,并为关键任务保留足够的计算资源。假设你设计的程序在背地里启动了额外的线程来运行任务,那这也属于资源规划漏算的情况,从而影响关键任务的运行,终于导致无法达到预期的性能。非常多程序中都存在全局对象,这些全局对象的初始化工作都是在进入main()函数之前进行的,为了能保证全局对象的安全初始化(按顺序的),因此在程序进入main()函数之前应该避免线程的创建,从而杜绝未知错误的发生。

三、fork()与多线程
    在程序中fork()与多线程的协作性非常差,这是POSIX系列操作系统的历史包袱。由于长期以来程序都是单线程的,fork()运转正常。当20世纪90年代初期引入线程之后,fork()的适用范围就大为缩小了。
    在多线程运行的情况下调用fork()函数,仅会将发起调用的线程拷贝到子进程中。(子进程中该线程的ID与父进程中发起fork()调用的线程ID是一样的,因此,线程ID同样的情况有时我们须要做特殊的处理。)也就是说不能同一时候创建出于父进程一样多线程的子进程。其它线程均在子进程中马上停止并消失,而且不会为这些线程调用清理函数以及针对线程局部存储变量的析构函数。这将导致下列一些问题:
1. 尽管仅仅将发起fork()调用的线程复制到子进程中,但全局变量的状态以及全部的pthreads对象(如相互排斥量、条件变量等)都会在子进程中得以保留,这就造成一个危急的局面。比如:一个线程在fork()被调用前锁定了某个相互排斥量,且对某个全局变量的更新也做到了一半,此时fork()被调用,全部数据及状态被复制到子进程中,那么子进程中对该相互排斥量就无法解锁(由于其并不是该相互排斥量的属主),假设再试图锁定该相互排斥量就会导致死锁,这是多线程编程中最不愿意看到的情况。同一时候,全局变量的状态也可能处于不一致的状态,由于对其更新的操作仅仅做到了一半相应的线程就消失了。fork()函数被调用之后,子进程就相当于处于signal
handler之中,此时就不能调用线程安全的函数(用锁机制实现安全的函数),除非函数是可重入的,而仅仅能调用异步信号安全(async-signal-safe)的函数。fork()之后,子进程不能调用:
  • malloc(3)。由于malloc()在訪问全局状态时会加锁。
  • 不论什么可能分配或释放内存的函数,包含new、map::insert()、snprintf() ……
  • 不论什么pthreads函数。你不能用pthread_cond_signal()去通知父进程,仅仅能通过读写pipe(2)来同步。
  • printf()系列函数,由于其它线程可能恰好持有stdout/stderr的锁。
  • 除了man 7 signal中明白列出的“signal安全”函数之外的不论什么函数。
2. 由于并未运行清理函数和针对线程局部存储数据的析构函数,所以多线程情况下可能会导致子进程的内存泄露。另外,子进程中的线程可能无法訪问(父进程中)由其它线程所创建的线程局部存储变量,由于(子进程)没有不论什么对应的引用指针。

    因为这些问题,推荐在多线程程序中调用fork()的唯一情况是:其后马上调用exec()函数运行还有一个程序,彻底隔断子进程与父进程的关系。由新的进程覆盖掉原有的内存,使得子进程中的全部pthreads对象消失。
    对于那些必须运行fork(),而其后又无exec()紧随其后的程序来说,pthreads API提供了一种机制:fork()处理函数。利用函数pthread_atfork()来创建fork()处理函数。pthread_atfork()声明例如以下:
#include <pthread.h>

// Upon successful completion, pthread_atfork() shall
return a value of zero; otherwise, an error number shall be returned to indicate the error.
// @prepare 新进程产生之前被调用
// @parent  新进程产生之后在父进程被调用
// @child    新进程产生之后,在子进程被调用
int pthread_atfork (void (*prepare)
(void), void (*parent) (void), void (*child)
(void));

该函数的作用就是往进程中注冊三个函数,以便在不同的阶段调用,有了这三个參数,我们就能够在相应的函数中加入相应的处理功能。同一时候须要注意的是,每次调用pthread_atfork()函数会将prepare加入�到一个函数列表中,创建子进程之前会(按与注冊次序相反的顺序)自己主动运行该函数列表中函数。parent与child也会被加入�到一个函数列表中,在fork()返回前,分别在父子进程中自己主动运行(按注冊的顺序)。详细事例可參考:http://blog.chinaunix.net/uid-26885237-id-3210394.html

四、总结
    fork()函数的调用会导致在子进程中除调用线程外的其他线程全都终止执行并消失,因此在多线程的情况下会导致死锁和内存泄露的情况。在进行多线程编程的时候尽量避免fork()的调用,同一时候在程序在进入main函数之前应避免创建线程,由于这会影响到全局对象的安全初始化。线程不应该被强行终止,由于这样它就没有机会调用清理函数来做对应的操作,同一时候也就没有机会来释放已被锁住的锁,假设还有一线程对未被解锁的锁进行加锁,那么将会马上发生死锁,从而导致程序无法正常执行。


參考
[1] Linux/UNIX系统编程手冊(上)
[2] Linux多线程服务端编程使用muduo C++网络库
[3] http://blog.chinaunix.net/uid-26885237-id-3210394.html






每天进步一点点——论fork()函数与Linux中的多线程编程的更多相关文章

  1. linux中c语言编程main函数和参数

    linux下main函数的的标准调用函数的标准形式 int main(int char,char *argv[]) 在main函数的两个参数中,argc必须是整型变量,其是命令行的参数的数目,argv ...

  2. Linux多任务编程之二:fork()函数及其基础实验(转)

    来源:CSDN  作者:王文松 转自Linux公社 fork()函数 在 Linux 中创建一个新进程的唯一方法是使用fork()函数.fork()函数是 Linux 系统中一个非常重要的函数,和咱们 ...

  3. Linux C 中 fork() 函数详解

    一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork() 函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同 ...

  4. Fork函数初识

    fork函数用于创建子进程,典型的调用一次,返回两次的函数.其中调用进程返回子进程的PID,而子进程则返回0.但是两个进程的执行顺序是不定的. fork函数调用完成以后父进程的虚拟存储空间被拷贝给了子 ...

  5. fork()函数详解

    原文链接:http://blog.csdn.net/jason314/article/details/5640969  一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函 ...

  6. linux中fork()函数详解(原创!!实例讲解) (转载)

     一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程, 也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不 ...

  7. (转)linux中fork()函数详解

    一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同, ...

  8. linux中fork()函数详解

    一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同, ...

  9. linux中fork()函数详解(转)

    转自:http://blog.csdn.net/jason314/article/details/5640969 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过 ...

随机推荐

  1. hdu 1398 整数划分变形 (母函数)

    有1,4,9,16,25.....2^17这么多面值的硬币,问任意给定一个不大于300的正整数面额,用这些硬币来组成此面额总共有多少种组合种数 比如10全14 + 6个 14+4+1+19+1 求(1 ...

  2. CCF CSP 201604-3 路径解析

    CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF CSP 201604-3 路径解析 问题描述 在操作系统中,数据通常以文件的形式存储在文件系统中.文件系 ...

  3. NHibernate 学习导航

    http://www.cnblogs.com/lyj/archive/2008/10/30/1323099.html

  4. volatile 和锁的内存语义

    一.volatile 的内存语义 1. volatile 的特性 volatile变量自身具有以下特性: 可见性 :对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后 ...

  5. Eclipse插件安装出现Duplicate location错误

    一.原因 1.曾今安装过此插件 2.曾今安装此插件的时候出现错误 二.解决方法[eclipse] - Help - Install new software - Available Software ...

  6. gpfs中遇到的错误

    主要导致这个问题是之前GPFS格式化的磁盘会留下gpfs的一些信息 mmcrnsd: Disk name nsd01 is already registered for use by GPFS.mmc ...

  7. OpenStack计费项目Cloudkitty系列详解(一)

    云计算是一种按需付费的服务模式,虽然OpenStack前期在计量方面走了些“弯路”,但现在的ceilometer.gnocchi.aodh.panko项目的稳步并进算是让其峰回路转.然而,目前来看Op ...

  8. [ 转载 ] Java基础4--Java中的static关键字解析

    Java中的static关键字解析 static关键字是很多朋友在编写代码和阅读代码时碰到的比较难以理解的一个关键字,也是各大公司的面试官喜欢在面试时问到的知识点之一.下面就先讲述一下static关键 ...

  9. 关于mysql_connect CLIENT_MULTI_RESULTS

    自己写了一个mysql存储过程,以为php有用于调用存储过程的内建函数,查了一下发现只能用mysql_query(call pro())这样的方式,我认为从本质上也就相当于在mysql命令行里执行语句 ...

  10. linux下elasticsearch安装教程

    centos 7.5安装 elasticsearch 第一步,安装elasticsearch需要Java8 首先使用 yum list installed | grep java 查看安装的Java版 ...