《一》概念
进程:进程是资源分配的最小单位,进程是运行中的程序 系统通过进程控制块(PCB)来管理进程,操作系统通过双向循环链表来管理所有的PCB变量
线程:线程是进程内部的一条执行序列, 一个进程至少有一个线程: 主函数的执行序列, 将其称之为主线程。 可以通过线程库创建其他的线程, 创建的线程称之为函数线程。线程是 CPU 调度的最小单位。
线程依赖于进程而存在: 如果进程结束, 进程内部的所有线程都会终止。
《二》创建方式
创建进程的方法:1:fork()函数创建一个新进程 2:vfork()函数创建一个新进程 3:clone()创建新的进程
3种方式区别:
1:fork创建的子进程只是把父进程的数据段和代码段拷贝一份
vfork创建的子进程和父进程共享数据段(共享地址空间)
2:fork创建的子进程和父进程的运行顺序不一定
vfork创建的子进程先运行完后父进程才可以运行,且子进程必须显示调用exit()来结束否则将不能结束
3:clone带有参数,是将父进程资源有选择的复制给子进程,没有复制的通过指针的复制让子进程共享,函数的返回值是子进程的PID
创建线程的方法:
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);
《三》释放方式
释放进程:在进程的释放过程中可能会产生僵尸进程和孤儿进程
僵尸进程产生的原因:
1:进程主体释放,但PCB未释放
2:子进程结束,父进程未结束,并且父进程未获取子进程 的退出状态
僵尸进程的解决方法:
1、 结束其父进程。
2、 父进程获取子进程的退出状态: 在父进程中调用wait()函数,但是wait 函数会阻塞运行, 直到第一个子进程退出。
3、在子进程结束时,子进程给父进程发一个信号(告知父进程我已经结束了)父进程接收到信号后在对子进程做处理。
孤儿进程产生的原因:父进程结束, 子进程依旧存在。 那么子进程就被称为孤儿进程。 系统会将所有的孤儿进程挂载到 init 下。 init 进程的 pid = 1
释放线程:<主线程结束最后默认会调用 exit 函数,而exit 函数用于结束整个进程会导致没有执行完的函数线程也被迫结束,所以我们要确保函数线程和主线程都要正常释放>
1:pthread_exit():在进程主线程中调用pthread_exit(),只会使主线程退出;而如果是return,编译器将使其调用进程退出的代码(如exit()),从而导致进程及其所有线程结束运行。
2: int pthread_join(pthread_t thread, void **retval);这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回0,如果失败则返回一个错误号。(使函数线程先退出,主函数再退出)
《四》数据共享问题
进程的数据共享问题:.data .bss .text .heap的数据都不共享 ,但是进程之间共享 fork 之前打开的文件描述符, 并且进程共用文件读写偏移量。
线程的数据共享问题:线程之间除了栈区, 其他空间都是共享的。
《五》通讯方式
进程间的通讯方式:管道 消息队列 信号 套接字 共享内存
线程间的通讯方式; 因为线程间的数据都是共享的<除了栈区数据>,所以进程之间的通讯比较简单
《六》同步控制
进程间的同步控制:信号量
线程间的同步控制: 信号量 互斥锁 条件变量
1:信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int shared, unsigned int val);
int sem_wait(sem_t *sem); // P 操作
int sem_post(sem_t *sem); // V 操作
int sem_destroy(sem_t *sem); // 销毁信号量对象
2、 互斥锁
互斥: 访问的临界资源只有唯一的一份。
互斥锁控制的是线程之间访问唯一临界资源的一种方式。
#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3、 条件变量
《七》线程安全:同一个进程中的线程之间共享全局、 静态、 堆区数据, 如果两个以上的线程同时操作同一个共享的变量, 则会导致执行的结果与预期的不同, 这就称之为线程安全
不可重入版本的函数:char * strtok(char *buff, const char *flag); // 不保证线程安全的
可重入版本的函数:char* strtok_r(char *buff, const char *flag, char **saveptr);//保证线程安全版本
《八》线程与fork
在线程创建之后, 任意一个线程调用 fork 函数创建子进程, 子进程只会启动调用fork 函数的那个线程, 而其他线程并不会启动。
《九》线程切换对于进程切换速度更快,效率更快
因为部分对于进程不共享的数据 线程有些是共享的 可以直接用。