操作系统与网络 (9. Linux多线程Thread)

时间是一个伟大的作者,它会给每个人写出完美的结局来!

9. Linux多线程Thread

9.1 线程初始
9.1.1 线程的基本概念

(1)线程是一个执行流,用于运行代码以及处理数据;
(2)Linux下的线程:用户态线程+轻量级进程;
用户态线程:使用库函数实现创建的线程,这个用户态线程在内核中使用一个轻量级进程实现调度;

9.1.2 线程与进程
  • 线程
    (1)Linux下线程是CPU调度的基本单位——因为Linux是线程;
  • 进程
    (1)Linux下进程是资源分配的基本单位——因为程序运行时资源势分配给整个线程组(进程)的;
    (2)传统操作系统中使用pcb来描述一个程序的运行——pcb就是进程,LInux下使用pcb来模拟实现线程,因此Linux下的pcb实际上是一个轻量级进程LWP(轻量级进程是因为公用大部分进程资源,相较于传统进程更加轻量化)。
9.1.3 线程的优缺点
  • 线程的优点
    (1)创建一个新线程的代价要比创建一个新进程小得多;
    (2)与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多;
    (3)线程占用的资源要比进程少很多;
    (4)能充分利用多处理器的可并行数量;
    (5)在等待慢速I/O操作结束的同时,程序可执行其他的计算任务;
    (6)计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现;
    (7)I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
  • 线程的缺点
    (1)性能损失(增加了额外的同步和调度开销,而可用的资源不变);
    (2)健壮性降低(线程之间是缺乏保护的);
    (3)缺乏访问控制(进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个
    进程造成影响);
    (4)编程难度提高;
    (5)编写与调试一个多线程程序比单线程程序困难得多。
9.1.4 线程之间资源的独有与共享
  • 独有
    栈、寄存器、、信号屏蔽字、errno、进程ID、调度优先级;
  • 共享
    共享虚拟地址空间、文件描述符表、信号处理方式、当前的工作路径、用户ID、组ID。
9.2 线程控制
9.2.1 线程创建

(1)功能
创建一个新的线程
(2)原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(start_routine)(void), void *arg);
(3)参数
thread: 返回线程ID
attr: 设置线程的属性,attr为NULL表示使用默认属性
start_routine: 是个函数地址,线程启动后要执行的函数
arg: 传给线程启动函数的参数
(4)返回值
成功返回0;失败返回错误码。

9.2.2 线程终止

1. pthread_exit
(1)功能
线程终止;
(2)原型
void pthread_exit(void *value_ptr);
(3)参数
value_ptr:value_ptr不要指向一个局部变量;
(4)返回值
无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)。
2. pthread_cancel
(1)功能
取消一个执行中的线程;
(2)原型
int pthread_cancel(pthread_t thread);
(3)参数
thread:线程ID
(4)返回值
返回值:成功返回0;失败返回错误码。
3. 线程终止的三种方法

  1. 从线程函数return;这种方法对主线程不适用,从main函数return相当于调用exit;
  2. 线程可以调用pthread_exit终止自己;
  3. 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
  4. 线程退出之后,默认不会自动释放资源(保存自己的退出结果在线程的地址空间中),会造成资源泄露;
  5. 主线程退出之后,其他线程依然可以正常运行。
9.2.3 线程等待

(1)功能
阻塞等待指定线程退出,通过retval获取返回值;
(2)原型
int pthread_join(pthread_t thread, void **value_ptr);
(3)参数
thread:线程ID;
value_ptr:它指向一个指针,后者指向线程的返回值;
(4)返回值
成功返回0;失败返回错误码。
(5)pthread线程一不同的方法终止,通过pthread_join得到终止状态不同即为:

  • 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值;
  • 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED。
  • 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数;
  • 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

(6)说明
一个线程创建出来之后,默认有一个joinable属性,处于joinable属性的线程退出之后,不会自动释放资源,需要被其他线程等待,才能释放资源;处于joinable属性的线程必须等待,否则造成资源泄露。

9.2.4 线程分离

(1)功能
将线程joinable属性修改为detach属性
(2)原型
int pthread_detach(pthread_t thread);
(3)说明

  1. 线程若属于detach属性,则线程将退出后将自动回收资源;
  2. 这个线程将不再需要等待(因为线程退出,返回值占用的空间已经被回收了);

(4)应用场景
可以在任意线程中实现(对线程的返回值并不关心)。

9.2.5 说明

(1)Linux下操作系统并没有提供线程的控制系统调用接口;
(2)因此大佬们封装了一个线程控制接口库;
(3)Linux的线程:用户态线程+轻量级进程。
(4)要使用这些函数库,要通过引入头文<pthread.h>;
(5)链接这些线程函数库时要使用编译器命令的“-lpthread”选项。

9.2.6 线程中ID的讨论

(1)tid——线程地址空间的首地址;
(2)pcb——pid(轻量级进程Id,LWP);
(3)pcb——tgid(线程、进程组id,默认等于首线程id)。

9.3 线程安全
9.3.1 概念

