当前位置: 首页 > news >正文

聊一聊原子操作和弱内存序

1、原子操作概念

  在并发编程中,原子操作(Atomic Operation)是实现线程安全的基础机制之一。从宏观上看,原子操作是“不可中断”的单元,但若深入微观层面,其本质是由底层处理器提供的一组特殊指令来保证其原子性。

2、从宏观角度来看原子操作

2.1、概念

  在宏观层面,原子操作被视为保证数据一致性和系统稳定性的关键。在多线程程序中,如果多个线程同时访问和修改同一数据,没有适当的同步机制,就会导致竞态条件(race condition,如果输出的结果依赖于不受控制的事件的出现顺序,那么我们便称发生了 race condition),从而引发数据不一致的问题。

  例如,在多核处理器系统中,有个转账程序,假设有两个账户,里面的余额都是 500,现在要分两次从 A 账户(accountSource)转账 300 到 B 账户(accountTarget),伪代码设计如下:

Function TRANSFER (amount, accountSource, accountTarget) isif accountSource < accountTarget thenreturn;endaccountTarget.balance += amount;accountSource.balance -= amount;
end

  这两次转账操作分别有一个线程执行。如果第二次转账是在第一次转账后发生的,那么第二次转账就会判断发现 A 账户余额不足(accountSource < accountTarget),从而转账失败。

  但是,如果这两次转账在两个线程内同时执行,那么就可能出现不可预测的结果。如下图,两个线程检查 A 账户余额时都是 500,都进行了转账操作,结果 A 账户最后余额是 -100,这样的结果明显是不正确的。
在这里插入图片描述

2.2、程序级别的实现

  从宏观角度来看,互斥锁(Mutex)、同步锁(Synchronization Lock)、自旋锁(Spin Lock)等锁机制可以被理解为是为了确保某一操作或一系列操作在执行时具有原子性。这些锁机制是并发编程中用来控制多个线程对共享资源访问的同步工具,它们通过限制同一时间内只有一个线程可以执行特定的代码段(临界区),从而避免了竞态条件和数据不一致的问题。

操作系统中的锁——信号量(同步信号量、互斥信号量)、P/V操作、自旋锁

  上面的案例,使用互斥锁就可以解决,解决方法如下:

Function TRANSFER (amount, accountSource, accountTarget) isMutexLock(race)if accountSource < accountTarget thenreturn;endaccountTarget.balance += amount;accountSource.balance -= amount;MutexULock(&race)
end

3、从微观角度来看原子操作

3.1、概念

不同的架构采用不同方式实现原子语义。例如:

在 x86 架构中,通过 LOCK 前缀对读写指令加锁,以阻止总线或缓存干扰;

在 ARM 架构中,采用 LDREX / STREX 指令对指定地址建立“独占访问”监控;

而在更现代的处理器中,还结合缓存一致性协议(如 MESI)与内存屏障,实现高效的无锁同步。

从这一层面理解原子操作,不仅有助于深入掌握并发控制原理,也为构建高性能、正确性的底层系统组件奠定基础。

3.2、X86

  我们首先来看下,Linux 中 x86 架构下是如何实现 “原子自增” 的。

arch\x86\include\asm\atomic.h

/*** arch_atomic_inc - increment atomic variable* @v: pointer of type atomic_t** Atomically increments @v by 1.*/
static __always_inline void arch_atomic_inc(atomic_t *v)
{asm volatile(LOCK_PREFIX "incl %0": "+m" (v->counter) :: "memory");
}

这其中,关于宏 “LOCK_PREFIX” 的解释如下:

/**  arch\x86\include\asm\alternative-asm.h*/#ifdef CONFIG_SMP.macro LOCK_PREFIX
672:	lock.pushsection .smp_locks,"a".balign 4.long 672b - ..popsection.endm
#else.macro LOCK_PREFIX.endm
#endif

  关于 x86 下的 lock 指令前缀,在 Intel® 64 and IA-32 Architectures Software Developer’s Manual 中的章节 LOCK-Assert LOCK$ Signal Prefix 中给出了详细解释:

在这里插入图片描述
  简单概括一下上面的描述:

  • x86 中的 LOCK 是一个指令前缀,也就是说 LOCK 会使紧跟在其后面的指令变成原子指令(atomic instruction)
  • “LOCK” 前缀会锁定数据总线,这样同一总线上别的 CPU 就暂时不能通过总线访问该内存了,保证了这条指令在多处理器环境中的原子性
  • LOCK 指令前缀只能加在以下这些指令前面:ADD,ADC,AND,BTC,BTR,BTS,CMPXCHG,CMPXCH8B,CMPXCHG16B,DEC,INC,NEG,NOT,OR,SBB,SUB,XOR,XADD,XCHG,否则就会触发异常
    在这里插入图片描述
  • 从 P6 系列处理器开始,Intel 对原子操作的实现方式进行了优化。处理器在执行原子操作时,是否锁住整个系统总线(bus),取决于该操作是否跨越了缓存行(cache line)
    • 如果原子操作跨越了两个或多个缓存行(cross cache-line),那么就必须通过锁住总线(触发 bus lock)来保证操作的原子性
    • 而如果原子操作完全落在同一个缓存行内,则不需要锁总线,处理器可以借助 MESI 缓存一致性协议,通过缓存锁定(cache lock)来保证原子性,这样效率更高。

