线程基本概念:
1 线程
(1)概念:linux下没有真正的线程,所谓的线程都是通过进程的pcb模拟的,因此linux下的线程也称为“轻量级进程”,之前我们所说的进程现在看来,可以理解为:只有一个线程的线程组。也可以将线程理解为:在一个程序里的一个执行路线,更准确的来说,线程是一个进程内部的控制序列
(2)进程:至少有一个线程的线程组
(3)linux下的pcb是task_struct,task_struct中的tgid是线程组id,也等于第一个进程的pid,如果再创建新的线程,这些线程的tgid会相同
2 线程和进程
(1)进程是资源竞争的基本单位
(2)线程是程序执行的最小单位
(3)线程共享进程的数据,比如进程的正文段,数据段,文件描述符(代表一个线程打开文件,另一个线程也可以拿着文件描述在安全的前提下进行操作),信号的处理方式,当前工作目录,用户id,组id。但线程也有自己独有的资源:栈,上下文数据,errno全局变量,信号屏蔽字,调度优先级等等。(注:这些独有的数据都存放在虚拟地址空间的共享区)
(4)进程和线程的关系如下图:
3 线程的优点:
(1)创建一个线程的代价要比创建一个线程的代价小的多
(2)与进程之间的切换先比而言,线程之间的切换需要操作系统做的工作很少
(3)线程占有的资源要比进程小很多
(4)能充分利用多处理器的可并行数量
(5)在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
(6)线程多用于cpu密集型与I/O密集型程序
4 线程的缺点:
(1)性能损失:在有的情况下会增加额外的调度与同步开销(比如有的操作无法与其他线程共享处理器,当这种操作的数量大于处理器的数量时,会产生这种情况)
(2)健壮性降低:线程切换之间缺乏安全保护,需要额外的操作保证线程对共享资源的安全访问
(3)缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响
(4)编程难度较高
线程控制
注意:我们所使用的是大佬们封装的一套接口,因此所使用的都是库函数,所以也说这套接口创建的线程是用户态线程,并且这个用户态线程在操作系统中对应了一个轻量级进程
1 线程创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
参数:第一个参数返回线程id,第二个参数表示要设置线程的属性,第三个参数表示线程启动后要执行的函数,第四个参数表示创个线程启动函数的参数
返回值:成功返回0,失败返回错误码
2 线程终止
void pthread_exit(void *value_ptr);
参数:输出型参数,带出线程退出时的返回值,不能讲局部变量的地址传给它;
无返回值
int pthread_cancel(pthread_t thread);
功能:同一进程中的其他线程,可以取消另一个线程
参数:线程id
返回值:成功返回0,失败返回错误码
3 线程等待与分离
(1)需要线程等待的原因:已经退出的线程,其空间没有释放,仍然在进程地址空间中,创建新的线程不会覆盖退出线程的空间,因此为了避免内存泄漏,需要线程等待,获取线程的退出状态,并且允许系统回收资源
(2)int pthread_join(pthread_t thread, void** value_ptr)
参数:第一个参数:表示需要等待线程的线程id,第二个参数是输出型参数,指向一个指针,表示线程的退出状态返回值,传NULL表示不关心返回状态
返回值:成功返回0,失败返回错误码
注意:只有线程处于joinable状态,这个线程才能被等待,此状态是线程的默认属性;除此之外,还有detach状态,此状态表示线程退出后直接释放资源,线程无法被等待。线程的这两个属性是互斥的,二者只能选其一
(3)分离线程:int pthread_detach(pthread_t thread)
int pthread_detach(pthread_self())
线程的同步与互斥
大部分情况下,线程使用的数据都是局部变量,变量的地址空间在线程地址空间中,这种情况下,变量归单个线程,其他线程无法获得这种变量;但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互;多个线程并发的操作共享变量,会引发一些问题;因此需要通过同步与互斥实现一定的保护机制
1 互斥量
互斥:同一时间理解资源的唯一访问性
互斥锁是原子操作
(1)定义胡互斥锁变量:pthread_mutex_t mutex
(2)初始化变量:pthread_mutex_init(&mutex)
(3)临界资源加锁/解锁
pthead_mutex_lock(&mutex)
pthread_mutex_unlock(&mutex)
pthread_mutex_trylock(&mutex):非阻塞
pthread_mutex_timelock(&mutex)
(4)销毁互斥锁:
pthread_mutex_destory(&mutex)
2 条件变量
当一个线程互斥的访问某个变量时,他可能发现在其他线程改变状态之前什么也做不了:例如当一个线程访问队列时,发现队列为空,它只能等待其他线程将一个节点添加到队列中,这种情况下需要条件变量
(1)初始化
pthread_cond_init(&cond)
(2)销毁
pthread_cond_destory(&cond)
(3)等待条件满足
pthread_cond_wait(&cond,&mutex)
注意:为了保护条件的修改,所以我们使用了互斥锁进行保护,但是如果加锁之后条件不满足而陷入休眠,则造成死锁,因此在休眠之前先解锁,但是解锁和休眠应该是原子操作,,否则也可能会诱发死锁的情况出现
(4)唤醒等待
pthread_cond_signal(&cond) 只通知一个
pthread_cond_broadcast(&cond) 唤醒所有等待的线程
3 死锁产生的必要条件
(1)互斥条件
(2)不可剥夺条件
(3)请求与保持条件
(4)环路等待条件
4 预防死锁
破坏其中的一个条件
银行家算法
死锁检测算法