1.进程与线程主要区别
文章后面也有区别的讲解,这里做一个大体了解,有个概念性的认识。
Linux 进程和线程都是计算机执行程序的基本单位,但是它们有一些区别。下面是它们之间的主要区别:
资源占用
Linux 进程是独立的程序执行实例,拥有自己的地址空间、文件描述符、进程ID等系统资源,因此会占用更多的系统资源。而线程是进程内部的执行流,它们共享进程的地址空间和其他系统资源,因此占用的资源要比进程少得多。
调度
Linux 进程由调度程序调度执行,进程之间需要进行上下文切换,这会带来一定的开销。而线程是由内核的调度程序在进程内部调度执行的,因此不需要进行上下文切换,线程的切换开销比进程要小得多。
通信和同步
Linux 进程之间的通信和同步需要使用进程间通信机制(如管道、信号、共享内存等),因为它们之间是相互独立的。而线程之间可以直接共享进程的地址空间,因此它们之间的通信和同步要比进程更加高效。
安全性
由于每个进程都有自己独立的地址空间和系统资源,因此进程之间相互隔离,一个进程的错误不会影响其他进程。而线程共享进程的地址空间和资源,因此一个线程的错误可能会影响整个进程。例如,如果一个线程写入了一个错误的地址,会导致整个进程崩溃。因此,在使用线程时需要特别注意线程之间的数据访问和同步,以避免线程安全问题。
另外,由于线程共享进程的地址空间,因此线程之间的调用比进程更加快速,但同时也需要更加小心,以避免一个线程修改了另一个线程正在使用的数据。因此,线程编程需要注意避免数据竞争和锁定问题。
总的来说,安全性是进程和线程之间的一个重要区别,进程的隔离性更好,但是线程的通信和执行效率更高,开发者需要根据具体需求来选择使用进程或线程。
灵活性
进程比线程更加灵活。由于每个进程都是独立的程序实体,因此可以独立地运行、停止、调试和管理。而线程是在进程内部执行的,它们共享进程的资源,因此线程之间的状态和行为更加紧密相关。
创建和销毁
创建和销毁线程比进程要快得多。这是因为线程是在进程内部创建的,它们共享进程的资源,因此创建和销毁线程的开销要比创建和销毁进程小得多。另外,线程的创建和销毁也不需要像进程那样需要重新分配和释放系统资源,因此开销更小。
调试
在调试时,进程比线程更容易处理。由于进程是相互独立的,因此可以分别调试每个进程,而线程共享进程的地址空间和资源,因此在调试线程时,需要特别注意线程之间的交互和影响,这使得调试线程变得更加困难。
可扩展性
由于线程共享进程的资源,因此它们比进程更容易扩展。在多处理器系统中,多线程程序可以更好地利用系统资源,从而提高程序的性能和可扩展性。而进程之间的通信和同步需要使用额外的系统资源,因此在多处理器系统中,进程之间的通信和同步可能会成为瓶颈,限制系统的可扩展性。
2 进程函数用法及讲解
2.1 进程的创建和终止
2.1.1 fork
#include <sys/types.h>
#include <unistd.h>pid_t fork(void);
fork的返回值问题:
在父进程中,fork返回新创建子进程的进程ID;
在子进程中,fork返回0;
如果出现错误,fork返回一个负值;
getppid():得到一个进程的父进程的PID;
getpid():得到当前进程的PID;
*注意:在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
2.1.2 exit
进程退出一般有三种方法:
1、在main函数中使用 return关键字
,使用 return
后系统会调用 exit()
函数来终止进程。
2、手动调用 exit()
来终止进程。
3、调用 _exit()
来终止进程。
_exit会立刻结束进程将缓存释放掉,而exit先查看当前进程有没有文件缓存区,如果有,会先处理缓存区的数据,然后释放内存,下面这张图是一个很好的解释:
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void exit(int status);
exit(0):表示运行正常结束进程;exit(1):表示异常退出,返回值1是给操作系统的
注意return 是关键字,是语言级别,表示调用堆栈的返回,并将控制权移交给控制的前一级;exit是函数,表示系统级别,表示进程的结束。
2.1.3 wait
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *wstatus);
函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。返回值:正常情况下,wait的返回值为子进程的PID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD
注:
当父进程忘了用wait()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程.
如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID 1)继承,当子进程终止时,init进程捕获这个状态.
参数解释:
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就像下面这样:
pid = wait(NULL);
如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中, 这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息 被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:
1,WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数–指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。)
2, WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。
/*
下面的程序没有调用wait函数,那么子进程会成为无父进程状态,也就是僵尸进程,可以根据程序运行后
所打印的PID,然后命令行输 ps -aux 查看进程状态,
发现这个子进程依然存在,所以要调用wait来进行所谓的收尸
*/
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
pid_t pid;//定义fork的返回值,存储在相同类型的变量pid中
//如果创建成功,在父进程中fork返回新创建的子进程的进程号,在子进程中返回0
printf("hello world\n");
pid=fork();//fork创建子进程,将返回值给pid
if(pid<0)//如果出错,fork返回一个负值并设置错误码
perror("fork error");
else if(pid==0)//判断pid==0就是子进程
printf("I am child,my pid:%d\n",getpid());//getpid得到当前进程的进程号,getppid()是得到父进程的进程ID
else if(pid>0)//如果pid>0是父进程
printf("I am father,my pid:%d\n",pid);//
puts("hi ");//这里是父子进程都执行的部分,运行结果可以看到两个hi,
exit(0);
}
/*
下面的程序调用了wait来为孩子收尸,这样再ps -aux会发现进程结束,没有尸体残留
*/
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
pid_t pid;//定义fork的返回值,存储在相同类型的变量pid中
printf("hello world\n");
pid=fork();//fork创建子进程,将返回值给pid
if(pid<0)//如果出错,fork返回一个负值并设置错误码
perror("fork error");
else if(pid==0){//如果创建成功,在子进程中,返回的pid==0 是子进程
printf("I am child,my pid:%d\n",getpid());//getpid得到当前进程的进程号,getppid()得到父进程的进程号
printf("my father's pid:%d\n",getppid());
}
else if(pid>0){//如果pid>0是父进程,返回的pid 为创建的子进程的ID
printf("I am father,my son's pid:%d\n",pid);
printf("my pid:%d\n",getpid());
int status;
pid_t sonpid=wait(