距离上一次利用高并发技术实现360度行车记录仪功能已经过去半年了。开始写一系列关于系统编程和网络编程内容进行总结。
温故而知新,欢迎大家讨论学习。
2021-09-08 复习内容
:
- 复习代码
- 1 man 1 man 2 man 3 分别是标准命令 系统调用 和 库函数
- 编译需要 -l pthread 加载第三方库
- 什么叫第三方库呢:除本地类库、系统类库以外的类库,需要后来安装,才能调用的类库。C++常见第三方库参考
- 信号量那边的代码 如果是多生产者 都消费者就是错误的,因为没有处理生产者和生产者 或者 消费者和消费者之间互斥访问的问题。可能存在同时进入临界区。
- sem_init 初始化 分线程间 参数为0 进程间 参数为1 注意
2021-10-06 补充内容
- 条件变量虚假唤醒的问题
- 条件变量也叫做管程
- 生产者生产 上锁 生产 解锁 唤醒 ;消费者消费 上锁 while 判断阻塞 消费 释放锁。
1 同步的概念
- 所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。
- 设备同步,是指在两设备之间规定一个共同的时间参考;
- 数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持
一致; - 文件同步,是指让两个或多个文件夹里的文件保持一致。等等
2 线程同步的概念
- 线程同步简单说就是线程排队
- 线程同步是一种制约关系,一个线程的执行依赖另一个线程消息。
当它没有得到另一个线程的消息时应等待,得到消息被唤醒 - 线程同步使得多个线程协调工作从而带到一致性
3 为什么要有线程同步
- 共享资源,多个线程都可对共享资源操作 [容易产生冲突]
- 线程操作共享资源的先后顺序不确定
- cpu处理器对存储器的操作一般不是原子操作
举个例子:
两个线程都把全局变量增加1,这个操作平台需要三条指令完成
从内存读到寄存器 →寄存器的值加1 →将寄存器的值写会内存。
如果此时线程A取值在寄存器修改还未写入内存,线程B就从内存取值就会导致两次操作实际上只修改过一次。或者说后一次线程做的事情覆盖前一次线程做的事情。实际上就执行过一次线程。
4 互斥量 mutex
4.1 基本概念
- Linux 中提供一把互斥锁 mutex(也称之为互斥量)。
- 每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
- 资源还是共享的,线程间也还是竞争的,但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。
4.2 函数使用
4.2.1 pthread_mutex_t 类型
- 其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。
- 变量 mutex 只有两种取值 1、0。
4.2.2 pthread_mutex_init 函数
作用:
初始化一个互斥锁—> 初值可看作 1
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr)
- 参 1:传出参数,调用时应传 &mutex
- 参 2:互斥量属性。是一个传入参数,通常传 NULL,选用默认属性(线程间共享)。
4.2.2.1静态初始化和动态初始化
- 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了 static 关键字修饰),可以直接使用宏进行初始化。e.g.
pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
- 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL)
4.2.3 pthread_mutex_destroy 函数
作用:
销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex)
4.2.4 pthread_mutex_lock 函数
作用:
加锁。可理解为将 mutex–(或 -1),操作后 mutex 的值为 0。
int pthread_mutex_lock(pthread_mutex_t *mutex);
4.2.5 pthread_mutex_unlock 函数
作用:
解锁。可理解为将 mutex ++(或 +1),操作后 mutex 的值为 1。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
4.2.6 pthread_mutex_trylock 函数
作用:
尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
4.3 加锁和解锁
4.3.1 lock 与 unlock:
- lock 尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。
- unlock 主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。
- 例如:T1 T2 T3 T4 使用一把 mutex 锁。T1 加锁成功,其他线程均阻塞,直至 T1 解锁。T1 解锁后,T2 T3 T4 均被唤醒,并自动再次尝试加锁。
- 可假想 mutex 锁 init 成功初值为 1。lock 功能是将
mutex--
。而 unlock 则将mutex++
。
4.3.2lock 与 trylock:
- lock 加锁失败会阻塞,等待锁释放。
- trylock 加锁失败直接返回错误号(如:EBUSY),不阻塞
5 读写锁
这部分内容在项目中没有使用过,如有需要,下次补充…
6 条件变量
6.1 基本概念
- 条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。
6.2 为什么使用条件变量
- 线程抢占互斥锁时,线程A抢到了互斥锁,但是条件不满足,线程A就会让出互斥锁让给其他线程,然后等待其他线程唤醒他;一旦条件满足,线程就可以被唤醒,并且拿互斥锁去访问共享区。经过这中设计能让进程运行更稳定。
6.3 函数使用
6.3.1 pthread_cond_init 函数
作用:
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict
attr);- 参 2:attr 表条件变量属性,通常为默认值,传 NULL 即可
6.3.1.1 静态初始化和动态初始化
静态初始化:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
动态初始化:int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict
attr);
6.3.2 pthread_cond_destroy 函数
作用:
销毁一个条件变量
- i
nt pthread_cond_destroy(pthread_cond_t *cond);
6.3.3 pthread_cond_wait 函数(重)
作用:(非常重要 三点)
- 阻塞等待条件变量 cond(参 1)满足
- 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);
- 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
1.2.两步为一个原子操作。
6.3.4 pthread_cond_timedwait 函数
作用:
限时等待