浅论Lock 与X86 Cache 一致性

3.2.1 CAS

  CAS(Compare-And-Swap)是一种广泛用于实现无锁、并发算法的原子操作。它的基本语义是:比较一个内存位置的当前值是否为预期值,若相同则将其更新为新值,否则不做修改。 CAS 的原子性由底层硬件保障。通常通过以下过程完成:

  • 比较某个变量的当前值是否等于预期值;
  • 如果相等,则将该变量更新为新值;
  • 如果不相等,则重试操作,直到更新成功。

CAS 的伪代码逻辑如下:

int CAS(int *addr, int expected, int new_val) {if (*addr == expected) {*addr = new_val;return 1; // 成功} else {return 0; // 失败}
}

X86 下 CAS 操作的实现如下:

/** Atomic compare and exchange.  Compare OLD with MEM, if identical,* store NEW in MEM.  Return the initial value in MEM.  Success is* indicated by comparing RETURN with OLD.*/
#define __raw_cmpxchg(ptr, old, new, size, lock)                        \
({                                                                      \__typeof__(*(ptr)) __ret;                                       \__typeof__(*(ptr)) __old = (old);                               \__typeof__(*(ptr)) __new = (new);                               \switch (size) {                                                 \case __X86_CASE_B:                                              \{                                                               \volatile u8 *__ptr = (volatile u8 *)(ptr);              \asm volatile(lock "cmpxchgb %2,%1"                      \: "=a" (__ret), "+m" (*__ptr)              \: "q" (__new), "0" (__old)                 \: "memory");                               \break;                                                  \} 

  可以看到, “CAS 的原子性由底层硬件保障” 其实最终使用的就是 “lock” 指令前缀。


3.3 ARM

  再来看看在 ARM 上,是如何实现 “原子自增” 的。

arch\arm\include\asm\atomic.h

/** ARMv6 UP and SMP safe atomic ops.  We use load exclusive and* store exclusive to ensure that these are atomic.  We may loop* to ensure that the update happens.*/#define ATOMIC_OP(op, c_op, asm_op)					\
static inline void arch_atomic_##op(int i, atomic_t *v)			\
{									\unsigned long tmp;						\int result;							\\prefetchw(&v->counter);						\__asm__ __volatile__("@ atomic_" #op "\n"			\
"1:	ldrex	%0, [%3]\n"						\
"	" #asm_op "	%0, %0, %4\n"					\
"	strex	%1, %0, [%3]\n"						\
"	teq	%1, #0\n"						\
"	bne	1b"							\: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)		\: "r" (&v->counter), "Ir" (i)					\: "cc");							\
}	

可以看到,ARM 架构下主要是通过 LDREX、STREX 指令来实现原子操作:

(1)LDREX:Load-Exclusive

LDREX 指令用于从指定内存地址读取数据,并为该地址建立一个独占访问的监视状态(exclusive monitor):

LDREX Rx, [Ry]

该指令的语义如下:

  • 从 Ry 指向的内存地址读取 4 字节内容,存入寄存器 Rx;
  • 同时在该地址上设置一个本地 CPU 的独占访问标记(exclusive tag),表示当前处理器正在尝试对该地址进行原子更新;
  • 如果在后续执行 STREX 之前,该内存地址被其他处理器或总线设备访问(即产生干扰),则该独占标记会被清除。

(2)STREX:Store-Exclusive

STREX 指令尝试将新的值写入某个内存地址,并仅在该地址仍保持独占访问状态时写入成功:

STREX Rt, Rx, [Ry]

其语义如下:

  • 检查当前处理器是否仍对 Ry 指向的内存地址持有独占访问权;
  • 如果是,写入寄存器 Rx 的值到该地址,并将 Rt 置为 0(表示写入成功);
  • 如果不是(即独占访问状态已失效),则不进行写入,并将 Rt 置为非 0(通常为 1,表示写入失败,需要重试)。

4、什么是弱内存序

  有些 cpu 为了提高性能,引入了一些优化手段,导致指令实际执行的 “效果” 可能与程序顺序不同,具有这种内存模型的 cpu 通常被称为 ”弱内存模型“。同时,还有另外一些原因可能导致指令乱序的效果:

  • 编译器对指令做重排序
  • cache 一致性优化引入的乱序(可参考 ibm 的文章,看一下 store buffer 和 invalid queue 怎么影响指令执行顺序)
  • 指令多发射,有的流水线的指令由于没有阻塞而先执行,有的被阻塞了后执行从而导致顺序变化

4.1 指令乱序的最小约束条件

  看起来比较反直觉,毕竟大多数同学写程序时都不会考虑乱序问题,为什么从不出错?

  有几点原因,一个是对单线程来说,即便乱序发生了也不会影响程序的执行结果,另一个是在多线程程序中,对临界区加锁和解锁本身就自带了内存屏障语义,所以也不会出错。一般在多线程下写无锁算法时才需要考虑乱序,这个一会分析。

  所以首先回答一个关键问题,乱序的边界在哪里?是所有指令都能乱序,还是有一定的约束?

  事实上,为了保证程序的正确性,不管是编译器的指令重排序还是 cpu 指令执行乱序或者是 cache 一致性优化引入的乱序,指令的可见性顺序必须满足单线程语义下的正确性。换句话说,在指令执行的每个时间点上对内存的可见性顺序必须和程序顺序保持一致。

例如以下程序:

a = 1;
b = 2;
c = a;

这里 c = a 必须保证此时能看到 a 对应的内存值为 1,这是符合程序顺序的,否则程序结果就错了,所以以上程序不可能执行成这样:

c = a;
a = 1;
b = 2;

  而其余 “保证可见性顺序和程序顺序一致” 的乱序,理论上都是有可能发生的(只要乱序不会破坏单线程语义下的执行结果,编译器/CPU 就可能这样做)。即,可能发生以下 “不影响结果的乱序”:

b = 2;
a = 1;
c = a;

4.2 多线程下的乱序问题

  根据上述讨论可以知道,单线程情况下即便有乱序行为发生也不会影响程序的执行结果,所以无需担心。然而,在多线程下仅凭以上的约束就不足以保证程序的正确性了。比如以下程序:

T1:                 T2:
a = 123;
b = true;              if (b == true) {print(a);}

按照程序顺序,print(a) 应该打印出 123,但实际执行结果并不一定。按照上一节的约束条件,T1 的执行顺序可能是:

b = true;
a = 123;

这并不影响 T1 对其访问的内存的可见性顺序,但问题是,这改变了 T2 对 a 和 b 的可见性顺序,即 print(a) 的时候,看到了 b 为 true,但还没有看到 a 的最新值 123,导致打印出的 a 是旧值。

所以,多线程下乱序会引起问题,本质上是因为不管是编译器还是 cpu,在处理指令时其本身并没有 “线程” 的概念,无法从多线程角度对指令的执行增加新的约束。

4.3 解决乱序问题

既然编译器和 cpu 都没有线程概念,那需要的 “约束” 条件就要求程序员手工来加了,这就引入了 “内存屏障” (memory barrier)。对上面的程序来说,如果把读写 b 作为同步 a 的手段,想要保证内存可见性顺序须保证两件事:

  1. 从 T1 的角度来看,当 b = true 对外部可见时,a = 123 也必须对外部可见,这个约束叫做 release 语义(通常实现为 write barrier)
  2. 从 T2 的角度来看,当 b = true 可见时,如果 T1 的 b = true 具有 release 语义,那么 happens before b = true 的所有指令的结果对 T2 也必须可见,这种叫做 acquire 语义(通常实现为 read barrier)

加上内存屏障后的代码就一定能保证 print(a) 输出为 123:

T1:                 T2:
a = 123;
write_barrier();
b = true;              if (b == true) {read_barrier();print(a);}

弱内存模型的 CPU 乱序问题检测方法

4.4 内存屏障的使用场景

还是以上面的场景为例:

T1:                 T2:
a = 123;
b = true;              if (b == true) {print(a);}

  不同架构下,是否需要内存屏障、内存屏障的种类、使用方法都可能不同。这就需要具体问题具体分析,因文章篇幅的关系,这里不会详解。下图列出了常见的架构,对于内存乱序方面的差异:
在这里插入图片描述
例如,对于 X86 架构来说,不需要给 T1 和 T2 显式的加上内存屏障。因为 x86 硬件上保证了 T1 和 T2 场景下,不会出现内存乱序。

  • T1:Store → Store
  • T2:Load → Load

但对于 ARM 架构来说,就需要给 T1 和 T2 显式的加上内存屏障,因为 ARM 架构对于 T1 和 T2 场景下,允许发生内存乱序。

5、原子操作与内存屏障之间的联系与区别

5.1 联系

还是以上面的场景为例:

T1:                 T2:
a = 123;
b = true;              if (b == true) {print(a);}

不知道你是否有考虑过,在 ARM 架构下,该场景的内存乱序行为,是否可以通过 原子操作——互斥锁 来解决这个问题?例如:

T1:                 T2:
mutex(&lock)
a = 123;
b = true;
mutex_unlock(&lock)   mutex(&lock)   if (b == true) {print(a);}mutex_unlock(&lock)  

答案是,可以。因为互斥锁的实现中,都会带有内存屏障相关指令的,并且还是多处。

void __sched mutex_lock(struct mutex *lock)
{might_sleep();if (!__mutex_trylock_fast(lock))__mutex_lock_slowpath(lock);
}/** Optimistic trylock that only works in the uncontended case. Make sure to* follow with a __mutex_trylock() before failing.*/
static __always_inline bool __mutex_trylock_fast(struct mutex *lock)
{unsigned long curr = (unsigned long)current;unsigned long zero = 0UL;if (atomic_long_try_cmpxchg_acquire(&lock->owner, &zero, curr))return true;return false;
}/*** atomic_long_try_cmpxchg_acquire() - atomic compare and exchange with acquire ordering* @v: pointer to atomic_long_t* @old: pointer to long value to compare with* @new: long value to assign** If (@v == @old), atomically updates @v to @new with acquire ordering.* Otherwise, updates @old to the current value of @v.** Unsafe to use in noinstr code; use raw_atomic_long_try_cmpxchg_acquire() there.** Return: @true if the exchange occured, @false otherwise.*/
static __always_inline bool
atomic_long_try_cmpxchg_acquire(atomic_long_t *v, long *old, long new)
{instrument_atomic_read_write(v, sizeof(*v));instrument_atomic_read_write(old, sizeof(*old));return raw_atomic_long_try_cmpxchg_acquire(v, old, new);
}

可以看到 atomic_long_try_cmpxchg_acquire() - atomic compare and exchange with acquire ordering。互斥锁的实现,通常都会包含内存屏障指令。

常见函数的后缀:_acquire、_release、_releax 的含义如下:

_acquire:读取方屏障,防止之后的操作被提前 保证后续读取是“看到的最新的”
_release:写入方屏障,防止之前的操作被延后 保证写入对其他线程是可见的
_relaxed:无序,不插入任何内存屏障 完全依赖用户手动控制同步


再例如,以 ARM 架构下的 spin_lock 为例:

spin_lock -> raw_spin_lock -> LOCK_CONTENDED -> do_raw_spin_lock -> arch_spin_lock

/** ARMv6 ticket-based spin-locking.** A memory barrier is required after we get a lock, and before we* release it, because V6 CPUs are assumed to have weakly ordered* memory.*/static inline void arch_spin_lock(arch_spinlock_t *lock)
{unsigned long tmp;u32 newval;arch_spinlock_t lockval;prefetchw(&lock->slock);__asm__ __volatile__(
"1:	ldrex	%0, [%3]\n"
"	add	%1, %0, %4\n"
"	strex	%2, %1, [%3]\n"
"	teq	%2, #0\n"
"	bne	1b": "=&r" (lockval), "=&r" (newval), "=&r" (tmp): "r" (&lock->slock), "I" (1 << TICKET_SHIFT): "cc");while (lockval.tickets.next != lockval.tickets.owner) {wfe();lockval.tickets.owner = READ_ONCE(lock->tickets.owner);}smp_mb();
}

自旋锁实现的结尾,也都会有内存屏障相关指令。

5.2 区别

内存屏障:

  • 不能阻止多个线程同时访问共享资源
  • 不能提供互斥性
  • 不具有睡眠/调度行为
  • 是构建原子操作、无锁队列、自旋锁等的基础

互斥锁:

  • 可能会引起线程上下文切换(如果被阻塞)
  • 比内存屏障更“重”

相关文章:

聊一聊原子操作和弱内存序

1、原子操作概念 在并发编程中&#xff0c;原子操作&#xff08;Atomic Operation&#xff09;是实现线程安全的基础机制之一。从宏观上看&#xff0c;原子操作是“不可中断”的单元&#xff0c;但若深入微观层面&#xff0c;其本质是由底层处理器提供的一组特殊指令来保证其原…...

免费送源码:Java+ssm+MySQL 校园二手书销售平台设计与实现 计算机毕业设计原创定制

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对校园二手书销售平台等问题&#xff0c;对校…...

DAPP实战篇:使用ethersjs连接智能合约并输入地址查询该地址余额

本系列目录 专栏:区块链入门到放弃查看目录-CSDN博客文章浏览阅读400次。为了方便查看将本专栏的所有内容列出目录,按照顺序查看即可。后续也会在此规划一下后续内容,因此如果遇到不能点击的,代表还没有更新。声明:文中所出观点大多数源于笔者多年开发经验所总结,如果你…...

14.【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--微服务基础工具与技术--CAP

CAP 是一款专为 .NET 生态设计的开源框架&#xff0c;其核心目标是解决微服务中跨服务数据一致性问题。在分布式系统中&#xff0c;传统事务无法跨服务保证数据一致性&#xff0c;CAP 通过本地事务与消息记录绑定&#xff0c;再利用消息中间件&#xff08;如 RabbitMQ、Kafka 等…...

智能资源管理机制-重传机制

一、发送端资源管理的核心机制 1. 滑动窗口&#xff08;Sliding Window&#xff09; 这是TCP协议的核心优化设计&#xff1a; 窗口动态滑动&#xff1a;发送端不需要保留所有已发送的分组&#xff0c;只需维护一个"发送窗口"窗口大小&#xff1a;由接收方通告的接…...

【Linux网络与网络编程】08.传输层协议 UDP

传输层协议负责将数据从发送端传输到接收端。 一、再谈端口号 端口号标识了一个主机上进行通信的不同的应用程序。在 TCP/IP 协议中&#xff0c;用 "源IP"&#xff0c;"源端口号"&#xff0c;"目的 IP"&#xff0c;"目的端口号"&…...

局域网下ESP32-S3 LED灯的UDP控制

在局域网下通过IP地址控制ESP32-S3上的LED&#xff0c;可以使用UDP或TCP协议。以下是一个基于UDP协议的完整示例&#xff0c;包括ESP32-S3的服务器代码和一个简单的Python客户端代码。 ESP32-S3 服务器代码 import socket import time import network import machineled Non…...

call、bind、apply

call、bind、apply它们三个都是函数的方法&#xff0c;都可以用于改变this的指向问题。 var person "liangxiao" let obj {name:"张三",say:function() {console.log(this.name);} }obj.say(); setTimeout(function() {obj.say(); },1000) obj.say()打…...

Redis 哨兵模式 搭建

1 . 哨兵模式拓扑 与 简介 本文介绍如何搭建 单主双从 多哨兵模式的搭建 哨兵有12个作用 。通过发送命令&#xff0c;让Redis服务器返回监控其运行状态&#xff0c;包括主服务器和从服务器。 当哨兵监测到master宕机&#xff0c;会自动将slave切换成master&#xff0c;然后通过…...

客户端负载均衡与服务器端负载均衡详解

客户端负载均衡与服务器端负载均衡详解 1. 客户端负载均衡&#xff08;Client-Side Load Balancing&#xff09; 核心概念 定义&#xff1a;负载均衡逻辑在客户端实现&#xff0c;客户端主动选择目标服务实例。典型场景&#xff1a;微服务内部调用&#xff08;如Spring Cloud…...

Ningx负载均衡

Ningx负载均衡 upstream(上游)配置负载均衡1、weight&#xff08;加权轮询&#xff09;2、ip_hash&#xff08;负载均衡&#xff09;3、url hash负载均衡4、least_conn&#xff08;最小连接负载均衡&#xff09; upstream(上游)配置负载均衡 Nginx负载均衡 参考: nginx从安装…...

头歌软件工程导论UML画图题(基于starUML)

一.结构化分析方法-数据流图 本关卡需要画图的一共有5关&#xff0c;直接将此图画好每关提交一次即可&#xff0c;以下的所有图均以此方法提交 二.面向对象分析之用例图 三.面向对象分析之类图 注意此处创建Class之后&#xff0c;双击Class出现以下选项 点击相应的选项创建属性…...

智能车摄像头开源—9 动态权、模糊PID、速度决策、路径优化

目录 一、前言 二、动态权 1.概述 2.偏差值加动态权 三、模糊PID 四、速度决策 1.曲率计算 2.速度拟合 3.速度控制 五、路径 六、国赛视频 一、前言 在前中期通过识别直道、弯道等元素可进行加减速操作实现速度的控制&#xff0c;可进一步缩减一圈的运行速度&#xff…...

java基础 this和super的介绍

this和super this关键字的用法super关键字的用法this与super的区别和注意事项 this关键字的用法 this是自身的一个对象&#xff0c;代表对象本身&#xff0c;可以理解为&#xff1a;指向对象本身的一个指针 class Person{private String name;private int age;public String …...

《Python星球日记》第25天:Pandas 数据分析

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 订阅专栏&#xff1a;《Python星球日记》 目录 一、引言二、数据分组与聚合1. 分组操…...

C++在Linux上生成动态库并调用接口测试

加减乘除demo代码 项目结构 CPP/ ├── calculator.cpp ├── calculator.h ├── main.cpp 头文件 #ifndef CALCULATOR_H #define CALCULATOR_H#ifdef __cplusplus extern "C" {#endifdouble add(double a, double b);double subtract(double a, double b…...

Cesium.js(6):Cesium相机系统

Camera表示观察场景的视角。通过操作摄像机&#xff0c;可以控制视图的位置、方向和角度。 帮助文档&#xff1a;Camera - Cesium Documentation 1 setView setView 方法允许你指定相机的目标位置和姿态。你可以通过 Cartesian3 对象来指定目标位置&#xff0c;并通过 orien…...

机器学习中的数学(PartⅡ)——线性代数:概述

首先引入代数和线性代数的概念&#xff1a; 在将一些直观的、基于经验或直觉的概念转化为严格的数学或逻辑定义时&#xff0c;一种常用方法是构建一组对象和一组操作这些对象的规则&#xff0c;这就是代数。线性代数是研究向量和某些操作向量的规则。 其次从更广泛的意义上定…...

基于双闭环PID控制器的永磁同步电机控制系统匝间故障Simulink仿真

欢迎微♥关注“电击小子程高兴的MATLAB小屋”获取巨额优惠 1.模型简介 本仿真模型基于MATLAB/Simulink&#xff08;版本MATLAB 2013Rb&#xff09;软件。建议采用matlab2013 Rb及以上版本打开。&#xff08;若需要其他版本可联系代为转换&#xff0c;高于该版本的matlab均可正…...

在51单片机上实现平滑呼吸灯:50us定时器PWM实战指南

在51单片机上实现平滑呼吸灯:50us定时器PWM实战指南 引言 本文将详细介绍如何在51单片机平台上,通过精确的50us定时器中断实现无闪烁的呼吸灯效果。相比常见的125us实现方案,50us定时器能提供更高的PWM频率和更细腻的亮度控制。 硬件设计 基本电路配置 主控芯片:SC92F8…...

asm汇编源代码之CPU型号检测

提供1个子程序: 1. CPU型号检测 CPUTYPE 无输入参数&#xff0c;返回值AX指示CPU类型(报歉,当时最新CPU型号只有80486) 函数的返回值详细描述如下 CPUTYPE PROC  FAR ;OUT: AX01, 8086; AX02, 80286; AX03, 80386; AX04, 80486 UP; ; more source code at http://www.ahj…...

提高课:数据结构之树状数组

1&#xff0c;楼兰图腾 #include<iostream> #include<cstring> #include<cstdio> #include<algorithm>using namespace std;typedef long long LL;const int N 200010;int n; int a[N]; int tr[N]; int Greater[N], lower[N];int lowbit(int x) {ret…...

python可变对象与不可变对象

文章目录 Python 中的可变对象与不可变对象不可变对象(Immutable Objects)可变对象(Mutable Objects)重要区别 Python 中的可变对象与不可变对象 在 Python 中&#xff0c;对象可以分为可变对象(mutable)和不可变对象(immutable)&#xff0c;这是 Python 中非常重要的概念&…...

C++学习之金融类安全传输平台项目git

目录 1.知识点概述 2.版本控制工具作用 3.git和SVN 4.git介绍 5.git安装 6.工作区 暂存区 版本库概念 7.本地文件添加到暂存区和提交到版本库 8.文件的修改和还原 9.查看提交的历史版本信息 10.版本差异比较 11.删除文件 12.本地版本管理设置忽略目录 13.远程git仓…...

果篮问题 Python

# 给你两个长度为 n 的整数数组&#xff0c;fruits 和 baskets&#xff0c;其中 fruits[i] 表示第 i 种水果的 数量&#xff0c;baskets[j] 表示第 j 个篮子的 容量。 # 你需要对 fruits 数组从左到右按照以下规则放置水果&#xff1a; # 每种水果必须放入第一个 容量大于等于 …...

Spring 是如何解决循环依赖的?

在使用 Spring 框架进行开发时&#xff0c;循环依赖是一个常见而棘手的问题。循环依赖指的是两个或多个 bean 之间的相互依赖&#xff0c;导致 Spring 容器无法正常创建这些 bean。下面将深入探讨 Spring 如何解决循环依赖问题&#xff0c;并提供一些最佳实践。 什么是循环依赖…...

部署NFS版StorageClass(存储类)

部署NFS版StorageClass存储类 NFS版PV动态供给StorageClass(存储类)基于NFS实现动态供应下载NFS存储类资源清单部署NFS服务器为StorageClass(存储类)创建所需的RBAC部署nfs-client-provisioner的deployment创建StorageClass使用存储类创建PVC NFS版PV动态供给StorageClass(存储…...

深入理解 PyTorch 的 nn.Embedding:词向量映射及变量 weight 的更新机制

文章目录 前言一、直接使用 nn.Embedding 获得变量1、典型场景2、示例代码&#xff1a;3、特点 二、使用 iou_token nn.Embedding(1, transformer_dim) 并访问 iou_token.weight1、典型场景2、示例代码&#xff1a;3、特点 三、第一种方法在模型更新中会更新其值吗&#xff1f…...

go语言内存泄漏的常见形式

go语言内存泄漏 子字符串导致的内存泄漏 使用自动垃圾回收的语言进行编程时&#xff0c;通常我们无需担心内存泄漏的问题&#xff0c;因为运行时会定期回收未使用的内存。但是如果你以为这样就完事大吉了&#xff0c;哪里就大错特措了。 因为&#xff0c;虽然go中并未对字符串…...

操作系统

操作系统 操作系统&#xff08;OperatingSystem&#xff0c;OS&#xff09;是指控制和管理整个计算机系统的硬件和软件资源&#xff0c;并合理地组织调度计算机的工作和资源的分配&#xff1b;以提供给用户和其他软件方便的接口和环境&#xff1b;它是计算机系统中最基本的系统…...

《JVM考古现场(十八):造化玉碟·用字节码重写因果律的九种方法》

"鸿蒙初判&#xff01;当前因果链突破十一维屏障——全体码农修士注意&#xff0c;《JVM考古现场&#xff08;十八&#xff09;》即将渡劫飞升&#xff01;" 目录 上卷阴阳交缠 第一章&#xff1a;混沌初开——JVM因果律的量子纠缠 第二章&#xff1a;诛仙剑阵改—…...

【2】k8s集群管理系列--包应用管理器之helm(Chart语法深入应用)

一、Chart模板&#xff1a;函数与管道 常用函数&#xff1a; • quote&#xff1a;将值转换为字符串&#xff0c;即加双引号 • default&#xff1a;设置默认值&#xff0c;如果获取的值为空则为默认值 • indent和nindent&#xff1a;缩进字符串 • toYaml&#xff1a;引用一…...

汇编获取二进制

mov_.S mov %r8d,0 nop执行命令&#xff1a; gcc -c mov_.S 会输出 mov_.o 文件&#xff1a;objdump -D mov_.o : mov_.o&#xff1a; 文件格式 elf64-x86-64Disassembly of section .text:0000000000000000 <.text>:0: 44 89 04 25 00 00 00 mov %r8d,0x0…...

《嵌套调用与链式访问:C语言中的函数调用技巧》

&#x1f680;个人主页&#xff1a;BabyZZの秘密日记 &#x1f4d6;收入专栏&#xff1a;C语言 &#x1f30d;文章目入 一、嵌套调用&#xff08;一&#xff09;定义&#xff08;二&#xff09;实现方式&#xff08;三&#xff09;优点&#xff08;四&#xff09;缺点 二、链式…...

txt、Csv、Excel、JSON、SQL文件读取(Python)

txt、Csv、Excel、JSON、SQL文件读取&#xff08;Python&#xff09; txt文件读写 创建一个txt文件 fopen(rtext.txt,r,encodingutf-8) sf.read() f.close() print(s)open( )是打开文件的方法 text.txt’文件名 在同一个文件夹下所以可以省略路径 如果不在同一个文件夹下 ‘…...

前端工程化之新晋打包工具

新晋打包工具 新晋打包工具前端模块工具的发展历程分类初版构建工具grunt使用场景 gulp采用管道机制任务化配置与api简洁 现代打包构建工具基石--webpack基于webpack改进的构建工具rollup 推荐举例说明package.jsonrollup.config.mjsmy-extract-css-rollup-plugin.mjssrc/index…...

Python语言介绍

Python 是一种高级、通用、解释型的编程语言&#xff0c;由 Guido van Rossum 于 1991 年首次发布。其设计哲学强调代码的可读性和简洁性。 Python通过简洁的语法和强大的生态系统&#xff0c;成为当今最受欢迎的编程语言之一。 一、核心特点 Python 是一种解释型、面向对象、…...

关于 Spring Boot 部署到 Docker 容器的详细说明,涵盖核心概念、配置步骤及关键命令,并附上表格总结

以下是关于 Spring Boot 部署到 Docker 容器的详细说明&#xff0c;涵盖核心概念、配置步骤及关键命令&#xff0c;并附上表格总结&#xff1a; 1. Docker 核心概念 概念描述关系镜像&#xff08;Image&#xff09;预定义的只读模板&#xff0c;包含运行环境和配置&#xff08…...

Tomcat 服务频繁崩溃的排查方法

# Tomcat 服务频繁崩溃排查方法 当Tomcat服务频繁崩溃时&#xff0c;可以按照以下步骤进行系统化排查&#xff1a; ## 1. 检查日志文件 **关键日志位置**&#xff1a; - catalina.out (标准输出和错误) - catalina.log (主日志) - localhost.log (应用相关日志) - host-mana…...

分布式系统-脑裂,redis的解决方案

感谢你的反馈&#xff01;很高兴能帮到你。关于你提到的“脑裂”&#xff08;split-brain&#xff09;&#xff0c;这是一个分布式系统中的常见术语&#xff0c;尤其在像 Redis Cluster 这样的高可用集群中会涉及。既然你问到了&#xff0c;我会从头解释“脑裂”的含义、Redis …...

MySQL InnoDB 索引与B+树面试题20道

1. B树和B+树的区别是什么? 数据存储位置: B树:所有节点(包括内部节点和叶子节点)均存储数据。 B+树:仅叶子节点存储数据,内部节点仅存储键值(索引)。 叶子节点结构: B+树:叶子节点通过双向链表连接,支持高效的范围查询。 查询稳定性: B+树:所有查询必须走到叶子…...

深入解析 Spring AI Alibaba 多模态对话模型:构建下一代智能应用的实践指南

一、多模态对话模型的技术演进 1.1 从单一文本到多模态交互 现代AI应用正经历从单一文本交互到多模态融合的革命性转变。根据Gartner预测&#xff0c;到2026年将有超过80%的企业应用集成多模态AI能力。Spring AI Alibaba 对话模型体系正是为这一趋势量身打造&#xff0c;其技…...

2025年ESWA SCI1区TOP:动态分类麻雀搜索算法DSSA,深度解析+性能实测

目录 1.摘要2.麻雀搜索算法SSA原理3.孤立微电网经济环境调度4.改进策略5.结果展示6.参考文献7.代码获取 1.摘要 污染物排放对环境造成负面影响&#xff0c;而可再生能源的不稳定性则威胁着微电网的安全运行。为了在保障电力供应可靠性的同时实现环境和经济目标的平衡&#xff…...

MySQL Error Log

MySQL Error Log Error Log 的开启Error Log 查看Error Log 滚动 MySQL Error Log MySQL主从复制&#xff1a;https://blog.csdn.net/a18792721831/article/details/146117935 MySQL Binlog&#xff1a;https://blog.csdn.net/a18792721831/article/details/146606305 MySQL Ge…...

让DeepSeek API支持联网搜索

引子 DeepSeek官网注册的API token是不支持联网搜索的&#xff0c;这导致它无法辅助分析一些最新的情况或是帮忙查一下互联网上的资料。本文从实战角度提供一种稳定可靠的方法使得DeepSeek R1支持联网搜索分析。 正文 首先登录火山方舟控制台&#xff0c;https://www.volcen…...

SQL 语句说明

目录 数据库和数据表什么是 SQL 语言数据操作语言&#xff08;DML&#xff09;1、SELECT 单表查询通过 WHERE 对原始数据进行筛选通过 聚合函数 获取汇总信息通过 ORDER BY 对结果排序通过 GROUP BY 对数据进行分组通过 HAVING 对分组结果进行筛选 2、SELECT 多表查询3、INSERT…...

PostgreSQL内幕探索—基础知识

PostgreSQL内幕探索—基础知识 PostgreSQL&#xff08;以下简称PG&#xff09; 起源于 1986 年加州大学伯克利分校的 ‌POSTGRES 项目‌&#xff0c;最初以对象关系模型为核心&#xff0c;支持高级数据类型和复杂查询功能‌。 1996 年更名为 PostgreSQL 并开源&#xff0c;逐…...

Springboot项目正常启动,访问资源却出现404错误如何解决?

我在自己的springboot项目中的启动类上同时使用了SprinBootApplication和ComponentScan注解, 虽然项目能够正常启动,但是访问资源后,返回404错误,随后在启动类中输出bean,发现controller创建失败: 而后我将ComponentScan去掉后资源就能访问到了. 原因 SprinBootApplication本身…...

MaxPooling层的作用(通俗解释)

MaxPooling层的作用&#xff08;通俗解释&#xff09; MaxPooling层是卷积神经网络中非常重要的组成部分&#xff0c;它的主要作用可以用以下几个简单的比喻来理解&#xff1a; 1. 信息压缩器&#xff08;降维作用&#xff09; 就像把一张高清照片缩小尺寸一样&#xff0c;M…...

0.DockerCE起步之Linux相关【完善中】

ubuntu用户组&权限&文件/目录 服务启停操作 sudo systemctl start docker # 启动服务3,4 sudo systemctl stop docker # 停止服务 sudo systemctl restart docker ps top 以下内容参考 Vim编辑器 Linux系统常用命令 管理Linux实例软件源 Cron定时任务 在Linux系统上…...