信号量、使用信号量来完成读写模型(消费者生产者模型)线程池、读写锁面试题
多线程3
- 信号量
- 资源计数器
- 信号量和条件变量的对比
- 信号量的互斥与同步
- 接口
- 信号量类型
- 初始化
- 等待接口
- 发送接口
- 销毁接口
- 使用信号量完成读写模型
- 线程池
- 本质
- 如何让一个线程池可以处理多种多样的问题?
- 读写锁
- 读写锁面试题
- 接口
- 初始化
- 销毁接口
- 加锁接口
- 解锁接口
信号量
资源计数器
信号量在本质上是PCB等待队列+计数器计数器:对资源的计数,会影响信号量的等待接口和发送接口(唤醒接口)的逻辑如果有一个停车场,内有8个车位,此时停满了车,又有2辆车想停进去,即处于等待队列,此时资源计数器的值便为-2当一个线程调用发送接口之后,资源计数器进行加1操作,此时,加1操作之后的资源计数器的结果还是小于0,此时需要通知等待队列吗?需要,即开走了一辆车,在等待队列中的一辆车停了进去,资源计数器进行加1操作,变成-1,但是等待队列有依然存在等待的车辆,所以需要通知当一个线程调用发送接口之后,资源计数器进行加1操作,此时,加1操作之后的资源计数器的结果是大于0,此时需要通知等待队列吗?不需要,假设这个停车场只停了一辆车,随后这辆车开走了,资源计数器中的数值为8,但是等待队列中并没有车辆在等待,所以不需要通知
信号量和条件变量的对比
条件变量:程序员需要自己把握资源的数量
信号量:信号量会自己维护资源的数量,只需要在初始化信号量的时候指定资源的数量
信号量的互斥与同步
信号量可以完成互斥,也可以完成同步互斥:资源计数器的初始值设置1,线程A拿到信号量,线程B一定拿不到同步:设定一个写线程和一个读线程,都从起始位置开始,设定一个数组,读线程初始化为0,写线程初始化为数组空间大小,写线程在写入后到下一个位置,写线程初始值-1,随后唤醒读线程,读线程计数器+1,在读完后进入下一个位置,读线程计数器-1并通知写线程进行写,写线程计数器+1写线程找下一个写的位置:此时位置 = n下一个位置 = (n + 1)% 数组大小
接口
信号量类型
sem_t
初始化
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem:信号量
pshared:用于进程间还是用于线程间线程间:0进程间:非0
value:初始化资源的数量
等待接口
int sem_wait(sem_t *sem);
会对资源计数器进行-1操作
判断资源计数器的值是否大于0是:接口返回否:将线程放到PCB等待队列,阻塞起来
发送接口
int sem_post(sem_t *sem);
会对资源计数器进行+1操作
判断资源计数器的值是否小于0是:通知PCB等待队列否:不通知PCB等待队列
销毁接口
int sem_destroy(sem_t *sem);
将开辟出来的sem的信号量释放掉
使用信号量完成读写模型
1、线程安全队列 <vector>互斥+同步
2、两种角色的线程
用到信号量需要包含一个头文件:
#include <semaphore.h>
1 #include <stdio.h>2 #include <unistd.h>3 #include <pthread.h>4 #include <semaphore.h>5 #include <iostream>6 #include <vector>7 8 using namespace std;9 10 #define CAPACITY 111 #define THREAD_NUM 112 13 class RingQueue14 {15 public:16 RingQueue()17 :vec_(CAPACITY)//初始化大小18 {19 capacity_ = CAPACITY;20 21 //初始化互斥信号量22 sem_init(&sem_lock_, 0, 1);23 //初始化同步信号量24 sem_init(&sem_read_, 0, 0);//刚开始只能读025 sem_init(&sem_write_, 0, CAPACITY);26 27 pos_read_ = 0;//读写从下标0开始28 pos_write_ = 0;29 }30 31 ~RingQueue()32 {33 //销毁互斥34 sem_destroy(&sem_lock_);35 36 //销毁读写37 sem_destroy(&sem_read_);38 sem_destroy(&sem_write_);39 }40 41 void Push(int data)//入队42 {43 sem_wait(&sem_write_);//进行写,保证同步44 45 sem_wait(&sem_lock_);//保证互斥46 vec_[pos_write_] = data;//在pos_write_位置写入47 pos_write_ = (pos_write_ + 1) % capacity_;48
W> 49 printf("i write %d, i am %p\n", data, pthread_self());50 51 sem_post(&sem_lock_);//解锁52 53 sem_post(&sem_read_);//通知读54 }55 56 void Pop(int* data)//出队57 {58 sem_wait(&sem_read_);//进行读,保证同步59 60 sem_wait(&sem_lock_);//加锁61 *data = vec_[pos_read_];//从pos_read_拿数据62 pos_read_ = (pos_read_ + 1) % capacity_;63
W> 64 printf("i read %d, i am %p\n", *data, pthread_self());65 66 sem_post(&sem_lock_);//解锁67 68 sem_post(&sem_write_);//通知写69 }70 71 private:72 vector<int> vec_;//模拟的队列73 int capacity_;//设定容量74 75 //保证互斥的信号量76 sem_t sem_lock_;77 //保证同步的信号量78 sem_t sem_write_;79 sem_t sem_read_;80 81 //标识读写的位置82 int pos_read_;83 int pos_write_;84 };85 86 void* ReadStart(void* arg)87 {88 RingQueue* rq = (RingQueue*)arg;89 while(1)90 {91 int data;92 rq->Pop(&data);93 }94 return NULL;95 }96 97 int g_data = 1;98 sem_t sem_write_lock_;//给写加个互斥锁99 100 void* WriteStart(void* arg)101 {102 RingQueue* rq = (RingQueue*)arg;103 while(1)104 {105 sem_wait(&sem_write_lock_);106 rq->Push(g_data++);107 sem_post(&sem_write_lock_);108 }109 return NULL;110 }111 112 int main()113 {114 sem_init(&sem_write_lock_, 0, 1);115 116 RingQueue* rq = new RingQueue();117 if(rq == NULL)118 {119 return 0;120 }121 122 pthread_t read[THREAD_NUM], write[THREAD_NUM];123 for(int i = 0; i < THREAD_NUM; i++)124 {125 int ret = pthread_create(&read[i], NULL, ReadStart, (void*)rq);126 if(ret < 0)127 {128 perror("pthread_create fail\n");129 return 0;130 }131 ret = pthread_create(&write[i], NULL, WriteStart, (void*)rq);132 if(ret < 0)133 {134 perror("pthread_create fail\n");135 return 0;136 }137 }138 139 for(int i = 0; i < THREAD_NUM; i++)140 {141 pthread_join(read[i], NULL);142 pthread_join(write[i], NULL);143 }144 145 delete rq;146 sem_destroy(&sem_write_lock_);147 148 return 0;149 }
将CAPACITY放大为10
线程池
本质
线程池的本质是:线程池当中包含一个线程安全的队列+一大堆的线程线程池当中的线程都是从线程池当中的线程安全队列当中获取元素进行处理逻辑上属于消费线程线程池当中的线程执行同样的入口函数,执行的是同样的代码
如何让一个线程池可以处理多种多样的问题?
一个服务端后台的代码执行的业务是非常庞杂的,如果一个线程池只能处理一个类型的数据,则需要对每一个业务数据都创建一个线程池,
这无疑是消耗巨大的一件事问:怎么让一个线程池可以处理多种多样的问题?设计线程安全队列:1、线程安全(互斥+同步)互斥锁+条件变量信号量2、设计线程安全队列的元素类型元素类型:待要处理的数据+处理该数据的方法(函数指针保存函数地址)
读写锁
读写锁适用的场景大量读,少量写
多个程序可以并行的对临界资源进行读操作,程序不会产生二义性结果
读写锁的三种模式:以读模式打开读写锁以写模式打开读写锁不加锁
读写锁的内部由一个引用计数,来统计当前以读模式打开的读写锁的线程的数量
通过引用计数来判断当前读写锁是否还有线程以读模式打开
判断什么时候读写锁是空闲的,没有被占用
读写锁面试题
现在有一个线程A和一个线程B都通过读模式打开了,接着有一个线程C想通过写模式打开,又有一个线程D想通过读模式打开,此时AB已经拥有了读写锁,线程C阻塞,线程D能否拿到读写锁?
不能拿到,读写锁有一个保护机制,当有线程以读摸式打开,又有线程想以写模式打开时,此后的所有想以读模式打开的线程都会被阻塞
接口
初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
销毁接口
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加锁接口
读方式
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
写方式
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
解锁接口
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
信号量、使用信号量来完成读写模型(消费者生产者模型)线程池、读写锁面试题相关推荐
- 【Linux】生产者消费者编程实现-线程池+信号量
生产者消费者编程实现,采用了线程池以及信号量技术. 线程的概念就不多说,首先说一下多线程的好处:多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞 ...
- 多线程锁,线程池,消费者生产者模型
锁是怎么存在的? 在python建立之初,开发者目的是为了快速把语言开发出来,如果加上GIL(c语言加锁),切换时按照100条字节指令来进行线程间的切换 为什么加锁? 非线程安全,控制一段代码 1.l ...
- Python笔记day40(并发)|守护线程、线程锁、信号量、事件、条件、定时器、队列、线程池
1,内容回顾 # 正确的学习方法# input# output# correct 纠正# 线程# 线程是进程中的执行单位# 线程是cpu执行的最小单位# 线城之间资源共享# 线程的开启和关闭以及切换的 ...
- JAVA那点破事!并发、IO模型、集合、线程池、死锁、非阻塞、AQS....
关于Java面试,面试官一般喜欢问哪些问题? 本文对一些高频问题做了汇总,为了便于大家查找问题,了解全貌,整理个目录,我们可以快速全局了解关于 JAVA 接下来,我们逐条来看看每个问题及答案 JDK. ...
- JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池...
/*** 多线程共享数据* 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行.* 多线程共享数据的安全问题,使用同步解决.* 线程同步两种方法: ...
- JAVA那点破事,并发、IO模型、集合、线程池、死锁、非阻塞、AQS...
JDK.JRE.JVM 三者有什么关系? 答案: JDK(全称 Java Development Kit),Java开发工具包,能独立创建.编译.运行程序. JDK = JRE + java开发工具( ...
- 【Linux入门】多线程(线程概念、生产者消费者模型、消息队列、线程池)万字解说
目录 1️⃣线程概念 什么是线程 线程的优点 线程的缺点 线程异常 线程异常 Linux进程VS线程 2️⃣线程控制 创建线程 获取线程的id 线程终止 等待线程 线程分离 3️⃣线程互斥 进程线程间 ...
- Linux detached(分离线程) 消费者和生产者模型
1.分离线程常用库 把一个线程的属性设置为 detachd 的状态,让系统来回收它的资源,而不再需要在其它线程中对其进行 pthread_join() 操作. <pthread.h> pt ...
- Linux服务器7 --- 多路IO复用+线程池服务端模型(高并发)分析
一.服务端特性概述 1.使用EPOLL模型在服务器中加入(网络IO监听,大量的监听能力) EPOLL采用边缘触发模式(后话) 2.线程池模型进行并发处理业务(并发处理能力) 1)提高线程重用性(避免频 ...
最新文章
- 几种P2P流媒体开源项目介绍
- ubuntu18.04安装mysql8.0.16
- 最优化方法(无约束)转载
- Spring-boot(一)
- 教你如何完全解析Kotlin中的注解
- 谷歌浏览器外贸版_做外贸没有单怎么办?找客户 供应商的小技巧-跨境电商
- dedecms 封面模板和列表模板有什么不同
- springboot配置手动提交_kafka教程-springboot消费者-手动提交offset
- Windows10下VB6.0开发——常用数值处理函数工具
- 微信电脑版真的要来了 微信Windows版客户端1.0 Alpha推出
- jquery判断日期格式
- 西北大学第四届程序设计竞赛新生赛 J 八意永琳的药房
- 开启xmp1还是2_英雄联盟手游高帧率模式怎么开启-高帧率模式开启方法
- 【证券相关】终值和现值
- 梁昌勇 软件工程_改进交互式蚁群算法及其应用
- jquery 会话存储_5个jQuery打字和会话插件
- java mysql 时间查询_java 根据时间段查询数据库
- 【CAD开发】glTF和b3dm文件格式读取(C++,Python)
- 安装Windows server 2003系统后无法安装显卡驱动的解决办法 (转载)
- 最新调研-化妆品和个人护理乳化剂行业研究分析报告