线程安全 : 多个线程同时对临界资源进行访问而不会造成数据二义性.
重入 : 同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入.
可重入函数 : 一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题;

9.3.2 线程安全的实现

(1) 同步

  • 概念
    保证对临界资源访问的时序合理性
  • 实现 (等待+唤醒)
    条件变量
    线程在对临界资源进行访问之前, 首先判断是否能够操作,若可以操作则线程直接操作; 若不可以操作, 则条件变量提供等待功能, 让pcb等待在等待队列上, 其他线程促使操作条件满足, 然后唤醒条件变量等待队列上的线程.
    条件变量 = 等待 + 唤醒 +等待队列
    信号量
    等待与唤醒的功能,不具备操作条件的时候则等待,其它线程促使条件满足后,唤醒等待的线程。

(2) 互斥

  • 概念
    保证对临界资源同一时间访问的安全性(唯一性).

  • 实现 (互斥锁)
    互斥锁

  • 原理
    通过对一个只有0/1的计数器的原子判断
    死锁

  • 概念
    指在一组进程中, 多个线程对多个锁资源进行争抢操作,但是因为推进
    顺序不当,造成环路等待导致程序流程无法推进.

  • 产生的必要条件

    • 互斥条件
      一个资源每次只能被一个执行流使用
    • 不可剥夺条件
      一个执行流已获得的资源,在末使用完之前,不能强行剥夺
    • 请求与保持条件
      一个执行流因请求资源而阻塞时,对已获得的资源保持不放
    • 环路等待条件
      若干执行流之间形成一种头尾相接的循环等待资源的关系

    预防死锁

    1. 破坏死锁的四个必要条件
    2. 加锁顺序一致
    3. 避免锁未释放的场景
    4. 资源一次性分配

    避免死锁算法
    银行家算法
    死锁检测算法

信号量

  • 具体实现
    1. 定义互斥锁变量
    pthread_mutex_t *mutex;
    2. 对互斥锁变量进行初始化
    静态分配
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
    动态分配
    int pthread_mutex_init(pthread_mutex_t restrict mutex,
    const pthread_mutexattr_t
    restrict attr);
    参数:mutex:要初始化的互斥量
    attr:NULL
    3. 对临界资源操作之前先加锁
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    返回值:成功返回0,失败返回错误号
    4. 对临界资源操作完毕后进行解锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    返回值:成功返回0,失败返回错误号
    5. 销毁互斥锁
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    使用PTHREAD
    MUTEX_ INITIALIZER初始化的互斥量不需要销毁;
    不要销毁一个已经加锁的互斥量;
    已经销毁的互斥量,要确保后面不会有线程再尝试加锁;
9.3.3 生产者与消费者模型(321)
  • 生产者消费模型优点
  1. 解耦和
    缓冲区作为中间过渡作用
  2. 支持忙闲不均
    缓存数据, 缓慢处理
  3. 支持并发
    多个生产者可同时向里面放置数据
  • 实现
  1. 一个场所
    缓冲区 (需要保证安全)
  2. 两类角色
    生产者, 消费者
  3. 三种关系
    生产则与消费者之间具有同步与互斥关系;
    生产者与生产者之间具有互斥关系;
    消费者与消费者之间具有互斥关系;
9.4 线程池
9.4.1 概念

(1) 一堆线程+任务队列 (缓冲任务)
(2) 在程序初始化时, 创建固定数量的线程 (最大数量限制),从任务队列中获取任务, 进行处理.
(3) 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池
维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

9.4.2 作用

(1) 避免为大量请求创建线程, 导致资源耗尽程序奔溃的问题;
(2) 避免大量线程频繁创建以及销毁所带来的时间成本;

9.4.3 实现

线程创建 + 线程安全的任务队列实现

9.4.4 线程池的销毁条件

所有线程退出之后

9.4.4 线程池的应用场景

(1) 需要大量的线程来完成任务,且完成任务的时间比较短。WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了,因为Telnet会话时间比线程的创建时间大多了。
(2) 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
(3) 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请
求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

9.5 线程安全的单例模式
9.5.1 设计模式

(1) 定义
大佬们对典型的应用场景设计出的解决方案
(2) 分类
(3) 使用场景

9.5.2 单例模式

是设计模式中的一种常见模式, 一个对象只能被实例化一次 (一个资源只能被加载一次).

9.5.3 实现

饿汉模式
懒汉模式
资源在使用的时候才加载, 在多个执行流中就可能被加载多次, 因此资源的加载/对象的实例化过程需要保护,线程安全的单例模式主要针对的是懒汉方式的实现.
非线程安全+volatile

  • 实现线程安全
class info{
static T* _data;
T* get_instance(){
	lock();
	if(NULL == _data){
		_data = new T();
	}
	unlock();
	return _data;
	}
}

避免锁冲突进行二次判断

9.6 常见的锁
9.6.1 常见的锁的种类

(1) 悲观锁
在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
(2) 乐观锁
每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据
前,会判断其他数据在更新前有没有对数据进行修改。
(3) 互斥锁
(4) 自旋锁
(5) 读写锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值