并发与竞争
并发与竞争
并发与竞争的产生
Linux是一个多任务操作系统,肯定会存在多个任务共同操作同一段内存或者设备的情况,多个任务甚至中断都能访问的资源叫做共享资源,就和共享单车一样。在驱动开发中要注意对共享资源的保护,也就是要处理对共享资源的并发访问。
并发就是多个“用户”同时访问同一个共享资源。
Linux 系统是个多任务操作系统,会存在多个任务同时访问同一片内存区域,这些任务可能会相互覆盖这段内存中的数据,造成内存数据混乱。针对这个问题必须要做处理,严重的话可能会导致系统崩溃。现在的 Linux 系统并发产生的原因很复杂,总结一下有下面几个主要原因:
①、多线程并发访问,Linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。
②、抢占式并发访问,从 2.6 版本内核开始,Linux 内核支持抢占,也就是说调度程序可以在任意时刻抢占正在运行的线程,从而运行其他的线程。
③、中断程序并发访问,硬件中断的权利是很大的。
④、SMP(多核)核间并发访问,现在 ARM 架构的多核 SOC 很常见,多核 CPU 存在核间并发访问。
并发访问带来的问题就是竞争(临界区就是共享数据段),对于临界区必须保证一次只有一个线程访问,也就是要保证临界区是原子访问的,原子访问就表示这一个访问是一个步骤,不能再进行拆分。如果多个线程同时操作临界区就表示存在竞争,编写驱动的时候要注意避免并发和防止竞争访问。
原子操作
原子操作简介
原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作。
例如C语言的a = 3
,对应的汇编是:
ldr r0, = 0X30000000 /* 变量 a 地址
*ldr r1, = 3
str r1, [r0] /* 将 3 写入到 a 变量中 */
一个最简单的设置变量值的并发与竞争的例子:
原子整形操作 API 函数
Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作,在使用中用原子变量来代替整形变量,此结构体定义在 include/linux/types.h 文件中,如果要使用原子操作 API 函数,首先要先定义一个 atomic_t 的变量。
typedef struct {int counter;
} atomic_t;
例如:一个定时器计数的变量可以定义为:atomic_t count;
定义原子变量的时候给原子变量赋初值:atomic_t b = ATOMIC_INIT(0);
不能直接(atomi_t b=0)
如果使用 64 位的 SOC 的话,就要用到 64 位的原子变量,Linux 内核也定义了 64 位原子结构体。
typedef struct {long counter;
} atomic64_t;
#endif
相应的也提供了 64 位原子变量的操作 API 函数,这里我们就不详细讲解了,和atomic_t的 API 函数有用法一样,只是将“atomic_”前缀换为“atomic64_”,将 int 换为 long long。
原子位操作 API 函数
函数 | 描述 |
---|---|
set_bit(int nr, void *p) | 将p地址的第nr位置1。 |
clear_bit(int nr, void *p) | 将p地址的第nr位清零。 |
change_bit(int nr, void *p) | 将p地址的第nr位进行翻转。 |
test_bit(int nr, void *p) | 获取p地址的第nr位的值。 |
test_and_set_bit(int nr, void *p) | 将p地址的第nr位置1,并且返回nr位原来的值。 |
test_and_clear_bit(int nr, void *p) | 将p地址的第nr位清零,并且返回nr位原来的值。 |
test_and_change_bit(int nr, void *p) | 将p地址的第nr位翻转,并且返回nr位原来的值。 |
自旋锁
原子操作只能对整形变量或者位进行保护,在实际的使用环境中不可能只有整形变量这么简单的临界区。例如:结构体变量就不是整型变量,对于结构体中成员变量的操作也要保证原子性,在线程 A 对结构体变量使用期间,应该禁止其他的线程来访问此结构体变量,这些工作原子操作都不能实现。
自旋锁简介
自旋锁(Spinlock)是 Linux 内核中用于同步不同 CPU 之间对共享资源访问的一种锁机制。当一个CPU 获得自旋锁时,它会阻止其他 CPU 获取同一把锁,直到锁被释放。自旋锁的主要特点是,等待锁的 CPU 不会进入睡眠状态,而是在循环中不断检查锁是否可用,因此称为“自旋”。
- 不可睡眠:持有自旋锁的 CPU 不能进入睡眠状态,这意味着自旋锁适用于不会被长时间持有的场景,否则可能会导致 CPU 资源的浪费。
- 无阻塞:请求自旋锁的进程不会释放 CPU,而是忙等待(busy-wait),直到获得锁。
- 适用于中断上下文:自旋锁可以在中断处理程序中使用,因为中断处理程序不能睡眠。
- 避免死锁:由于自旋锁不会导致进程睡眠,因此不会导致死锁。
- 性能开销:在单核处理器上,自旋锁实际上是无操作,因为不存在多个 CPU 竞争的问题。但在多核处理器上,如果锁被长时间持有,会导致其他 CPU 忙等,从而浪费 CPU 资源。
Linux 内核使用结构体 spinlock_t 表示自旋锁,结构体定义如下所示:
#include <linux/spinlock.h>
typedef struct spinlock {union {struct raw_spinlock rlock;#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))struct {u8 __padding[LOCK_PADSIZE];struct lockdep_map dep_map;};
#endif};
} spinlock_t;
定义自旋锁:spinlock_t lock;
自旋锁 API 函数
函数 | 描述 |
---|---|
DEFINE_SPINLOCK(spinlock_t lock) | 定义并初始化一个自旋锁变量。 |
spin_lock_init(spinlock_t *lock) | 初始化自旋锁。 |
spin_lock(spinlock_t *lock) | 获取指定的自旋锁,也叫做加锁。 |
spin_unlock(spinlock_t *lock) | 释放指定的自旋锁。 |
spin_trylock(spinlock_t *lock) | 尝试获取指定的自旋锁,如果没有获取到就返回0。 |
spin_is_locked(spinlock_t *lock) | 检查指定的自旋锁是否被获取,如果没有被获取就返回非0,否则返回0。 |
自旋锁API 函数适用于 SMP(多核)或支持抢占的单 CPU下线程之间的并发访问,也就是用于线程与线程之间,被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数,否则的话会可能会导致死锁现象的发生。自旋锁会自动禁止抢占,也就说当线程 A得到锁以后会暂时禁止内核抢占。如果线程 A 在持有锁期间进入了休眠状态,那么线程 A 会自动放弃 CPU 使用权。线程 B 开始运行,线程 B 也想要获取锁,但是此时锁被 A 线程持有,而且内核抢占还被禁止了!线程 B 无法被调度出去,那么线程 A 就无法运行,锁也就无法释放,死锁发生了!
中断需要访问共享资源
中断里面可以使用自旋锁,但是在中断里面使用自旋锁的时候,在获取锁之前一定要先禁止本地中断(也就是本CPU中断,对于多核 SOC来说会有多个 CPU 核),否则可能导致锁死现象的发生。
发生死锁的情况:线程 A 先运行,并且获取到了 lock 这个锁,当线程 A 运行 functionA 函数的时候中断发生了,中断抢走了 CPU 使用权。右边的中断服务函数也要获取 lock 这个锁,但是这个锁被线程 A 占有着,中断就会一直自旋,等待锁有效。但是在中断服务函数执行完之前,线程 A 是不可能执行的。
最好的解决方法就是获取锁之前关闭本地中断,Linux 内核提供了相应的 API 函数:
函数(include\linux\spinlock.h) | 描述 |
---|---|
void spin_lock_irq(spinlock_t *lock) | 禁止本地中断,并获取自旋锁 |
void spin_unlock_irq(spinlock_t *lock) | 激活本地中断,并释放自旋锁 |
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags) | 保存中断状态,禁止本地中断,并获取自旋锁 |
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) | 将中断恢复到以前的状态,并且激活本地中断,释放自旋锁 |
使用方法:
使用 spin_lock_irq/spin_unlock_irq 的时候需要用户能够确定加锁之前的中断状态,但实际上很难确定中断状态,因此不推荐使用spin_lock_irq/spin_unlock_irq。建议使用 spin_lock_irqsave/ spin_unlock_irqrestore,因为这一组函数会保存中断状态,在释放锁的时候会恢复中断状态。一般在线程中使用 spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock,示例代码如下所示:
DEFINE_SPINLOCK(lock)/* 定义并初始化一个锁 */
/* 线程 A */
void functionA (){
unsigned long flags;/* 中断状态*/
spin_lock_irqsave(&lock, flags)/* 获取锁*/
/* 临界区 */
spin_unlock_irqrestore(&lock, flags)/* 释放锁*/
}/* 中断服务函数 */
void irq() {
spin_lock(&lock)/* 获取锁*/
/* 临界区 */
spin_unlock(&lock)/* 释放锁*/
}
下半部(BH)也会竞争共享资源,有些资料也会将下半部叫做底半部。如果要在下半部里面使用自旋锁,可以使用下面的API 函数:
函数(include\linux\spinlock.h) | 描述 |
---|---|
void spin_lock_bh(spinlock_t *lock) | 关闭下半部,并获取自旋锁。 |
void spin_unlock_bh(spinlock_t *lock) | 打开下半部,并释放自旋锁。 |
其它类型的自旋锁
读写自旋锁
现在有个学生信息表,此表存放着学生的年龄、家庭住址、班级等信息,此表可以随时被修改和读取。此表肯定是数据,那么必须要对其进行保护,如果我们现在使用自旋锁对其进行保护。每次只能一个读操作或者写操作,但是,实际上此表是可以并发读取的。只需要保证在修改此表的时候没人读取,或者在其他人读取此表的时候没有人修改此表就行了。也就是此表的读和写不能同时进行,但是可以多人并发的读取此表。像这样,当某个数据结构符合读/写或生产者/消费者模型的时候就可以使用读写自旋锁。读写自旋锁为读和写操作提供了不同的锁,一次只能允许一个写操作,也就是只能一个线程持有写锁,而且不能进行读操作。但是当没有写操作的时候允许一个或多个线程持有读锁,可以进行并发的读操作。
Linux 内核使用 rwlock_t
结构体表示读写锁。头文件包含:<linux/rwlock.h>
函数 | 描述 |
---|---|
DEFINERWLOCK(rwlock_t lock) | 定义并初始化读写锁。 |
void rwlock_init(rwlock_t *lock) | 初始化读写锁。 |
读锁 | |
void read_lock(rwlock_t *lock) | 获取读锁。 |
void read_unlock(rwlock_t *lock) | 释放读锁。 |
void read_lock_irq(rwlock_t *lock) | 禁止本地中断,并且获取读锁。 |
void read_unlock_irq(rwlock_t *lock) | 打开本地中断,并且释放读锁。 |
void read_lock_irqsave(rwlock_t *lock, unsigned long flags) | 保存中断状态,禁止本地中断,并获取读锁。 |
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags) | 将中断状态恢复到以前的状态,并且激活本地中断,释放读锁 |
void read_lock_bh(rwlock_t *lock) | 关闭下半部,并获取读锁。 |
void read_unlock_bh(rwlock_t *lock) | 打开下半部,并释放读锁。 |
写锁 | |
void write_lock(rwlock_t *lock) | 获取写锁。 |
void write_unlock(rwlock_t *lock) | 释放写锁。 |
void write_lock_irq(rwlock_t *lock) | 禁止本地中断,并且获取写锁。 |
void write_unlock_irq(rwlock_t *lock) | 打开本地中断,并且释放写锁。 |
void write_lock_irqsave(rwlock_t *lock, unsigned long flags) | 保存中断状态,禁止本地中断,并获取写锁。 |
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags) | 将中断状态恢复到以前的状态,并且激活本地 |
中断,释放读锁。 | |
void write_lock_bh(rwlock_t *lock) | 关闭下半部,并获取读锁 |
void write_unlock_bh(rwlock_t *lock) | 打开下半部,并释放读锁。 |
顺序锁
自旋锁使用注意事项
①、因为在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式,比如稍后要讲的信号量和互斥体。
②、自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能导致死锁。
③、不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己把自己锁死了!
④、在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管你用的是单核的还是多核的 SOC,都将其当做多核 SOC 来编写驱动程序。
信号量
信号量(Semaphore)是操作系统中用于控制多个进程或线程对共享资源访问的一种同步机制。它是一个计数器,用于多进程(或线程)之间的同步,以及提供对共享资源的访问控制。信号量主要解决的问题是资源共享和互斥。
信号量和自旋锁的区别
• 阻塞与非阻塞:
自旋锁:当一个线程尝试获取一个已经被其他线程持有的自旋锁时,它会进入忙等(busy-wait)状态,即不断检查锁是否可用,而不进入睡眠状态。这意味着CPU资源会被占用,因为线程在不断地检查锁的状态。
信号量:当一个线程尝试获取一个不可用的信号量时,它会被阻塞,即操作系统将其挂起,不会占用CPU资源直到信号量变为可用状态。
• 使用场景:
自旋锁:适用于持有锁的时间非常短的情况,因为忙等避免了线程上下文切换的开销,但如果锁被持有的时间较长,会导致CPU资源浪费。
信号量:适用于持有锁的时间可能较长的情况,或者需要同步的资源数量较多时,因为它们允许线程在等待时释放CPU资源。
• 上下文切换:
自旋锁:不涉及上下文切换,因为线程在等待锁时仍然保持运行状态。
信号量:涉及上下文切换,因为线程在等待信号量时会被挂起,直到信号量可用时再被唤醒。
• 优先级问题:
自旋锁:可能导致优先级反转问题,因为低优先级的线程持有锁时,高优先级的线程可能无法获取锁而陷入忙等。
信号量:不会导致优先级反转问题,因为线程在等待信号量时会被阻塞,不会占用CPU资源。
• 可中断性:
自旋锁:通常不可中断,线程在忙等期间不能响应中断。
信号量:可以是可中断的,线程在等待信号量时可以响应中断并处理。总的来说,自旋锁适用于需
信号量的开销要比自旋锁大,因为信号量使线程进入休眠状态以后会切换线程,切换线程会有开销。
如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
计数信号量:当信号量的值可以大于1时,它被称为计数信号量。这种类型的信号量可以用来控制多个相同的源。
二值信号量:当信号量的值只能是0或1时,它被称为二进制信号量或互斥锁。用于确保只有一个线程或进程可以访问特定的资源。
相关文章:

并发与竞争
并发与竞争 并发与竞争的产生 Linux是一个多任务操作系统,肯定会存在多个任务共同操作同一段内存或者设备的情况,多个任务甚至中断都能访问的资源叫做共享资源,就和共享单车一样。在驱动开发中要注意对共享资源的保护,也就是要处…...
Java后端开发 ”Bug“ 分享——订单与优惠卷
“优惠券风波”:一段代码引发的线上事故 起因:优惠券功能上线 故事的开始源于公司新上线的一项促销活动——在用户未使用优惠券时,系统会自动赠送一张优惠券。这个功能不仅能提升用户体验,还能拉动平台的销售额。为了赶上活动上…...

Linux系统之tee命令的基本使用
Linux系统之tee命令的基本使用 一、tee命令介绍二、tee命令的使用帮助2.1 tee命令的help帮助2.2 tee命令帮助解释 三、tee命令的基本使用3.1 写入文件3.2 追加文件3.3 结合sudo命令3.4 结合EOF使用 四、注意事项 一、tee命令介绍 tee 是 Linux 和 Unix 系统中的一个命令&#x…...

idea 8年使用整理
文章目录 前言idea 8年使用整理1. 覆盖application配置2. 启动的时候设置编辑空间大小,并忽略最大空间3. 查询类的关系4. 查看这个方法的引用关系5. 查看方法的调用关系5.1. 查看被调用关系5.2. 查看调用关系 6. 方法分隔线7. 选择快捷键类型8. 代码预览插件9. JReb…...

