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