【操作系统】进程管理——用信号量机制解决问题,以生产者-消费者问题为例(个人笔记)
学习日期:2024.7.10
内容摘要:利用信号量机制解决几个经典问题模型
目录
引言
问题模型
生产者-消费者问题(经典)
多生产者-多消费者问题
吸烟者问题
读者写者问题(难点)
哲学家进餐问题(避免死锁)
引言
在《信号量机制》章节中,我们已经学习了信号量的概念和用其实现进程同步和互斥的工作原理,下面结合实际的模型来看信号量机制如何起效。
PV操作题目分析步骤:
1.关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
2.整理思路。根据各进程的操作流程缺点P、V操作的大致顺序。
3.设置信号量。根据题目条件确定信号量初始值(互斥一般为1,同步一般看对应资源的初始值)
简记口诀:
互斥:紧邻操作,成对PV。(紧邻互斥操作,在同一进程内完成PV)
同步:前V后P,前后后前。(在不同进程内进行操作以保证顺序执行,在前一个进程完成后进行V操作,在后一个进程开始前进行P操作)
问题模型
生产者-消费者问题(经典)
系统中有一组生产者进程和消费者进程,生产者每次生产一个产品放入缓冲区,消费者每次从缓冲区中取出一个产品使用(“缓冲区”理解为二者共享的仓库,“产品”理解为某种数据)
缓冲区是一个初始为空,大小固定为n,双方共享的区域,且缓冲区是临界资源,各进程必须互斥访问。(原因:如果不是互斥访问的,可能两个生产者进程在同一片区域放入数据,导致数据覆盖丢失)
首先分析,显然:
①缓冲区没满的情况下,生产者才能生产,二者有同步关系。
②缓冲区不空的情况下,消费者才能消费,二者有同步关系。
③进程对缓冲区的访问必须互斥。
那么,设置三个变量,一个mutex表示互斥标记,一个empty表示空闲区数目,一个full表示装入区数目。每次生产者进程行动时,P(empty),V(full),消费者行动时则反之
这样就能实现通过PV操作来表示资源的获取与释放。
注意!实现互斥是在同一进程中进行一对PV操作,在“把产品放入缓冲区/从缓冲区中取出”这一对临界资源的访问行为前后,成对进行。
而同步关系是在两进程分开执行,在一个进程中执行P,另一个执行V。具体理论部分可以参考上一章。
Q:能否交换相邻P操作的顺序?
不能。假如交换了生产者的前两个P操作的顺序,若缓冲区内已经放满产品,此时empty=0,full=n。若生产者先进行P(mutex)声明自己独占了缓冲区,然后P(empty)发现缓冲区的empty区不足,就会进入阻塞状态。
之后消费者想占用缓冲区,释放empty资源时,因为生产者已经占用了mutex,没有释放,因为互斥会导致无法进行V(empty)操作
这样,生产者在等待消费者释放empty,消费者在等待生产者解除对缓冲区的占用,双方会无限等待下去,形成死锁。
所以表示互斥的PV部分要成对出现,成对执行,且实现互斥的P操作一定在实现同步操作的P操作之后。
Q:能否交换相邻V操作的顺序?
可以,V操作不会导致进程阻塞,但是为了方便理解记忆,还是让实现互斥的PV操作紧邻互斥行动区为好。
Q:能否不依赖互斥标识资源mutex?
通常不行,但是特殊情况下可以。这个特殊情况就是缓冲区容量为1的情况(即empty的初始值为1),此时如果一个生产者生产了产品,执行P(empty),另一个生产者也想生产产品时,因为empty的值已经是0,就会发生阻塞,从而避免了生产者同时访问的问题。而在生产者执行完互斥操作,V(full)之前,因为full的值为0,消费者进行P(full)时也会阻塞,避免了生产者访问时消费者同时访问的问题,从而不依赖mutex就能实现互斥访问。
多生产者-多消费者问题
是生产者-消费者问题的变形,区别在于增加了若干组生产者和消费者。比如说1号生产者会生产1型商品,2号生产者生产2型商品,而1号消费者只消费1型商品,2消费者只消费2型商品。
所有的生产者和消费者仍旧共享一个缓冲区,此处的“多”指的是“多类”而不是“多个”,与上一问题的根本区别是有多类生产者和消费者。
关系分析:
①首先,同生产者-消费者问题的前提一样,缓冲区没满才能生产,不空才能消费,且互斥访问。
②根据不同资源的类型,分别PV不同的变量,来为每对生产者和消费者确立同步关系。
在分析同步问题的时候,不能从单个进程行为的角度来分析,而应该从事件角度分析。
不要想着“x号生产者生产了一个x号产品,x号消费者......”,然后设置四个同步信号量。而是从事件的角度分析,把进程的先后顺序概括为事件的先后顺序,在这个例子中,就是生产产品事件先于消耗产品事件。
生产产品可以是1号生产者,也可以是2号生产者,它们只需要每次占用一个仓库空间P(empty),然后增加一个对应产品V(product_x)。同样的,消费者也只要每次消耗一个产品P(product_x),然后释放一个空间P(empty)就行了。最后再在互斥操作前后加上mutex的成对PV操作保证互斥,根据空间的大小设置empty的初始值,就可以解决问题了!
吸烟者问题
仍然是生产者-消费者问题的变形。更准确的来说,是“可生产多种产品的单生产者-多消费者问题”
假设一个系统有三个抽烟者进程和一个供应者进程,每个抽烟者进程不停的卷烟并抽烟,这需要三种材料:烟草、纸卷和胶水。在三个抽烟者中,第一个有烟草,第二个有纸卷,第三个有胶水。供应者会无限提供三种材料,每次将两个材料置于桌面,而拥有剩下那个材料的抽烟者就会拿走,完成抽烟,然后给供应者一个信号告诉他完成了,供应者就会放另外两种材料在桌上,让三个抽烟者能够轮流抽烟。
首先,桌子可以抽象为容量为1,互斥访问的缓冲区。为什么放两件东西容量为1呢?
我们将供应者供应的两件东西看作是一个组合,则他一共供给三个组合,第一个消费者需要纸卷+胶水,定义为组合1,第二个消费者需要烟草+胶水,定义为组合2,同理得组合3。
这样问题就抽象为了一个生产者能生产1号,2号,3号三种产品,分别对应三个消费者,要进行轮流消费,缓冲区容量为1。
关系分析:
①桌上有组合x 先于 x号消费者取走东西,且缓冲区互斥访问。
②消费者发出完成信号 先于 生产者将下一个组合放到桌上。
Q:轮流操作如何实现?
如右图所示,通过一个if(i==0)的选择分支结构,和结尾的i=(i+1)%3,实现了让i在0,1,2不断循环变化,从而轮流执行三个部分的生产,每次产生对应的offer。补充:如果要随机实现,可以用Random(i)
而如上图每一个消费者每次消耗一个offer的商品P(offer_x),然后再释放一个finish信号给生产者,保证先释放信号,然后生产者再生产下一个产品。
之前介绍过,因为此时缓冲区的容量为1,故不需要互斥信号量mutex也可以实现互斥访问。
读者写者问题(难点)
在系统中有读者、写者两组并发进程,共享一个文件。多个读者可以同时对文件执行读操作,但是只允许一个写者往文件中写入信息,写者对文件的访问必须互斥,也不能边写边读,每个写者在工作时都必须独占该共享文件。
与消费者进程不同,读者进程在读数据时,不会“消耗”数据, 所以读者进程可以同时访问共享数据,互不影响。
不能边读边写是因为,如果允许并发执行,读者可能先读了某数据的一部分,但剩下的部分被写者覆盖了,导致读者所读的数据并不是所期望的。不能同时写的原因同生产者进程互斥原因,会相互覆盖。
关系分析:
①写者进程和所有进程互斥,读者进程之间不互斥。
难点就在于,如何在实现写者进程互斥的同时,让读者进程不互斥。
我们一步一步来:
semaphore rw=1writer(){while(1){P(rw); //写之前上锁写文件操作...V(rw); //写完解锁}
}reader(){while(1){P(rw); //读之前上锁读文件操作...V(rw); //读完解锁}
}
先写个最简单的,但是显然,这样是每个进程都互斥访问共享区,不能实现同时读操作。
那么,要如何实现“同时读”呢?可以设法让后面的读进程跳过P(rw)这一步。
所以,逻辑代码变成了这样:
semaphore rw=1;
int count = 0; //记录当前有几个读进程在访问文件writer(){while(1){P(rw); //写之前上锁写文件操作...V(rw); //写完解锁}
}reader(){while(1){
if(count==0) //由第一个读进程负责上锁P(rw); //读之前上锁count++; 读文件操作...count--;
if(count==0)//由最后一个读进程负责解锁V(rw); //读完解锁}
}
我们引入了一个新的变量count用来记录当前访问文件的读进程数,当count==0时,访问文件的读进程是第一个,它负责上锁,之后访问的读进程,因为count!=0,就跳过了P(rw)语句,从而可以直接进行读文件操作。同理,当最后一个读进程读完时,count==0,最后一个进程进行V(rw)释放资源,表示没有读进程还在操作了。
但是这样还是有一个问题,因为读进程是并发执行的,如果两个读进程同时开始执行,当第一个读者进程P(rw)以后,还没有来得及count++时,第二个读进程可能已经通过了conunt的判断语句,也进行P(rw),但是此时rw已经为0了,这会导致第二个进程阻塞。
显然,出现上一问题的原因是,我们对count变量的检查和赋值无法一气呵成。无法一气呵成...?我们最初用PV操作就是为了解决进程互斥中,软件实现方法无法一气呵成的问题的,(详见《进程的同步与互斥》)所以可以想到,再加一组互斥标识,保证进程能互斥的访问count。
semaphore rw = 1;
semaphore mutex = 1;//用于保证对count变量的互斥访问
int count = 0; //记录当前有几个读进程在访问文件writer(){while(1){P(rw); //写之前上锁写文件操作...V(rw); //写完解锁}
}reader(){while(1){P(mutex);if(count==0) //由第一个读进程负责上锁P(rw); //读之前上锁count++; V(mutex); 读文件操作...P(mutex);count--; if(count==0)//由最后一个读进程负责解锁V(rw); //读完解锁V(mutex);}
}
我们引入了mutex变量,来保证进程能互斥的访问count,此时如果两个读进程同步访问count,第一个进程会P(mutex)阻止其它读进程修改count,并且在自己修改完count之后,其它进程才能进行if判断,这样就不会出现上述问题了。两组对于mutex的PV操作,能够保证进程对count的互斥访问。
但是,还有一个潜在的问题(受不了了),只要有读进程还在读,rw的值就始终是0,写进程就会一直阻塞,如果一直有源源不断的读进程,写进程就会饥饿。在这种算法中,读进程是优先的。那么,该如何避免写进程饥饿呢?就是说,怎样能让写进程在想写入的时候,在有限的等待时间内就能写入呢?
semaphore rw = 1;
semaphore mutex = 1;//用于保证对count变量的互斥访问
semaphore w = 1; //用于实现“写优先”
int count = 0; //记录当前有几个读进程在访问文件writer(){while(1){P(w);P(rw); //写之前上锁写文件操作...V(rw); //写完解锁V(w);}
}reader(){while(1){P(w);P(mutex);if(count==0) //由第一个读进程负责上锁P(rw); //读之前上锁count++; V(mutex);V(w); 读文件操作...P(mutex);count--; if(count==0)//由最后一个读进程负责解锁V(rw); //读完解锁V(mutex);}
}
我们又引入了一个新的变量w,用于实现写优先。
假如三个进程读者A,写者,读者B并发执行。
当读者进程A通过了上半部分,开始进行读文件操作时,释放了w,占用了rw。此时写者进程可以进行P(w)操作了,但是会被阻塞在P(rw)操作上。当读者进程B开始运行时,因为写进程已经进行过P(w)操作了,读者B会在P(w)处被阻塞。
这样当读者A的读操作结束时,count--后为0,读者A会认为自己是最后一个读进程,从而释放rw,这样写者就可以执行P(rw),实现了写者优先于读者B执行。
此方法并不是真正的“写优先”,只是保证了写进程不会饥饿,相对公平的实现了先来先服务。
此处的w好比一个提供顺序功能的标记,第一个读者最开始先占用,然后在读文件之前释放,第二个读者如果在写者之后来,就会因为写者已经占用了w而不能继续。
小结:
读者-写者问题为我们解决复杂的互斥问题提供了思路,核心思想在于设置一个计数器count来记录当前正在访问共享文件的读进程数,用count的值来判断当前进入的进程是否是第一个/最后一个读进程,从而跳过部分P指令,实现读进程不互斥。
在对count变量的检查和赋值不能一气呵成时,采用mutex变量来确保count部分被互斥访问。
在写进程会饥饿时,引入了w变量来确保可以公平的完成先来先服务。
哲学家进餐问题(避免死锁)
圆桌上坐着5位哲学家,每两个哲学家之间摆着一根筷子,桌子的中间是火锅,哲学家们会思考和干饭。哲学家们在思考时不会影响别人,只有想干饭时,才会拿起左、右各一根筷子(一根一根拿),如果筷子已经在他人手上则需要等待。因为火锅很烫,哲学家必须需要一双筷子才能进餐,进餐完毕后,哲学家放下筷子继续思考。
难点:这个问题中只有互斥关系,没有同步关系,但是在这个模型中,每个哲学家进程都需要同时持有两个临界资源才能开始吃饭。我们可以想到一个很尴尬的情况,那就是每个哲学家都拿了一根筷子,大家都没有办法吃饭,但是都在等待别人吃饭然后让出筷子,这就是死锁。这是临界资源分配不当导致的,我们要设法避免这种情况发生。
关系分析:
①系统中有5个哲学家进程,5位哲学家与左右邻居对其之间的筷子的访问是互斥关系。
②筷子是本模型中的临界资源,哲学家想要吃饭需要获取其左右两根筷子。
我们给哲学家和筷子编号为0~4。显然,编号为i的哲学家需要编号i和i+1的两根筷子来吃饭,考虑到边界,严谨的表述是i和(i+1)%5
我们很容易想到一个简单的写法:
这种写法能实现对筷子的互斥访问,但是不能避免前面提到的死锁问题。那么该怎么解决呢?
①可以要求奇数号哲学家先拿左手的筷子,偶数号哲学家先拿右手的筷子。如图所示,以0、1为例,这样两位就会在一开始争抢1号筷子,谁先拿到,另一个就在不占用任何筷子的情况下直接进入阻塞态,这样就避免了五个人每人占用一根筷子进入阻塞态的死锁。
以代码实现的话,在每个哲学家拿筷子之前判断号码的奇偶,然后确定P操作的顺序即可。
②保证至少有一个哲学家可以一气呵成的拿到完整的一双筷子。
使用mutex互斥变量,保证至少有一个哲学家可以拿到完整的一双筷子。
在拿筷子的部分前后加入了mutex的PV操作。当0号哲学家拿筷子时,先P(mutex),之后依次拿起左右的筷子,即使在这过程中发生了进程调度,别的进程也会因为mutex值已经为0被阻塞,不能拿起筷子,直到0号哲学家把筷子拿完,释放mutex以后,别的哲学家才能拿起筷子。这样能够保证,至少第一个开始拿筷子的进程会一气呵成的拿到一双筷子,避免死锁。
但是,这个方法也有一个问题。如图所示,当0号哲学家拿完一双筷子开始进餐后,4号哲学家开始拿筷子,因为此时mutex已经释放,4号哲学家顺利执行了P(mutex),且拿起了4号筷子,当想拿起0号筷子时,发现被占用,进入阻塞态。
此时,对于2号哲学家来说,他左右两侧的筷子都是可以使用的,没有被占用,但是如果他想进食,因为4号哲学家还在占用着mutex并被阻塞,所以他在执行P(mutex)时会被阻塞。明明有临界资源可以用,但是却不能访问,这违背了空闲让进原则,这种方法的并发度不够高。
③最多允许四个哲学家同时拿起筷子。
只要最多四个哲学家同时拿起筷子,就必然有至少一个哲学家可以拿起一双筷子,避免了死锁。
为了解决这个问题,我们可以把上个方法中,mutex的初始值设置为4。这样,在上一情况中,4号哲学家依然会被右手的筷子阻塞,但2号哲学家不会被mutex阻塞,可以正常进食。
同时,在极端情况,即每个哲学家进程拿起一根筷子就被调度的情况中,最后一个进程想要拿起筷子时,会因为mutex已经为0,在P(mutex)步骤中被阻塞,从而在不占用任何筷子的情况下进入阻塞态,这样就不会发生死锁。
小结:
哲学家进餐问题的关键在于避免死锁,每个进程都需要持有两个临界资源,因此就有了“死锁”的隐患,想避免死锁,就要尽量让进程在不占用临界资源的情况下进入阻塞态。
感谢您看到这里,如果满意的话麻烦您点个赞支持一下,个人主页还有更多内容分享。
个人能力不足,如有错漏部分还请指出,我会尽快修改。
内容总结自王道计算机考研《操作系统》 和 人民邮电出版社《操作系统导论》
相关文章:

【操作系统】进程管理——用信号量机制解决问题,以生产者-消费者问题为例(个人笔记)
学习日期:2024.7.10 内容摘要:利用信号量机制解决几个经典问题模型 目录 引言 问题模型 生产者-消费者问题(经典) 多生产者-多消费者问题 吸烟者问题 读者写者问题(难点) 哲学家进餐问题࿰…...

算法刷题笔记 KMP字符串(C++实现,并给出了求next数组的独家简单理解方式)
文章目录 题目描述基本思路实现代码 题目描述 给定一个字符串S,以及一个模式串P,所有字符串中只包含大小写英文字母以及阿拉伯数字。模式串P在字符串S中多次作为子串出现。求出模式串P在字符串S中所有出现的位置的起始下标。 输入格式 第一行输入整数…...

SpringCloud架构师面试
一、微服务是什么 1、基本概念 微服务是一种架构风格(区别于单体架构、垂直架构、分布式架构、SOA架构),应用程序被划分为更小的、流程驱动的服务。 2、微服务的特征 轻量化:将复杂的系统或者服务进行纵向拆分,每个…...

C语言 | Leetcode C语言题解之第228题汇总区间
题目: 题解: char** summaryRanges(int* nums, int numsSize, int* returnSize) {char** ret malloc(sizeof(char*) * numsSize);*returnSize 0;int i 0;while (i < numsSize) {int low i;i;while (i < numsSize && nums[i] nums[i …...

入职前回顾一下git-01
git安装 Linux上安装git 在linux上建议用二进制的方式来安装git,可以使用发行版包含的基础软件包管理工具来安装。 红帽系 sudo yum install gitDebian系 sudo apt install gitWindows上安装git 去官网下载和操作系统位数相同的安装包.或者可以直接安装GitHub…...

this指向解析
先看题目: 第一题: var name window var person1 { name: person1, show1: function () { console.log(this.name) }, show2: () > console.log(th show3: function () { return function () { …...

学习小记-Nacos的服务注册与发现原理
服务注册: 当一个服务实例启动时,它会向 Nacos 服务器注册自己的信息,包括 IP 地址、端口号、元数据(如服务版本、区域信息等)。服务实例使用 Nacos API 发送注册请求,Nacos 服务器接收请求并存储服务实例信…...

视频号矩阵系统源码,实现AI自动生成文案和自动回复私信评论,支持多个短视频平台
在当今短视频蓬勃发展的时代,视频号矩阵系统源码成为了自媒体人争相探索的宝藏。这一强大的技术工具不仅能帮助我们高效管理多个短视频平台,更能通过AI智能生成文案和自动回复私信评论,为自媒体运营带来前所未有的便利与效率。 一、视频号矩…...

[Spring] SpringBoot基本配置与快速上手
🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...

tomcat的优化、动静分离
tomcat的优化 tomcat自身的优化 tomcat的并发处理能力不强,大项目不适应tomcat做为转发动态的中间件(k8s集群,pytnon rubby),小项目会使用(内部使用的)动静分离 默认配置不适合生产环境&…...

Python与自动化脚本编写
Python与自动化脚本编写 Python因其简洁的语法和强大的库支持,成为了自动化脚本编写的首选语言之一。在这篇文章中,我们将探索如何使用Python来编写自动化脚本,以简化日常任务。 一、Python自动化脚本的基础 1. Python在自动化中的优势 Pyth…...

树与二叉树
前言: 树这个结构想必在日常生活中很常见到,而现在要介绍的是一种独特的数据结构--二叉树。 1.树 (1)定义: 是一种非线性结构,有一个特殊的节点叫做根节点,树没有前驱节点;树是递…...

SpringBoot+Vue实现简单的文件上传(Excel篇)
SpringBootVue实现简单的文件上传 1 环境 SpringBoot 3.2.1,Vue 2,ElementUI 2 页面 3 效果:只能上传xls文件且大小限制为2M,选择文件后自动上传。 4 前端代码 <template><div class"container"><el…...

科研绘图系列:R语言金字塔图(pyramid plot)
介绍 金字塔图(Pyramid chart)是一种用于展示人口统计数据的图表,特别是用于展示不同年龄段的人口数量。这种图表通常用于展示人口结构,比如性别和年龄的分布。 特点: 年龄分层:金字塔图按年龄分层,每一层代表一个年龄组。性别区分:通常,男性和女性的数据会被分别展…...

Tomcat多实例
一、Tomcat多实例 Tomcat多实例是指在同一台服务器上运行多个独立的tomcat实例,每个tomcat实例都具有独立的配置文件、日志文件、应用程序和端口,通过配置不同的端口和文件目录,可以实现同时运行多个独立的Tomcat服务器,每个服务…...

前端Vue组件化实践:自定义加载组件的探索与应用
在前端开发领域,随着业务逻辑复杂度的提升和系统规模的不断扩大,传统的开发方式逐渐暴露出效率低下、维护困难等问题。为了解决这些挑战,组件化开发作为一种高效、灵活的开发模式,受到了越来越多开发者的青睐。本文将结合实践&…...

半小时获得一张ESG入门证书【详细中英文笔记一】
前些日子,有朋友转发了一则小红书的笔记给我, 标题是《半小时获CFI官方高颜值免费证书 ESG认证》。这对考证狂魔的我来说,必然不能错过啊,有免费的羊毛不薅白不薅。 ESG课程的 CFI 官方网址戳这里:CFI 于是信心满满的…...

类形断言和和类型推导的区别是什么?
类型断言(Type Assertion)和类型推导(Type Inference)在TypeScript中的区别 如下: 定义: 类型断言:是程序员明确指定一个值的类型,即允许变量从一种类型更改为另一种类型。它不会进行…...

Spring-Spring、IoC、DI、注解开发
1、Spring是什么 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。 Spring整体架构 Spring优点: Spring属于低侵入设计。IOC将对象之间的依赖关系交给Spring,降低组件之间的耦合,实现各个层之间的解耦,让我们更专注于业务…...

Facebook的未来蓝图:从元宇宙到虚拟现实的跨越
随着科技的不断演进和社会的数字化转型,虚拟现实(VR)和增强现实(AR)作为下一代计算平台正逐渐走进人们的视野。作为全球领先的科技公司之一,Facebook正在积极探索并推动这一领域的发展,以实现其…...

Redis6.2.1版本集群新加副本
测试数据 通过redis-benchmark生成测试数据 ./bin/redis-benchmark -h 172.31.4.18 -p 6381 -a Redis_6.2.1_Sc --cluster -t set -d 128 -n 10000000 -r 100000000 -c 200新加节点 172.31.4.18:6381> AUTH Redis_6.2.1_Sc OK172.31.4.18:6381> cluster meet 172.31.4…...

2.The DispatcherServlet
The DispatcherServlet Spring的Web MVC框架与许多其他Web MVC框架一样,是请求驱动的,围绕一个中央Servlet(即DispatcherServlet)设计,该Servlet将请求分派给控制器,并提供其他功能以促进Web应用程序的开发…...

bug定位策略
前提--用户环境层面 hosts异常:hosts文件主要是加快某个域名或者网站的解析速度,从而达到快速访问的作用,也可以屏蔽网站。hosts异常可能会导致部分网页无法访问,能够加载,但是网页无法正常显示;测试环境脏…...

基于R语言的水文、水环境模型优化技术及快速率定方法与多模型案例
在水利、环境、生态、机械以及航天等领域中,数学模型已经成为一种常用的技术手段。同时,为了提高模型的性能,减小模型误用带来的风险;模型的优化技术也被广泛用于模型的使用过程。模型参数的快速优化技术不但涉及到优化本身而且涉…...

内存函数(C语言)
内存函数 以下函数的头文件:string.h 针对内存块进行处理的函数 memcpy 函数原型: void* memcpy(void* destination, const void* source, size_t num);目标空间地址 源空间地址num,被拷贝的字节个数 返回目标空间的起始地…...

力扣 哈希表刷题回顾
哈希表理论总结 什么时候用哈希表,快速判断一个元素是否出现在集合中时,用哈希这种空间换时间的方法。 哈希函数与哈希碰撞 哈希函数是指将key映射到对应的哈希表上 哈希碰撞是指映射的过程中容易出现多对一的情况,用什么方法解决拉链法和…...

Qt 统计图编程
学习目标:Qt 折线图,柱形图和扇形统计图编程 学习基础 Qt QChart 曲线图表操作-CSDN博客 学习内容 Qt中绘制三种常见的图表非常方便, 主要步骤如下: 1. 折线图: - 使用QLineSeries定义折线数据,添加多个坐标点 - 使用QValueAxis创建X轴和Y轴 - 将…...

SQL中的谓词与谓词下推
在 SQL 查询中,谓词(Predicate)是用来对数据进行过滤的条件。它们决定了数据从数据库表中被选择的条件。理解和正确使用 SQL 谓词对于编写高效查询至关重要。 目录 什么是谓词?一个真实的故事SQL 谓词的代码示例比较谓词逻辑谓词…...

浅聊授权-spring security和oauth2
文章目录 前言自定义授权spring security授权oauth2授权概述 前言 通常说到授权,就会想到登录授权、token令牌、JWT等概念,授权。顾名思义就是服务器授予了客户端访问资源的权益,那么要实现授权有几种方案呢,三种授权方式在公司项…...

时间复杂度计算
目录 时间复杂性 ⼤O的渐进表⽰法 时间复杂性 定义:在计算机科学中,算法的时间复杂度是⼀个函数式T(N),它定量描述了该算法的运⾏时间。 时间复杂度是衡量程序的时间效率,那么为什么不去计算程序的运⾏时间呢? 1.…...