线程的概念
线程的定义
线程是进程中的⼀个执行单元,负责当前进程中程序的执行,⼀个进程中至少有⼀个线程 ⼀个进程中是可以有多个线程 多个线程共享同一个进程的资源,每个线程参与操作系统的统一调度
可以简单理解: 进程 = 进程资源 + 主线程 + 子线程+......
线程与进程区别
内存空间 一个进程中多个线程共享同一个内存空间
多个进程拥有独立的内存空间
进程/线程间通讯 线程间通讯方式简单 进程间通讯方式复杂 并发操作,线程比进程更节约资源
联系紧密的任务在并发时优先选择多线程,如果任务之间比较独立,在并发时建议选择多进程。
线程资源
共享进程的资源
同一块地址空间
文件描述符表
每种信号的处理方式(如:SIG_DFL,SIG_IGN或者自定义的信号优先级)
当前工作目录
用户id和组id
独立的资源
线程栈
每个线程都有私有的上下文信息。
线程ID
寄存器的值 errno变量
信号屏蔽字以及调度优先级
线程相关的命令
在 Linux 系统有很多命令可以查看进程,例如 pidstat 、top 、ps ,也可以查看一个进程下的线程
使用 pidstat 命令查看某一个进程下的线程
step 1 : 运行 sem 程序,此程序包含两个进程
step 2 : 查看 sem进程所对应的 id
step 3 : 使用 pidstat 命令查看相应进程的线程
top 命令查看某一个进程下的线程,需要用到 -H 选项在结合 -p 指定 pid
ps 命令结合 -T 选项就可以查看某个进程下所有线程
线程操作
创建线程
创建线程调用 pthread_create 函数
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void * (*start_routine) (void *), void *arg);
函数参数
thread:线程ID变量指针
attr:线程属性,默认属性可设置为NULL
start_routine:线程执行函数
arg:线程执行函数的参数
一旦子线程创建成功,则会被独立调度执行,并且与其他线程并发执行
创建多个线程时,一般由主线程统一创建,并等待释放资源或者分离线程,不要递归创建
1. 多个线程如果任务相同,则可以使用同一个线程执行函数
2. 多个线程如果任务不同,则可以使用不同的线程执行函数
线程的等待、退出和分离
线程退出使用 pthread_exit 函数
Tips:
1. 当主线程调用pthread_exit函数时,进程不会结束,也不会导致其他子线程退出
2. 任何线程调用exit函数会让进程结束
主线程需要等待子线程退出,并释放子线程资源
线程等待调用 pthread_join函数,会阻塞调用线程
线程分为可结合的与可分离的
可结合 :可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源 (如栈)是不释放的。 线程创建的默认状态为可结合的,可以由其他线程调用 pthread_join函数等待子线程退出并 释放相关资源
可分离 :不能被其他线程回收或者杀死的,该线程的资源在它终止时由系统来释放。
线程分离调用 pthread_detach 函数
注意:线程分离函数不会阻塞线程的执行
线程间通信
主线程向子线程传递参数
通过 pthread_create 函数的第4个参数 arg 进行传递
子线程给主线程传递参数
子线程给主线程传参的方式如下:
在子线程将需要返回的值存储在 pthread_exit 函数中的 retval 参数中
在主线程中通过 pthread_join 函数的第2个参数 retval 得到返回, pthread_join 函数会将线程的 返回值(指针)保存到 retval 中
线程互斥锁
关于线程互斥锁
线程的主要优势在于能够通过全局变量来共享信息,不过这种便捷的共享是有代价的:
必须确保多个线程不会同时修改同一变量 某一线程不会读取正由其他线程修改的变量,实际就是 不能让两个线程同时对临界区进行访问 线程互斥锁则可以用于解决多线程资源竞争问题
将数字字符串转换为整数 int atoi(const char *nptr)
1. 线程互斥锁工作机制: 当线程A获得锁,另外一个线程B在获得锁时则会阻塞,直到线程A释放锁,线程B才会获得锁。
2. 线程互斥锁工作原理 :本质上是一个pthread_mutex_t类型的变量,假设名为 v
当 v = 1,则表示当前临界资源可以竞争访问 ,得到互斥锁的线程则可以访问,此时 v = 0
当 v = 0,则表示临界资源正在被某个线程访问,其他线程则需要等待
3. 线程互斥锁的特点 :互斥锁是一个 pthread_mutex_t类型的变量,就代表一个互斥锁
如果两个线程访问的是同一个 pthread_mutex_t 变量,那么它们访问了同一个互斥锁
对应的变量定义在 pthreadtypes.h 头文件中,是一个共用体中包含一个结构体
线程互斥锁的初始化
线程互斥锁的初始化方式主要分为两种
1. 静态初始化
定义 pthread_mutex_t 类型的变量,然后对其初始化为 PTHREAD_MUTEX_INITIALIZER pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
2. 动态初始化
动态初始化主要涉及两个函数 pthread_mutex_init 函数 与 pthread_mutex_destroy 函数
线程互斥锁的操作
线程互斥锁的操作主要分为 获取锁(lock) 与 释放锁(unlock)
获取锁的函数:pthread_mutex_lock
释放锁的函数:pthread_mutex_unlock
线程同步
线程同步:是指在互斥的基础上,通过其它机制实现访问者对 资源的有序访问。
条件变量:线程库提供的专门针对线程同步的机制
生产者与消费者模型原理
1. 在这个模型中,包括生产者线程与消费者线程。通过线程来模拟多个线程同步的过程
2. 在这个模型中,需要以下组件
仓库 : 用于存储产品,一般作为共享资源
生产者线程 : 用于生产产品
消费者线程 :用于消费产品
3.原理: 当仓库没有产品时,则消费者线程需要等待,直到有产品时才能消费 当仓库已经装满产品时,则生产者线程需要等待,直到消费者线程消费产品之后
条件变量
条件变量 允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程等待这一通知
条件变量初始化
条件变量的本质为 pthread_cond_t 类型的变量,其他线程可以阻塞在这个条件变量上,或者唤醒阻塞 在这个条件变量上的线程。
条件变量的初始化分为静态初始化与动态初始化
1. 静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2. 动态初始化 pthread_cond_init 函数
函数原型
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
函数功能
初始化条件变量
pthread_cond_destroy函数
函数原型
int pthread_cond_destroy(pthread_cond_t *cond);
函数功能
销毁条件变量
条件变量原理
原理分析
基于条件变量的阻塞与唤醒,具体的原理如下图
step 1 :消费者线程判断消费条件是否满足(仓库是否有产品),如果有产品则可以消费,然后解锁
step 2 :当条件满足时(仓库产品数量为0),则调用 pthread_cond_wait 函数,这个函数具体做的事 情如下:
在线程睡眠之前,对互斥锁解锁
让线程进入到睡眠状态
等待条件变量收到信号时,该函数重新竞争锁,并获取锁
step 3 :重新判断条件是否满足,如果满足,则继续调用 pthread_cond_wait 函数
step 4 :唤醒后,从 pthread_cond_wait 返回,条件不满足,则正常消费产品
step 5 :释放锁,整个过程结束
函数使用
pthread_cond_wait
函数原型 int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 函数功能 阻塞线程,等待唤醒
Tips:
条件变量需要与互斥锁结合使用,先获得锁才能进行条件变量的操作
调用函数后会释放锁,并阻塞线程
一旦线程唤醒,需要重新竞争锁,重新获得锁之后,pthread_cond_wait 函数返回
pthread_cond_signal和pthread_cond_broadcast
函数原型
int pthread_cond_signal(pthread_cond_t *cond);
函数功能
唤醒所有阻塞在某个条件变量上的线程
函数原型
int pthread_cond_broadcast(pthread_cond_t *cond);
函数功能
唤醒所有阻塞在某个条件变量上的线程
Tips:
pthread_cond_signal 函数主要适用等待线程都在执行完全相同的任务
pthread_cond_broadcast 函数主要适用等待线程都执行不相同的任务
条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制,发送信号时若无任何线 程在等待该条件变量,则会被忽略
条件变量代表是一个通讯机制,用于传递通知与等待通知,用户可以设定条件来发送或者等待通知