多个微服务 Mybatis 过程中出现了Invalid bound statement (not found)的特殊问题
针对多个微服务的场景,记录一下这个特殊问题: 如果启动类上用了这个MapperScan注解 在resource 目录下必须建相同的 com.demo.biz.mapper 目录结构,否则会加载不到XML资源文件 。 并且切记是com/demo/biz 这样的格式创建,不要使用…...
k8s,service如何找到容器
Kubernetes之所以需要Service,一方面是因为Pod的IP不是固定的,另一方面则是因为一组Pod实例之间总会有负载均衡的需求 被selector选中的Pod,就称为Service的Endpoints,查看方式: kubectl get endpoints hostnames需要…...

观察者模式和发布-订阅模式有什么异同?它们在哪些情况下会被使用?
大家好,我是锋哥。今天分享关于【观察者模式和发布-订阅模式有什么异同?它们在哪些情况下会被使用?】面试题。希望对大家有帮助; 观察者模式和发布-订阅模式有什么异同?它们在哪些情况下会被使用? 1000道 …...
docker compose deploy fate cluster
官方文档 写的不清晰 KubeFATE,用于生成部署脚本,链接 部署机就是下载了 KubeFATE的主机;运行机就是要安装fate容器的主机(部署机和运行机可以相同) 两个主机:并非必须 centos7,Ubuntu也行Doc…...
字节跳动Java开发面试题及参考答案(数据结构算法-手撕面试题)
怎么判断两个链表是否相交?怎么优化? 判断两个链表是否相交可以采用多种方法。 一种方法是使用双指针。首先分别遍历两个链表,得到两个链表的长度。然后让长链表的指针先走两个链表长度差的步数。之后,同时移动两个链表的指针,每次比较两个指针是否指向相同的节点。如果指…...
网工日记:FTP工作模式
FTP 基本概念 FTP(File Transfer Protocol)即文件传输协议,是用于在网络上进行文件传输的标准协议。它运行在 TCP/IP 协议栈之上,采用客户端 - 服务器(C/S)架构,通过在客户端和服务器之间建立控…...

