本文已参与「新人创作礼」活动,一起开启掘金创作之路。
信号量
其本质是:计数器 + 等待队列 + 等待 + 唤醒
功能:实现线程/进程的同步与互斥
计数器就是判断的条件(当计数只有0/1时,就可以实现互斥)。
等待队列 + 等待 + 唤醒实现同步的基本功能。
system V信号量:信号量原语:P/V操作
P:(-1 +阻塞)V:(+1 + 唤醒)
POSIX信号量:
头文件<semahore.h>
定义:sem_t信号量变量
初始化
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem:信号量变量pshared:选项标志:决定信号量用于进程间还是线程间同步互斥 (选项:0:线程间非0值:进程间)value: 信号量初始计数
计数-1,等待:数据操作前资源计数判断
int sem_wait(sem_t *sem); // 若计数<=0;则 < 阻塞 >
int sem_trywait(sem_t *sem); // 若计数>0;则报错返回 < 非阻塞 >
int sem_timedwait(sem_t* sem, struct timespec* abs_timeout); // 若计数<=0;则限时阻塞,超时则报错返回
- 计数
>0则计数-1,直接返回,执行后面逻辑。 - 计数
<=0则计数-1,阻塞等待。
生产数据后则计数+1,唤醒等待:
int sem_post(sem_t *sem); //计数+1 ,并唤醒等待
销毁
int sem_destroy(sem_t *sem);
信号量与条件变量的区别:
- 信号量拥有资源计数的功能。临界资源是否能够操作通过自身计数判断。
- 条件变量是搭配互斥锁使用。
- 信号量还可以实现互斥,计数仅为
0/1。
线程池
一堆固定数量的或者有最大数量限制的线程 + 任务队列 = 线程池
- 功能:并发处理任务请求
- 线程池避免大量频繁的线程创建销毁的时间成本。
- 线程池避免峰值压力带来的瞬间大量线程被创建资源耗尽,程序崩溃的危险。
线程池的种类与使用场景:之后补充~
线程池的设计: (线程数量固定的线程池)
- int _max_thr;
- int _cur_thr;
- int _quit_flag; //退出标志
- int capacity; //队列最大节点数
- std::queue <
task> _queue; //任务队列 - pthread_mutex_t _mutex;
- pthread_cond_t _cond_consumer;
- pthread_cond_t _cond_productor;
task:
- typedef bool(*task_callback)(int data);
- int _data;
- task_callback _handle; //处理的就是data
- setTask(int data, task_callback handle)
线程池对外的接口:
ThreadInit()//初始化PushTask(Task &)//添加任务ThreadQuit()//退出
线程池的实现:之后详解~
线程安全的单例模式
设计模式:常见场景的一般经典解决方案。单例模式是设计模式中的一种。
单例模式:一个对象只能被实例化一次 / 一个。(资源仅需一套)
单例模式分为:
| 模式 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 饿汉单例模式 | 程序初始化时进行实例化 | 因为资源已经全部加载,运行速度快 | 初始化时候耗时较长 |
| 懒汉单例模式 | 程序资源使用时再进行加载,对象使用时再实例化 | 初始化加载快 | ==线程安全问题==、运行流畅度不够 |
读写锁:读者-写者问题
- 特点:写互斥、读共享。
- 本质:通过两个计数器实现:一个
writer计数器,一个read计数器。 - 实现:通过自旋锁实现。
自旋锁:循环判断条件是否满足,优点是反应及时,缺点是CPU消耗叫高。
- 适用于:阻塞时间很短的等待。
读的时候大家可以一起读,但是不能写。
若当前已经加写锁,则其他线程进行加写锁/读锁都会被阻塞。 若当前已经加读锁,则其他线程可以加读锁,但加写锁会被阻塞。
如果设置为读优先,适用于写者多,读者少的情景。拒绝后续读锁。 如果设置为写优先,适用于读者多,写者少的情景。拒绝后续写锁。