Linux — 多线程

线程概念

Linux下没有真正的线程,因为Linux下的线程是用进程pcb模拟的(也就是说Linux下pcb实际上是一个线程),所以Linux下的线程也被叫做轻量级进程。既然Linux下pcb成了线程,那么进程变成了线程组
在这里插入图片描述
线程是进程的一条执行流,Linux下线程是以进程的pcb模拟的,所以才说Linux下的线程是轻量级进程,因此Linux下的线程是cpu调度的基本单位
Linux下的进程成了线程组,进程id==线程组id,所以才说Linux下的进程是线程组,资源分配给整个线程组的,所以进程是资源分配的基本单位,并且进程中的线程共享大部分进程的资源。

线程共享

  • 文件描述符表
  • 用户id、组id
  • 信号处理方式
  • 工作路径(当前工作目录)
  • 共享虚拟地址空间——共享地址段和代码段

线程独有(相对独有——因为数据还是在虚拟地址空间)

  • 栈区
  • 上下文数据
  • 线程id
  • errno
  • 信号屏蔽字
  • 一组寄存器

线程的优缺点

优点:
linux下的线程共用进程的虚拟地址空间

  • 线程的创建/销毁成本更低——不用每次都创建虚拟地址空间
  • 线程的调度切换成本更低
  • 线程间的通信更加方便
  • 线程的执行力度更加细致

缺点:

  • 缺乏访问控制——线程安全
    • 有些系统调用和程序异常时针对整个进程产生影响的
    • 多个线程对临界(公共)资源进行操作有可能会造成数据混乱

线程控制

线程创建

pthred_create

int pthred_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg)
	thread    //获取线程id(用户态线程id)    无符号长整形
	attr    //设置线程属性,通常置空
	start_routine    //线程的入口函数(线程所运行的代码)
	arg    //线程入口函数的参数
	返回值    //成功返回0,失败返回错误码

通过参数返回一个用户态的线程id,这个线程id是线程地址空间在进程虚拟地址空间的首地址,用户态线程的操作都是围绕这个用户态的线程id来操作的。
每一个线程都是一个task_struck,也就意味着每个线程都有一个pid,但是ps命令只能显示一个。task_struct中不仅有pid还有一个tgid(线程组id==线程组领导者的pid),那么ps看到的pid实际上是这个tgid(线程组id),因此我们说linux下的进程称为的线程组。
查看线程信息可以使用 ps-L命令, LWP这一项显示的就是线程pcb中的pid。

  • tid 线程id
  • pid 进程id
  • tgid 线程组id(主线程的pid)

线程终止

不能再main函数中return,不能调用exit函数,因为这两个都是退出进程的,进程推出了,所有的线程都得退出。

pthread_exit

void pthread_exit(void *value_ptr)   //退出调用线程
	//value_ptr:value_ptr不要指向一个局部变量。 
	//返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

pthread_exit(NULL);

pthread_cancel

void pthread_cancel(pthread_t thread)  //退出指定线程
	//thread:线程ID 
	//返回值:成功返回0;失败返回错误码

pthread_cancel(tid);

线程等待

  • 获得其他普通线程的退出返回值,避免产生僵尸线程。
  • 获得退出线程的返回值,并且允许操作系统回收线程资源。

线程等待的前提: 只有线程处于joinable状态(线程默认属性),这个线程才能被等待。

pthrad_join

int pthrad_join(pthread_t thread, void **value_ptr)	
	//thread    线程id
	//value_ptr    用于获取一个线程退出的返回值   
//阻塞性函数,如果线程没有退出就一直等待

线程分离

默认情况下,新创建的线程是joinable状态的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程
资源。

pthread_deatch

int pthread_detach(pthread_t thread)	//设置线程分离属性(deatch)
	//thread    指定要分离的线程id
	//分离指定的线程,被分离的线程退出时自动回收资源
    //处于detach状态的线程退出后自动回收资源,不会保存返回值,所以不需要被等待

线程安全

因为线程是cpu调度的基本单位,因此多个线程可能会同时争夺对临界资源的操作,多个线程因为临界资源的争抢写入操作会导致程序逻辑的混乱/数据的二义性;同时,线程本身就是因为通信方便以及成本低而广为使用,这样就无法避免大量临界资源的争抢操作,这时候就必须要考虑如何保证线程安全。

线程间的同步与互斥

互斥: 任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源。
同步: 让数据的访问更加具有时序的可控性,保证对临界资源的时序可控性。

互斥锁 — 互斥

大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。但有时候,很多变量都需要在线程间通过数据的共享,完成线程之间的交互,这样的变量称为共享变量。而多个线程并发的操作共享变量,会带来一些问题。因此,我们需要一扇门,来保证只有一个线程进入临界区,Linux下,为我们提供了一种方式,称为互斥锁。

初始化互斥锁变量

pthread_mutex_t mutex;   //互斥锁变量

//01、
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

//02、
pthread_mutex_init();    //初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
	//mutex   互斥量
	//attr    属性

加锁

pthread_mutex_lock    //阻塞加锁,获取不到一直等待
int pthread_mutex_lock(pthread_mutex_t *mutex);
	//成功返回0, 失败返回错误信号

pthread_mutex_trylock();    //非阻塞加锁,获取不到直接退出

pthread_mutex_timedlock();    //限时阻塞加锁

解锁

pthread_mutex_unlock();    //解锁
//在任何有可能退出的地方都必须解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
	//成功返回0, 失败返回错误信号

释放互斥锁

pthread_mutex_destroy    //释放
int pthread_mutex_destroy(pthread_mutex_t *mutex);
条件变量 — 同步

满足操作条件,才可以操作,不满足需要等待,而条件满足就需要对其他线程修改条件,并通知一下等待的线程。

初始化条件变量

pthread_cond_t  cond    //条件变量

//01、
pthread_cond_t  cond  = PTHREAD_COND_INITIALIZER;

//02、
pthread_cond_init    //初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
	//cond    条件变量
	//attr    条件变量的属性,通常置空

等待

pthread_cond_wait();    //等待
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex)      
	//cond    要在这个条件变量上等待    
	//mutex:互斥量
	///这个函数需要在挂起之前对互斥锁进行解锁操作
	//这个时候解锁操作和挂起操作必须是一个原子操作,否则不安全
	//条件变量中的解锁和休眠操作必须是原子操作,并且在被唤醒后会将互斥锁的值修改为0
	//条件变量的判断必须是一个循环,否则如果有多个线程被同时唤醒,则对临界资源的访问不安全
pthread_cond_timedwait(); //限时等待

通知

pthread_cond_signal    //通知(通知等待在条件变量上的线程/进程)
int pthread_cond_broadcast(pthread_cond_t *cond);   
int pthread_cond_signal(pthread_cond_t *cond);

销毁条件变量

pthread_cond_destroy();    //销毁
int pthread_cond_destroy(pthread_cond_t *cond);
死锁

一个程序一直获取不到锁,因此一直处于卡死状态就称为死锁
死锁产生的四个必要条件

  • 互斥条件
    • 只有一个能够获得锁
  • 不可剥夺条件
    • 使用时别人不能释放
  • 请求与保持条件
    • 保持第一个,请求第二个,如果拿不到,也不释放第一个
  • 环路等待条件
    • A拿到第一个请求第二个,B拿到第二个请求第一个

预防死锁

  • 打破死锁条件
  • 加锁顺序一致
  • 避免锁为释放
  • 资源一次性分配

避免死锁算法

  • 死锁检测
  • 银行家算法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值