多线程总结

线程

线程是什么?
线程是进程中一条执行流。进程是一个pcb(是一个运行中程序的描述,Linux下是一个task_struct结构体(描述了程序的描述信息,从而实现操作系统对运行中程序的调度),Linux下线程是一个task_struct结构体,是一个pcb,这些pcb在一个运行中程序中共用一个虚拟地址空间(公用同一份资源),相较于进程【传统pcb】更加轻量化,因此Linux下线程是一个轻量级进程
进程与线程间关系?
进程中有一个/多个线程,是一个线程组。(如工厂——机器之间的关系)
进程是资源分配的基本单位,线程是CPU调度的基本单位

多个线程公用一个虚拟地址空间,如何实现独立调度、不栈混乱?
线程数据的独有与共享【栈独有,堆共享】
线程间独有的数据:

  • 栈:避免栈混乱
  • 寄存器:每个pcb是一个执行流,保证执行流独立执行
  • 信号屏蔽字:阻塞自己想阻塞的信号
  • errno:每调用一次系统调用重置errno

线程间共享的数据:

  • 虚拟地址空间【代码段、数据段】
  • IO信息
  • 工作线路
  • 用户id
  • 组id

多线程、多进程都可以进行多任务处理,哪个更好?
多线程、多进程各有优缺点,观察应用场景决定使用哪个。
多线程任务处理:
优点:

  • 线程间通信更加灵活【除了进程间通信方式,换可通过全局变量、函数传参实现通信】
  • 线程的创建、销毁成本更低,共享进程大部分资源
  • 同一进程的线程间调度成本更低。【进程切换需要重新加载数据/页表信息…】
  • 线程执行粒度更加细致【精确到某函数】

缺点:

  • 线程间缺乏访问控制,有些系统调用【exit】或异常针对整个进程产生效果
  • 任务处理较多稳定性低

多进程的用用场景:对主程序稳定性、安全性要求高。eg:shell、网络服务器

多执行流任务操作的优势?
多进程/线程进行并发/并行处理:

线程控制

Linux下操作系统并没有给用户提供创建线程的系统调用接口,无法直接创建线程。但封装了一个线程库,通过线程库实现线程控制的各种操作。

线程创建
int pthread_create(pthread_t *tid, const pthread_attr_t *attr,void *(*start_routine) (void *arg), void *arg);
tid:每个线程在进程虚拟地址空间中都有一个相对独立的空间,返回的tid就是这个空间在虚拟地址空间中的首地址【向用户返回线程id,后续线程的操作句柄】
attr:设置线程属性,通常置为NULL
start_routine:线程的入口函数
arg:最终通过线程入口函数的参数传递给线程的数据
返回值:成功返回0;失败返回非0值的一个错误编号:

  • EAGAIN:创建数达上限
  • EINVAL:属性无效
  • EPERM:属性权限

pthread_pthread_self(void):获取当前调用线程的id

ps -l
#查看线程【轻量级进程】信息

各种id
tid——线程空间在虚拟地址空间中的首地址
PID——线程组id(tgid-mainthread->pcb->pid)
LWP——每个线程pcb的pid(pcb->pid)
线程终止
线程主动退出:

  1. 在线程入口函数中,调用return; (main函数中的return, 退出的并不是主线程,而是进程)
  2. void pthread_ exit(void *retval); (main主线程中调用, 主线程退出,进程并不一定退出,所有线程退出,进程才会退出)

线程被动取消:
int pthread_cancel(pthread_t tid);//取消一个正在运行的线程

线程等待
等待一个指定线程的退出;获取这个退出线程的返回值;并且允许系统回收这个线程占用的资源。注意:并不是所有的线程都需要被等待,线程有一个属性。默认叫joinable,处于这个属性的线程,退出后不会自动回收资源,需要其它线程进行等待处理
操作:
int pthread join(pthread _t tid, void **reval);//等待线程退出并获取返回值

  • tid:用于指定要等待的线程
  • retval:输出性参数,返回线程的退出返回值

等待线程退出,joinable这个属性的线程,不会自动回收资源,过程等待线程退出,获取返回值,释放资源。
线程分离
不想获取返回值的情况下,直接释放资源,不去等待。
线程分离就是设置线程属性,从joinable设置为detach, 处于detach属性的线程退出后,会自动释放资源(这种线程不需要被等待)
pthread_ join是线程等待——阻塞函数(如果线程没退出,就一直等待)

操作:
int pthread_detach(pthread_t tid);分离一个指定的线程——设置这个指定线程的属性为detach。

pthread_detach(pthread_self());

分离一个线程,可以在任意位置,任意线程中完成。

线程分离是在干什么?
设置属性
被分离的线程有什么效果?
退出后自动释放资源,不需要被等待

线程安全

多个执行流对临界资源进行争抢访问,而不会造成数据二义或者逻辑混乱。称这段争抢访问的过程是线程安全的
实现:如何保证多个执行流对临界资源进行争抢访问而不会造成数据二义

  • 同步:通过条件判断,实现对临界资源访问的时序合理性
  • 互斥:通过唯一访问,实现对临界资源访问的安全性
互斥的实现技术:互斥锁

实现互斥的原理:只要保证同一时间只有一个执行流能够访问资源就是互斥

对临界资源进行状态标记:没人访问的时候标记为1, 表示可访问;有人正在访问的时候,标记为0,表示不可访问
在对临界资源进行访问之前先进行状态的判断,决定是否能够访问,不能访问则使其休眠
能够实现互斥的技术中:互斥锁
互斥锁:其实就是一个计数器【只有0/1】,用于标记资源当前的访问状态
1——可访问
0——不可访问
互斥锁想要实现互斥,每个线程在访问临界资源之前都要先访问同一个互斥锁(加锁)
互斥锁本身就是一个临界资源(涉及到计数器的修改, 修改过程必须保证安全,如果连自己都保护不好,就不能保护别人)
互斥锁的计数器操作如何实现原子性?

  1. 将cpu寄存器上的值修改为0,然后与内存中计数器进行数据交换(意味着这时候计数器变为0,谁来访问,都是不可访问状态,无法进行操作,这时让寄存器判断是否可以访问)

  2. 若寄存器交换后数据为0,则表示当前不可访问,则将pcb状态置为阻塞状态,线程将被挂起等待;若寄存器交换后数据为1,则表示当前可以访问,则加锁操作直接返回,表示加锁成功——继续可以访问资源

  3. 访问完数据之后,进行解锁操作(将内存中计数器的值再修改回来)

互斥锁如何实现互斥?
通过在访问临界资源前先加锁,通过访问互斥锁进行状态判断是否可以加锁

互斥锁的操作
1.定义互斥锁变量
pthread_mutex mutex
2.初始化互斥锁
mutex=PTHREAD_MUTEX_INITIALIZER
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)
mutex:互斥锁变量首地址
attr:互斥锁属性-通常置NULL,
3.在访问临界资源前,先加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);//阻塞加锁【如果不能加锁,则一直等待】
int pthread_ mutex_trylock(pthread_mutex_t *mutex);非阻塞加锁【如果不能加锁,则立即报错返回,若可以加锁,则加锁后返回】
4.对临界资源访问后解锁——状态标为可访问1
int pthread_mutex_unlock(pthread_mutex_t *mutex);
5.使用完后释放资源。销毁互斥锁
int ptread_mutex_destroy(pthread_mutex_t *mutex);

