多线程概念
想了解线程,前提是要了解进程 进程概念: 1. 从用户角度,进程就是一个正在执行的程序,就想你正在使用的csdn,属于一个进程 2. 从操作系统角度,对进程的描述就是一个pcb,这个描述是一个结构体(task_struct) task_struct{ //进程标识pid,内存指针,进程状态,上下文数据,程序计数器,记账状态,文件状态信息,优先级 }; 3. 操作系统对进程的管理:先描述再组织,操作系统在硬盘上,进程在内存中,cpu要处理程序, 因此要将程序先加载到内存中。因此进程就是pcb,cpu调度就是pcb 4. 当我们运行一个程序,这个程序被加载到内存中运行,那么这个时候操作系统创建一个进程, 实际就是创建了一个 PCB(task_struct)并且加入到PCB表(双向链表)来进行管理。关于线程的更多知识总结,在之前的博客中都有总结过
如果了解了进程,那么就很容易了解多线程了
多线程概念: 前提:Linux下没有真正的线程,因为Linux的线程是以进程的pcb模拟的,所以Linux下的线程也叫轻量级进程 简单的说,进程是由至少一个的线程的线程组组成的
线程是能够运行和cpu调度的最小单位,所以线程就是pcb是进程实际运作的单位是进程中单一顺序的执行流(例如,一个进程中的main函数,是由线程执行起来的)一个进程可以并发执行多个线程因为外部看到的pid实际上是task_struct中的tgid,而tgid是线程组id一个进程下回有至少一个进程在运行,所以进程间会有它们之间的共享资源和独有资源
例如学校里的某一个正在上课班级,至少会有一个学生,这些学生共享教室和老师等物品,但是他们会有独有物品 独有资源: 文件描述符表:也称io标识符表,线程可以根据文件描述符表找到对应的文件信号的处理方式:SIG_DFL,SIG_IGN或者自定义的信号处理方式用户id,组id和当前工作目录优点:由于资源共享,所以进程间通信变得极为简单
资源独有 栈优先级:线程优先级,针对轮询运行时的比较上下文数据:寄存器,存储文件中的数据线程标识符信号屏蔽字:每个线程都可以屏蔽想要屏蔽的信息error优点:由于资源独有,多线程可以同时运行,但是不会运行时造成调用栈混乱
优点: 1. 由于部分资源共享,多线程通信更便捷 2. cpu调度成本更低 3. 创建与销毁成本更低
缺点: 1. 缺乏访问控制,编码复杂度高,exit退出整个进行 2. 稳定性较差,某个线程出了错误就会导致整个进程的退出
线程属性:
为什么需要线程等待? 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。 创建新的线程不会复⽤刚才退出线程的地址空间 线程等待:等待指定线程退出,获取指定线程返回值,回收线程资源 一个线程被创建,默认有一个属性是joinable;处于这个属性的线程必须被等待,因为线程退出后,不会自动回收资源,造成内存泄露; int pthread_join(pthread_t tid,void **retval); tid:线程ID value_ptr:它指向一个指针,后者指向线程的返回值 返回值:成功返回0;失败返回错误码线程分离:线程退出后,不会保存返回值,直接回收资源,将属性从joinable改为detach int pthread_detach(pthread_t tid); tid:线程ID同步与互斥 同步: 对临界资源的的公平性保证,使得每一个线程访问的时序可控 互斥: 对临界资源的唯一性保证,使得同一时间只有唯一访问权
实现同步: pthread_cond_wait
实现互斥:使用互斥锁和互斥量
互斥锁: 实际就是一个计数器,但是这个计数器的值只有0/1 互斥锁也是一个临界资源,如何保证安全,意味着互斥锁的操作必须是原子操作 pthread_mutex_t mutex
互斥锁操作步骤: a.定义互斥锁变量 pthread_mutex_t b.初始化互斥锁变量 pthread_mutex_init c.头文件:#include <pthread.h> 互斥锁变量: int pthread_mutex_destroy(pthread_mutex_t *mutex);互斥锁初始化: int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); mutex:要初始化的互斥锁 attr:互斥锁属性,通常置NULL临界操作加锁 pthread_mutex_lock(阻塞加锁) pthread_mutex_unlock(解锁) pthread_mutex_trylock(非阻塞加锁) pthread_mutex_timelock(有时间限制的加锁) #include <pthread.h> //加锁 int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_timelock(pthread_mutex_t *mutex); printf("加锁之后要在所有可能return退出的地方解锁"); // d.临界操作完毕解锁 pthread_mutex_unlock pthread_mutex_unlock(pthread_mutex_t *mutex); // e.销毁互斥锁 pthread_mutex_destory pthread_mutex_destory(pthread_mutex_t *mutex);死锁:
产生的四个必要条件: a.互斥条件 b.不可剥夺条件(一个线程加的锁其他线程不可以释放) c.请求与保持条件(线程1已经获取锁1,但并没有释放锁1,保持锁1,就去请求锁2,) d.环路等待条件(在c的条件下,有另一个线程已经获取锁2,没有释放锁2,就去请求锁1,这时,线程1与线程2互相等待锁的释放,便会形成环路) 预防死锁:破坏其中的一个条件,预防c和d 注意加锁解锁的顺序必须保持一致 避免死锁:银行家算法,死锁检测算法