Linux之多线程(四):信号量、线程池、读写者模型

291 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

信号量

其本质是:计数器 + 等待队列 + 等待 + 唤醒 功能:实现线程/进程的同步与互斥 计数器就是判断的条件(当计数只有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

线程池

一堆固定数量的或者有最大数量限制的线程 + 任务队列 = 线程池

  • 功能:并发处理任务请求
  • 线程池避免大量频繁的线程创建销毁的时间成本。
  • 线程池避免峰值压力带来的瞬间大量线程被创建资源耗尽,程序崩溃的危险。

线程池的种类与使用场景:之后补充~

线程池的设计: (线程数量固定的线程池)

  1. int _max_thr;
  2. int _cur_thr;
  3. int _quit_flag; //退出标志
  4. int capacity; //队列最大节点数
  5. std::queue <task> _queue; //任务队列
  6. pthread_mutex_t _mutex;
  7. pthread_cond_t _cond_consumer;
  8. pthread_cond_t _cond_productor;

task:

  1. typedef bool(*task_callback)(int data);
  2. int _data;
  3. task_callback _handle; //处理的就是data
  4. setTask(int data, task_callback handle)

线程池对外的接口:

  • ThreadInit() //初始化
  • PushTask(Task &) //添加任务
  • ThreadQuit() //退出

线程池的实现:之后详解~


线程安全的单例模式

设计模式:常见场景的一般经典解决方案。单例模式是设计模式中的一种。

单例模式:一个对象只能被实例化一次 / 一个。(资源仅需一套)

单例模式分为:

模式描述优点缺点
饿汉单例模式程序初始化时进行实例化因为资源已经全部加载,运行速度快初始化时候耗时较长
懒汉单例模式程序资源使用时再进行加载,对象使用时再实例化初始化加载快==线程安全问题==、运行流畅度不够

读写锁:读者-写者问题

  • 特点:写互斥、读共享
  • 本质:通过两个计数器实现:一个writer计数器,一个read计数器。
  • 实现:通过自旋锁实现。

自旋锁:循环判断条件是否满足,优点是反应及时,缺点是CPU消耗叫高。

  • 适用于:阻塞时间很短的等待。

读的时候大家可以一起读,但是不能写。

若当前已经加写锁,则其他线程进行加写锁/读锁都会被阻塞。 若当前已经加读锁,则其他线程可以加读锁,但加写锁会被阻塞。

如果设置为读优先,适用于写者多,读者少的情景。拒绝后续读锁。 如果设置为写优先,适用于读者多,写者少的情景。拒绝后续写锁。