注意
锁的初始化在创建流程之前
加锁后在任意可能退出线程的位置解锁(否则可能造成其他获取锁的线程卡死)
锁的销毁要保证没有人使用互斥锁
仅对临界资源访问部分进行加锁

死锁
多个执行流对锁资源进行争抢访问,但由于推进顺序不当导致互相等待,使得进程流程无法继续

死锁产生的必要条件

  • 互斥:同一时间仅一个线程可加锁
  • 循环等待(环路等待)
  • 不可剥夺:线程加的锁仅自己能解
  • 请求和保持:抢到锁1后去抢锁2,抢不到2也不释放锁1

预防死锁?
编程期间注意破坏必要条件
避免死锁?
死锁检测算法/银行家算法

同步的实现:条件变量

条件变量:实现同步的思路向用户提供两个接口+ pcb等待队列

  • 让线程陷入阻塞休眠的接口
  • 唤醒线程休眠的接口

同步
通过条件判断:什么时候能够访问资源,什么时候不能访问;若不能访问就要使线程阻塞:若能访问了就要唤醒线程。实现线程对临界资源访问的合理性

条件变量
只是向外提供了等待与唤醒的接口,却没有提供条件判断(条件变量本身并不具备判断什么时候该等待,什么时候该唤醒)的功能;意味着,条件判断需要用户自己来完成

操作流程
用户不能访问资源的时候调用接口陷入等待,其它线程产生资源,然后调用接口唤醒等待队列中的线程;是否能够访问的条件判断需要用户自己完成,并且需要互斥保护

  1. 定义条件变量
    pthread_cond_t cond

  2. 初始化条件变量
    pthread_cond_init(pthread_ cond_t *cond, pthread_condattr_t atr) cond=PTHREAD_COND_INITIALIZER
    条件变量要搭配互斥锁使用,且不提供条件判断,需要用户自己判断(判断条件常为临界资源的访问)
    pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *abstime);//限制等待时长的阻塞操作:等待-段指定的时间,时间到了调用就会报错返回——ETIMEDOUT

  3. 线程等待的接口
    pthread_cond_wait(pthread_cond_t *cond, pthread_mutex t *mutex);
    包含三步操作:解锁、休眠、被唤醒后加锁

  4. 线程唤醒的接口
    pthread_cond_signal(pthread_cond_t *cond)//唤醒至少一个等待的线程
    pthread_cond_broadcast(pthread_cond_t *cond)//唤醒所有等待的线程

  5. 不再使用销毁释放资源
    pthread_cond_destroy(pthread_cond_t *cond);

注意:条件的判断应该是while循环判断,防止多个资源被唤醒,拿到锁后要重新判断是否还有锁,没有则调用pthread_cond_wait进入休眠;有则加锁。
让不同的角色等待在不同的条件变量上,使得唤醒分明(多少种角色就有多少种条件变量)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值