Linux的多进程与多线程

Linux 是多任务操作系统,可以同时运行多个进程,来完成多项工作。在Linux编程中,为了满足项目高并发的性能需求,采用多进程和多线程进行编程,下面将具体介绍Linux下的多进程与多线程编程。

1.多进程
进程就是处于活动状态的程序,占用一定的内存空间。进程可以把自己复制一份,从而创造出一个新的进程。新的进程称为 子进程,原来的进程称为 父进程。
进程可以复制自己。这意味着启动一个程序,可能会产生多个进程。这样的程序能同时进行多项工作。多进程编程就是要设计一个这样的程序。

1.1进程的创建
在进程中创建子进程是通过 fork() 函数进行创建的。fork() 不需要任何参数,返回值是 pid_t 型。pid_t 型实际上就是 int 型。这是专门用来保存进程 PID(进程的编号)的类型。如果子进程创建成功,fork() 函数将返回子进程的 PID,否则返回 -1。

前面说过,创建子进程相当于把自己复制一份。也就是说,创建出来的子进程和父进程几乎是一模一样的,并且都将接着执行 fork() 函数后面的代码。

不同的是,对于 fork() 函数的返回值,在子进程中将得到 0。因此,如果在 fork() 函数之后用一个 if 语句对 fork() 函数的返回值进行判断,子进程和父进程将进入不同的分支。函数如下:

pid_t cpid;

cpid = fork();
if (cpid == -1) {
    printf("Create process failed!\n");
    exit(1);
}

if (cpid == 0) {
    printf("Hello from Child!\n");
} else {
    printf("Hello from Parent!\n");
}

1.2进程状态
进程从创建到运行结束,经历的全部过程,称为进程的生命周期。在生命周期的不同阶段,进程会呈现不同的状态。下表列出了进程可能出现的所有状态。

状态 含义
创建状态 正在被创建
就绪 刚刚创建好,还没运行过
内核状态 运行中
用户状态 暂停中
睡眠 已经轮到这个进程上场了,但是它的某些需求得不到满足,只能继续等待
唤醒 正在睡眠的进程,正在被唤醒
被抢占 运行到一半,CPU 被另一个进程抢占
僵死状态 进程已经结束,但记录还在

1.3进程的调度
实际上 CPU 只能同时处理一个进程的工作。也就是说,进程并不是真的同时都在运行。
Linux 是一个 分时 操作系统。在同一时刻,只有一个进程得到 CPU 的处理,但很快就会变成另一个进程,如此往复。虽然每个进程一次只占用 CPU 很短的一段时间,但是每个进程总是很快就能再一次占用 CPU,所以这些进程看起来就好像一直都在运行一样。

CPU 在工作时就好像人体的循环系统一样。心脏的跳动维持血液的流动;晶振的振荡维持 CPU 的运行。心脏一旦停止跳动,血液也就停止流动;晶振一旦停止振荡,CPU 也就停止工作。
晶振在单位时间内的振荡次数称为 时钟频率,两次振荡之间的时间间隔称为 时钟周期。进程占用 CPU 的时间应该以时钟周期为单位来计量。

1.4多进程优缺点
优点:
a,顺序程序的特点:具有封闭性和可再现性。
b,程序的并发执行和资源共享。多道程序设计出现后,实现了程序的并发执行和资源共享,提高了系统的效率和系统的资源利用率。
c,每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系; 通过增加CPU,就可以容易扩充性能; 可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系; 每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大 。

缺点:
a,操作系统调度切换多个线程要比切换调度进程在速度上快的多。而且进程间内存无法共享,通讯也比较麻烦。
b,线程之间由于共享进程内存空间,所以交换数据非常方便;在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

2.多线程
进程进一步细分,就是线程。每一个进程都至少有一个线程,这个线程称为 主线程。主线程就是运行主函数 main() 的线程。创建线程相当于调用一个函数,只不过原来的线程会立即执行后续的代码而不等待这个函数返回。这使得被调函数中的代码和后续的代码是并行执行的。因此,可以简单地认为多线程就是同时运行多个函数。

2.1线程创建
线程通过调用 pthread_create() 函数创建。函数原型如下:

int pthread_create(pthread_t * id, pthread_attr_t * attr, void * (* start_routine)(void *), void * arg);

其中:
第一个参数要求一个 pthread_t 变量的地址。这个变量用来保存线程的标识符

第二个参数要求一个 pthread_attr_t 结构的地址。这个结构用于设定线程的一些属性,一般设为 0

第三个参数要求一个函数。创建的线程将调用这个函数。这个函数称为 线程函数。线程函数必须以一个 void 指针为参数,返回值也必须是一个 void 指针

第四个参数是一个 void 指针,它将会作为线程函数的参数。如果不需要传参,设为 0

如果线程创建成功,pthread_create() 函数将返回 0,否则返回要给错误代码。这些错误代码是线程库定义的一些常量,但没有一个是 -1。

2.2线程结束
线程调用 pthread_exit() 函数可结束自己,这个函数相当于结束进程的 exit(),唯一的参数是一个 void 指针,用来指向返回值。函数原型如下:

void pthread_exit(void * retval);

2.3等待线程结束
可以调用 pthread_join() 函数来等待另一个线程结束,其函数原型如下:

int pthread_join(pthread_t id, void ** retval);

其中:
第一个参数要求一个线程的标识符

第二个参数要求一个 void 指针的地址。这个指针将被指向线程的返回值。如果不需要得到线程的返回值,可设为 0。

如果顺利,pthread_join() 函数将返回 0,否则返回一个错误代码。

2.4线程脱离同步
pthread_detach() 函数用来使一个线程与其他线程脱离同步。脱离同步是指其他线程不能用 pthread_join() 函数来等待这个线程结束。这个线程将在退出时自行释放所占的资源。函数原型如下:

int pthread_detach(pthread_t id);

pthread_detach() 函数唯一的参数就是需要脱离同步的线程的标识符。如果顺利,将返回 0,否则返回一个错误代码。

2.5.多线程与多线程的优缺点
优点:
a,它是一种非常”节俭”的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。当然,在具体的系统上,这个数据可能会有较大的区别。
b,线程间方便的通信机制,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
c,使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
d,改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

缺点:
a,每个线程与主程序共用地址空间,受限于2GB地址空间。
b,线程之间的同步和加锁控制比较麻烦; 一个线程的崩溃可能影响到整个程序的稳定性; 到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows Server 2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数; 线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU。
c,调度时, 要保存线程状态,频繁调度, 需要占用大量的机时; 程序设计上容易出错(线程同步问题)。

你可能感兴趣的:(Linux)