进程和线程及Linux下的编程

时间:2022-09-09 18:24:14

一、概述

  进程和线程网络上有一堆解释,我不喜欢抄袭,也不喜欢套用太教科书的说法。就以我自己的理解来说说进程和线程吧,当然自己的理解肯定不是很严谨,但是理解起来应该会比教科书快一点。进程和线程都可以认为是并发执行程序,但是只有多处理器下的多线程才可以真正实现并发(多个线程在同一个时间片同时运行),其他的实际上并不是真正的并发,都是交替在cpu上运行,只是每个程序运行的时间很短(时间片),快速的交替,所以看上去就是同时在运行(并发)。

  几乎是同样的效果,为什么又分为进程和线程呢?进程和线程最大的区别在于,进程与进程间互不影响,他们拥有各自的完整的虚拟内存,是一个完整的个体。而线程存在于进程中,一个进程可以有很多线程,同一个进程下的线程具有很多共享的内存,比如文件描述符,全局变量等,也有他们私有的数据,如栈区的内存等。所以创建一个线程的开销(占有的资源)远远小于一个进程,这也就是为什么分进程和线程的原因 。

  所以总结一下程序,进程线程的关系吧。一个运行的程序可以有多个进程,一个进程下面可以有多个线程。这就是这三者的关系。要想更加深入地理解这个概念,还是得通过编程去理解。因为理论和实践的关系,马克思已经讨论地不要不要的。

 

二、概念

  进程号:用于标记一个进程的一个编号。

  僵尸进程:子进程已经结束,但是父进程并没有回收其资源,此时的子进程就是一个僵尸进程。使用wait和waitpid函数可以解决这个问题

  孤儿进程:父进程已经结束,但是子进程仍在运行。这时候子进程就是孤儿进程

  守护进程:一种脱离终端,在后运行的一种进程。终端的开关与他无关。

  PCB:进程控制块。存放进程的管理和控制信息的数据结构。它是进程管理和控制的数据结构,每一个进程均有一个PCB,在创建进程时,建立PCB,伴随进程运行的全过程,直到进程结束和结束。

  进程的状态:三态模型:就绪,运行,阻塞(睡眠)

        五态模型:略

三、套路

  ①进程:fork()创建新进程---->通过fork的返回值分别运行父子进程---->分别写父子进程程序---->父进程等待子进程结束回收资源

  ②线程:套路一:创建一个新的线程---->进入线程函数执行---->主线程等待回收子线程

      套路二:创建一个新的线程---->分离线程---->两个线程毫无瓜葛

四、进程相关函数

1、头文件

  <unistd.h>,<sys/type.h>,<sys/wait.h>

2、shell下查看进程命令

  #ps查看当前进程  #ps -ef  #ps -ax查看所有进程

  参数:STAT:当前进程状态;  TTY:进程从哪个终端启动;  CMD:启动进程所用的命令

3、进程号的类型

  pid_t 

4、获取进程号

  pid_t getpid(void)  获取当前进程进程号

  pid_t getppid(void)  获取父进程号

  pid_t getgdid(void)  获取组进程号

5、启动新进程

  ①通过调用system函数来完成,如system(“ps &”),但是这种启动依赖于shell

  ②通过复制当前进程来新建

  pid_t fork(void)  

  子进程从fork函数后开始执行。这是一个特殊的函数,有两个返回值,子进程返回0,父进程返回子进程的PID(进程号)。子进程中大量的数据都是复制的。比如文件描述符,进程上下文等。只有进程号,计时器等少量是子进程所独有的。

6、进程的替换

  因为通过进程的复制得到一个新的进程,但是我们往往生成一个进程需要执行新的任务,所以需要替换掉复制过来的进程,让他去执行我们想要的程序。用exec系列函数来进行替换进程。

  int execl(const char *path, const char *arg, ...,NULL);
  int execlp(const char *file, const char *arg, ...,NULL);
  int execle(const char *path, const char *arg,..., NULL,char * const envp[]);
  int execv(const char *path, char *const argv[]);
  int execvp(const char *file, char *const argv[]);
  int execvpe(const char *file, char *const argv[],char *const envp[]);

参数:path路径(包括可执行的文件名)  file当前路径可执行的文件名  argv可执行文件选项,例argv={"ps", "ax", 0}注意最后要加0  

   envp环境变量,默认寻找的路径。例:wxvp[]={"PATH = /bin:/user",0}冒号的作用是分隔,表示多个默认路径

7、进程的挂起(休眠,阻塞)

  unsigned int sleep(unsigned int sec)

  两种方式解除挂起:指定的时间到了或者收到信号

8、进程的等待(也是挂起)

  作用是等待子进程结束并回收子进程的资源,避免生成僵尸进程

  ①pid_t wait(int *status)  

  参数:status,存放子进程状态的内存  可以用三个宏来读取status的状态WIFEXITED(status)子进程正常结束则为非0  WEXITSTATUS(status)正常结束则返回退出码  WIFSTOPPED(status)意外终止则为非0

  如果仅仅是回收资源,则wait(NULL);

  ②pid_t waitpid(pid_t pid, int *status, int options)

  等待相应PID子进程结束回收空间

  参数:status 等同于上个函数的status

     pid:>0时  =-1时  =0时  <-1时

     options:不想使用时置0

五、线程相关函数

   1、头文件

    <pthread.h>编译时要加-lpthread选项来连接库

  2、线程号类型

  pthread_t

  3、创建线程函数

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

  参数:thread存数新线程号的内存  attr线程属性结构体地址,默认用NULL  start_toutine线程函数入口  arg传给线程函数的数,不传值为NULL

  返回值:成功0  失败非0

  4、阻塞线程函数

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

  作用:等待子线程结束后,主线程再退出。因为主线程退出后,子线程会全部结束

  retval线程函数的返回值

  返回值:成功0  失败:非0

  5、分离线程函数

  int pthread_detach(pthread_t thread);

  作用:主线程结束会默认结束所有子线程。用这个函数后,主线程结束后不再结束子线程

  返回值:成功0  失败:非0

  6、线程退出函数

  void pthread_exit(void *retval);

  retval存储线程函数数值的变量地址

  返回值:成功0  失败:errno

  7、取消一个正在执行的线程

  int pthread_cancel(pthread_t thread);

  可以取消一个线程。线程默认是可以被取消的,但是也可以设置成不可以被取消

  8、设置线程是否可以被取消

  ①是否可取消int pthread_setcancelstate(int state, int *oldstate);

  state:设置是否可取消的状态PTHREAD_CANCEL_ENABLE可取消  PTHREAD_CANCEL_DISABLE不可取消

  ②是否立即取消int pthread_setcanceltype(int type, int *oldtype);

  PTHREAD_CANCEL_DEFERRED不立即取消,直到取消点取消(取消点包括printf等)  PTHREAD_CANCEL_ASYNCHRONOUS立即取消

  返回值:成功0  失败:errno

  9、线程清理函数

  void pthread_cleanup_push(void (*routine)(void *),void *arg);

  void pthread_cleanup_pop(int execute);

  10、初始化描述线程属性的变量

  int pthread_attr_init(pthread_attr_t *attr);

  参数:attr用于描述线程的属性

  11、对线程变量的清理回收

  int pthread_attr_destroy(pthread_attr_t *attr);