unity使用代码在动画片段中添加event
unity使用代码在动画片段中添加event using UnityEngine;public static class AnimationHelper {/// <summary>/// 获取Animator状态对应的动画片段/// </summary>/// <param name"animator">Animator组件</param>/// <param name"…...

嵌入式轻量级开源操作系统:HeliOS的使用
嵌入式轻量级开源操作系统:HeliOS的使用 📍项目地址:https://github.com/heliosproj/HeliOS HeliOS项目是一个社区交付的开源项目,用于构建和维护HeliOS嵌入式操作系统(OS)。HeliOS是一个功能齐全的操作系统࿰…...
解决VMware的ubuntu22虚拟机没有网络
解决步骤 1.在 Windows 系统中,按 “WinR” 键,输入 “services.msc” 并回车,在服务列表中找到 “VMware DHCP Service” 和 “VMware NAT Service”,确保这两个服务已启动,若未启动则右键点击选择 “启动”…...

金属衬底介质片对平面波的反射-问题的解析求解和FEM求解
金属衬底介质片对平面波的反射-问题的解析求解和FEM求解 参考有限元从零单排系列4 代码参考了上面大佬文章提供的,但是部分计算系数错了,我改了下加了许多注释,便于大家理解。 书籍参考的电磁场有限元方法(金建铭),所用的公式都…...
2023 年 9 月青少年软编等考 C 语言四级真题解析
目录 T1. 酒鬼T2. 大盗T3. 核电站思路分析T4. 盒子与小球之二思路分析T1. 酒鬼 此题为 2021 年 3 月四级第一题原题,见 2021 年 3 月青少年软编等考 C 语言四级真题解析中的 T1。 T2. 大盗 此题为 2021 年 6 月四级第二题原题,见 2021 年 6 月青少年软编等考 C 语言四级真…...

C++的内存四区
文章目录 内存四区1.程序运行前1.1 代码区2.1 全局区2.2 示例 2.程序运行后1.1 栈区1.2 堆区 内存四区 1.程序运行前 在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域。该区域的数据在程序结束后由操作系统释放. 1.1 代码区 存放 CPU …...

Java爬虫技术:按关键字搜索VIP商品详情
在数字化时代,电子商务平台的竞争日益激烈,而精准的数据采集和分析成为了企业获取竞争优势的关键。对于电商平台而言,能够根据用户输入的关键字快速搜索并展示VIP商品的详细信息,不仅能够提升用户体验,还能够增加销售机…...
C++ —— 模板类与函数
C —— 模板类与函数 模板类可以用于函数的参数和返回值,有三种形式: 普通函数,参数和返回值是模板类的实例化版本。函数模板,参数和返回值是某种的模板类。函数模板,参数和返回值是任意类型(支持普通类和…...

【软考高级】系统架构设计师复习笔记-精华版
文章目录 前言0 系统架构设计师0.1 考架构还是考系分0.2 架构核心知识0.3 架构教材变化 1 计算机操作系统1.1 cpu 组成1.2 内核的五大功能1.3 流水线技术1.4 段页式存储1.5 I/O 软件1.6 文件管理1.7 系统工程相关 2 嵌入式2.1 嵌入式技术2.2 板级支持包(BSP…...

免费 IP 归属地接口
免费GEOIP,查询IP信息,支持IPV4 IPV6 ,包含国家地理位置,维度,asm,邮编 等,例如 例如查询1.1.1.1 http://geoip.91hu.top/?ip1.1.1.1 返回json 对象...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...

FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...