Linux 内核自旋锁spinlock(二)--- ticket spinlock
文章目录
- 前言
- 一、ticket spinlock
- 二、源码分析
- 2.1 spin_lock_init
- 2.2 spin_lock
- 2.2 spin_unlock
- 参考资料
前言
自旋锁是 Linux 内核中最底层的互斥机制。因此,它们对内核的安全性和性能有着巨大的影响,因此对各种(特定架构的)自旋锁实现进行了大量的优化工作并不奇怪。2.6.25 版本引入ticket spinlock。
在 x86 架构中,2.6.24 内核中的自旋锁由一个整数值表示。值为一表示锁是可用的。spin_lock() 函数的代码通过以系统范围的原子方式递减该值,然后查看结果是否为零来工作;如果是,则成功获取了锁。如果递减操作的结果为负数,spin_lock() 函数会知道该锁被其他线程占有。因此,它会在一个紧密的循环中忙等(“自旋”),直到锁的值变为正数;然后它会回到开头并再次尝试获取锁。
在关键部分执行完毕后,锁的所有者通过将其设置为1来释放锁。
这种实现非常快速,特别是在无争用情况下(大部分情况下应该是这样)。它还可以很容易地看出对锁的争用有多严重 - 锁的值越负,尝试获取它的处理器就越多。但是,这种方法存在一个缺点:它是不公平的。一旦释放锁,能够递减它的第一个处理器将成为新的所有者。无法确保等待时间最长的处理器首先获得锁;实际上,刚释放锁的处理器可能由于拥有该缓存行,在决定快速重新获取锁时具有优势。
一旦spinlock被释放,第一个能够成功执行CAS操作的CPU将成为新的owner,没有办法确保在该spinlock上等待时间最长的那个CPU优先获得锁,这将带来延迟不能确定的问题。
有可能存在多个线程同时争抢自旋锁的情况,但老版本的实现无法保证先抢的一定能先得到自旋锁。因此新版本(2.6.25以后)的实现排队功能,也就是先到先得。
因此Linux内核 2.6.25以后的的自旋锁 称为 ticket spinlock,基于 FIFO 算法的排队自选锁。
一、ticket spinlock
自旋锁变为16位量,分为两个字节:
next | owner
每个字节可以被视为一个票号(ticket)。比如在商店吃饭,在那里顾客拿着纸质票据以确保按到达顺序接受服务,你可以将“next”字段视为出现在取票机上下一张票上的号码,而“owner”是出现在柜台上“现在服务”的显示器上的号码。
比如:
(1)顾客A来时,next 和 owner都是0,next 等于 owner,说明锁没有被持有,可以加锁,此时饭店没有客人,顾客A的排号是0,直接进餐,同事next++,next = 1 。
(2)顾客B来时,next = 1 owner = 0,next 不等于 owner,说明锁被持有,此时饭店有客人在用餐,顾客B的排号是1,等待进餐,同事next++,next = 2 。
(3)顾客A吃完,owner++,owner = 1,此时顾客B的排号是1,owner = 顾客B的排号,因此顾客B用餐。
因此,在这个方案中,锁的值(两个字段)被初始化为零。spin_lock()函数首先注意到锁的值,然后递增“next”字段 - 所有操作都在单个原子操作中完成。如果“next”字段的值(在递增之前)等于“owner”,则已经获取了锁,工作可以继续进行。否则,处理器将自旋,等待直到“owner”递增到正确的值。在这个方案中,释放锁只需要简单地递增“owner”字段。
在旧的自旋锁实现中,所有争夺锁的处理器都争先恐后地抢夺锁。现在它们很好地排队等候,并按到达顺序获取锁。多线程运行时间变得更加平稳,最大延迟降低了(而且更重要的是,变得确定性)。
不同的架构以及内核版本字段稍有差异,owner对应图中 current_ticket,next对应图中 next_ticket。
由于大多数自旋锁结构嵌入在属于同一缓存行的数据结构中,对缓存行进行一致性监控以读取当前头部或票据号会对锁持有者造成缓存丢失的不利影响。由于锁持有者以独占状态获取缓存行,因此由于多个线程读取当前头部位置或票据号的锁状态而导致缓存行的共享状态,会导致缓存行丢失。
可见,使用ticket spinlock可以让CPU按照到达的先后顺序,去获取spinlock的所有权,形成了「有序竞争」。根据硬件维护的cache一致性协议,如果spinlock的值没有更改,那么在busy wait时,试图获取spinlock的CPU,只需要不断地读取自己包含这个spinlock变量的cache line上的值就可以了,不需要从spinlock变量所在的内存位置读取。
但是,当spinlock的值被更改时,所有试图获取spinlock的CPU对应的cache line都会被invalidate,因为这些CPU会不停地读取这个spinlock的值,所以"invalidate"状态意味着此时,它们必须重新从内存读取新的spinlock的值到自己的cache line中。
而事实上,其中只会有一个CPU,也就是队列中最先达到的那个CPU,接下来可以获得spinlock,也只有它的cache line被invalidate才是有意义的,对于其他的CPU来说,这就是做无用功。内存比cache慢那么多,开销可不小。
在存在争用的锁的情况下,缓存争用似乎不是一个很大的问题。等待锁的 CPU 会以共享模式缓存其内容;直到拥有该锁的 CPU 释放它之前,不应该发生缓存跳动。释放锁(以及被另一个 CPU 获取)需要对锁进行写操作,这需要独占的缓存访问。那时的缓存行移动会造成损害,但可能不会像等待锁一样严重。因此,在存在争用的情况下,似乎尝试优化缓存行为不太可能产生太多有用的结果。
然而,这个情况并不完全;还必须考虑另外几个事实。处理器不会缓存单个值;它们会将连续的 128 个字节(通常)作为单个单位缓存为一个“行”。换句话说,在任何现代处理器中,缓存行几乎肯定比保存自旋锁所需的更大。因此,当一个 CPU 需要对自旋锁的缓存行进行独占访问时,它还会获得对周围大量数据的独占访问。这就是另一个重要细节的作用所在:自旋锁往往嵌入在它们保护的数据结构内部,因此周围的数据通常是持有锁的 CPU 感兴趣的数据。
内核代码会获取一个锁来处理(通常是修改)结构的内容。通常,修改受保护结构内的字段需要访问保存结构自旋锁的同一缓存行。如果锁没有争用,这种访问不是问题;拥有锁的 CPU 可能也拥有缓存行。但是,如果锁受到争用,将会有一个或多个其他 CPU 不断查询其值,获取对同一缓存行的共享访问,并剥夺锁持有者所需的独占访问。因此,在受影响的缓存行内的数据后续修改将导致缓存丢失。因此,查询受争用锁的 CPU 可以显著减慢锁持有者的速度,即使该持有者并未直接访问锁。
二、源码分析
typedef struct arch_spinlock {union {__ticketpair_t head_tail;struct __raw_tickets {__ticket_t head, tail;} tickets;};
} arch_spinlock_t;
简化成:
typedef struct arch_spinlock {struct __raw_tickets {__ticket_t head, tail;} tickets;
} arch_spinlock_t;
head 和 tail 也就是 owner 和 next ,排队自旋锁的原理很简单,就是判断head和tail两个变量的值,如果相等则为未加锁,否则说明已经处于加锁状态。自旋锁初始化将head和tail都设置为0。当有线程加锁的时候,首先判断head和tail是否相等,相等就将tail加1,此时加锁成功。如果两者不相等则表示已经有其它线程加锁,此时只能等待。释放锁就将head加1。
2.1 spin_lock_init
// v3.10/source/include/linux/spinlock.h#define spin_lock_init(_lock) \
do { \spinlock_check(_lock); \raw_spin_lock_init(&(_lock)->rlock); \
} while (0)
// v3.10/source/include/linux/spinlock.h# define raw_spin_lock_init(lock) \do { *(lock) = __RAW_SPIN_LOCK_UNLOCKED(lock); } while (0)
// v3.10/source/include/linux/spinlock_types.h#define __RAW_SPIN_LOCK_INITIALIZER(lockname) \{ \.raw_lock = __ARCH_SPIN_LOCK_UNLOCKED, \SPIN_DEBUG_INIT(lockname) \SPIN_DEP_MAP_INIT(lockname) }#define __RAW_SPIN_LOCK_UNLOCKED(lockname) \(raw_spinlock_t) __RAW_SPIN_LOCK_INITIALIZER(lockname)
// v3.10/source/arch/x86/include/asm/spinlock_types.h#define __ARCH_SPIN_LOCK_UNLOCKED { { 0 } }
自旋锁初始化将head和tail都设置为0。
2.2 spin_lock
// v3.10/source/include/linux/spinlock.hstatic inline void spin_lock(spinlock_t *lock)
{raw_spin_lock(&lock->rlock);
}
// v3.10/source/include/linux/spinlock.h#define raw_spin_lock(lock) _raw_spin_lock(lock)
// v3.10/source/kernel/spinlock.cvoid __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{__raw_spin_lock(lock);
}
EXPORT_SYMBOL(_raw_spin_lock);
// v3.10/source/include/linux/spinlock_api_smp.hstatic inline void __raw_spin_lock(raw_spinlock_t *lock)
{preempt_disable();spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
// v3.10/source/include/linux/spinlock.hstatic inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{__acquire(lock);arch_spin_lock(&lock->raw_lock);
}
// v3.10/source/arch/x86/include/asm/spinlock.hstatic __always_inline void arch_spin_lock(arch_spinlock_t *lock)
{__ticket_spin_lock(lock);
}
// v3.10/source/arch/x86/include/asm/spinlock.h/** Ticket locks are conceptually two parts, one indicating the current head of* the queue, and the other indicating the current tail. The lock is acquired* by atomically noting the tail and incrementing it by one (thus adding* ourself to the queue and noting our position), then waiting until the head* becomes equal to the the initial value of the tail.** We use an xadd covering *both* parts of the lock, to increment the tail and* also load the position of the head, which takes care of memory ordering* issues and should be optimal for the uncontended case. Note the tail must be* in the high part, because a wide xadd increment of the low part would carry* up and contaminate the high part.*/
static __always_inline void __ticket_spin_lock(arch_spinlock_t *lock)
{register struct __raw_tickets inc = { .tail = 1 };inc = xadd(&lock->tickets, inc);for (;;) {if (inc.head == inc.tail)break;cpu_relax();inc.head = ACCESS_ONCE(lock->tickets.head);}barrier(); /* make sure nothing creeps before the lock is taken */
}
typedef struct arch_spinlock {union {__ticketpair_t head_tail;struct __raw_tickets {__ticket_t head, tail;} tickets;};
} arch_spinlock_t;
typedef struct arch_spinlock {struct __raw_tickets {__ticket_t head, tail;} tickets;
} arch_spinlock_t;
核心思想
票证锁通过两个计数器(head 和 tail)实现公平的锁机制,保证线程按请求顺序获取锁(类似排队叫号系统)。
(1)tail:表示下一个可分配的“排队号”(线程获取锁时自增)。
(2)head:表示当前允许持有锁的“排队号”。
线程需等待直到自己的号(tail)与 head 相等才能获得锁。
票据锁由当前队列头部和尾部两部分构成。通过原子操作,首先注意到尾部并将其递增一,从而将自己添加到队列并记录位置,然后等待直到头部变为尾部的初始值。
(1)首先,创建一个名为 inc 的结构体 __raw_tickets,并将其初始化为 { .tail = 1 }。
使用 xadd 函数来原子地递增锁的票据数,并将其赋值给 inc。xadd 操作同时覆盖了锁的两个部分,即增加尾部并加载头部的位置,处理了内存排序问题。
inc = xadd(&lock->tickets, inc);
/* * An exchange-type operation, which takes a value and a pointer, and* returns the old value.*/
#define __xchg_op(ptr, arg, op, lock) \({ \__typeof__ (*(ptr)) __ret = (arg); \switch (sizeof(*(ptr))) { \case __X86_CASE_B: \asm volatile (lock #op "b %b0, %1\n" \: "+q" (__ret), "+m" (*(ptr)) \: : "memory", "cc"); \break; \case __X86_CASE_W: \asm volatile (lock #op "w %w0, %1\n" \: "+r" (__ret), "+m" (*(ptr)) \: : "memory", "cc"); \break; \case __X86_CASE_L: \asm volatile (lock #op "l %0, %1\n" \: "+r" (__ret), "+m" (*(ptr)) \: : "memory", "cc"); \break; \case __X86_CASE_Q: \asm volatile (lock #op "q %q0, %1\n" \: "+r" (__ret), "+m" (*(ptr)) \: : "memory", "cc"); \break; \default: \__ ## op ## _wrong_size(); \} \__ret; \})#define __xadd(ptr, inc, lock) __xchg_op((ptr), (inc), xadd, lock)
#define xadd(ptr, inc) __xadd((ptr), (inc), LOCK_PREFIX)
注意 xadd 返回的是旧值,而不是xadd后的新值。
xadd 是一个原子操作(Exchange & Add),初始化时 lock->tickets.tail = lock->tickets.head = 0,完成两件事:
原子地将 inc.tail(即 1)加到 lock->tickets.tail,相当于为当前线程分配一个“排队号”。
返回操作前的 lock->tickets 旧值(包含旧的 head = 0 和 tail = 0)。
执行后:
inc.tail 变为当前线程的“排队号”(旧 tail 值) = 0。
inc.head 被设为旧 head 值 = 0。
注释提到 tail 必须放在结构体的高位(如 64 位的高 32 位),因为 xadd 对低位的原子操作可能进位污染高位。例如,32 位的 xadd 操作低 16 位时,进位会影响高 16 位。
(2)然后,进入一个无限循环,检查 inc.head 是否等于 inc.tail,如果相等则跳出循环。如果不相等,则循环,在循环中,调用 cpu_relax 函数,然后将 inc.head 更新为 lock->tickets.head 的值。这个步骤是为了等待直到头部变为尾部的初始值。
循环条件:检查当前线程的“排队号”(inc.tail)是否等于当前 head。
若相等(inc.head == inc.tail),表示轮到当前线程持有锁,退出循环。
若不相等,继续等待。
cpu_relax():提示 CPU 减少自旋等待的能耗(如执行 pause 指令),避免总线争用。
/* REP NOP (PAUSE) is a good thing to insert into busy-wait loops. */
static inline void rep_nop(void)
{__asm__ __volatile__("rep;nop": : :"memory");
}#define cpu_relax() rep_nop()
示例流程
假设初始时 head=0, tail=0:
线程A 调用锁:
xadd 后 tail=1,返回旧值 head=0, tail=0 → inc.head=0, inc.tail=0。检查 inc.head == inc.tail(0 == 0),直接获得锁。
线程B 调用锁:
xadd 后 tail=2,返回旧值 head=0, tail=1 → inc.head=0, inc.tail=1。自旋等待直到 head 变为 1。
线程A 释放锁:
将 head 从 0 增到 1。线程B 发现 head=1 与 inc.tail=1 相等,获得锁。
2.2 spin_unlock
// v3.10/source/include/linux/spinlock.hstatic inline void spin_unlock(spinlock_t *lock)
{raw_spin_unlock(&lock->rlock);
}
// /v3.10/source/include/linux/spinlock.h#define raw_spin_unlock(lock) _raw_spin_unlock(lock)
// v3.10/source/kernel/spinlock.cvoid __lockfunc _raw_spin_unlock(raw_spinlock_t *lock)
{__raw_spin_unlock(lock);
}
// v3.10/source/include/linux/spinlock_api_smp.hstatic inline void __raw_spin_unlock(raw_spinlock_t *lock)
{spin_release(&lock->dep_map, 1, _RET_IP_);do_raw_spin_unlock(lock);preempt_enable();
}
static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock)
{arch_spin_unlock(&lock->raw_lock);__release(lock);
}
// v3.10/source/arch/x86/include/asm/spinlock.hstatic __always_inline void arch_spin_unlock(arch_spinlock_t *lock)
{__ticket_spin_unlock(lock);
}
// v3.10/source/arch/x86/include/asm/spinlock.hstatic __always_inline void __ticket_spin_unlock(arch_spinlock_t *lock)
{__add(&lock->tickets.head, 1, UNLOCK_LOCK_PREFIX);
}
在函数中,使用 __add 函数对 lock->tickets.head 执行加1操作,以解锁该自旋锁。这段代码简单地对票据自旋锁进行解锁操作,通过增加 lock->tickets.head 的值来释放锁。
参考资料
https://lwn.net/Articles/267968/
https://zhuanlan.zhihu.com/p/62363777
http://www.wowotech.net/kernel_synchronization/spinlock.html
https://zhuanlan.zhihu.com/p/80727111
https://www.cnblogs.com/lovemengx/p/16989679.html
相关文章:
Linux 内核自旋锁spinlock(二)--- ticket spinlock
文章目录 前言一、ticket spinlock二、源码分析2.1 spin_lock_init2.2 spin_lock2.2 spin_unlock 参考资料 前言 自旋锁是 Linux 内核中最底层的互斥机制。因此,它们对内核的安全性和性能有着巨大的影响,因此对各种(特定架构的)自…...
Elixir语言的计算机基础
Elixir语言的计算机基础 引言 Elixir是一种现代的编程语言,建立在Erlang虚拟机(BEAM)上,专注于并发、分布式系统和容错能力。随着互联网的发展,应用程序的需求变得越来越复杂,Elixir凭借其高效的性能、灵…...
MindStudio制作MindSpore TBE算子(二)算子测试
在上一节中,成功制作了Mindspore的Add算子,具体可以查看MindStudio制作MindSpore TBE算子(一)算子制作,这一节,一起看看如何对算子进行测试。 建议参考以下内容一起食用: 算子代码实现 MindSpor…...
深度解读城市地下网管管廊改造要点
引 言 近日国家发改委和住建部联合发布通知(后附),要求各地抓紧编制“城市地下管网和综合管廊建设改造实施方案”并于12月27日前报国家发改委和住建部相关司处,逾期未报的城市(县、区),视同自愿…...
Docker 部署 redis | 国内阿里镜像
一、简易单机版 1、镜像拉取 # docker hub 镜像 docker pull redis:7.0.4-bullseye # 阿里云镜像 docker pull alibaba-cloud-linux-3-registry.cn-hangzhou.cr.aliyuncs.com/alinux3/redis_optimized:20240221-6.2.7-2.3.0 2、运行镜像 docker run -itd --name redis \n …...
Day88:加载游戏图片
在游戏开发中,加载和显示图片是非常常见的需求,尤其是在 2D 游戏 中,角色、背景、道具、敌人等都需要用图片来表示。今天,我们将学习如何在 Python 游戏开发中使用 Pygame 加载并显示图片。 1. 加载游戏图片的基本步骤 在 Pygame 中加载图片通常需要以下几个步骤: 导入 P…...
Elasticsearch:在 Elastic 中玩转 DeepSeek R1 来实现 RAG 应用
在这个春节,如一声春雷,DeepSeek R1 横空出世。现在人人都在谈论 DeepSeek R1。这个大语言模型无疑在中国及世界的人工智能发展史上留下了重要的里程碑。那么我们改如何结合 DeepSeek R1 及 Elasticsearch 来实现 RAG 呢?在之前的文章 “使用…...
SOME/IP报文格式及发现协议详解
在之前的文章中,我们介绍了SOME/IP协议的几种服务接口。在本篇博客中,主要介绍some/ip协议传输的header报文格式以及SOME/IP-SD发现协议。 目录 流程 报文格式 Message ID Length Request ID protocal version/Interface Version Message Type…...
elementplus 使用日期时间选择器,设置可选范围为前后大于2年且只能选择历史时间不能大于当前时间点
需求:时间选择器可选的时间范围进行限制,-2年<a<2年且a<new Date().getTime()核心:这里需要注意plus版没有picker-options换成disabled-date属性了,使用了visible-change和calendar-change属性逻辑:另设一个参…...
C语言·关键字·char关键字
C语言菜鸟入门关键字char关键字_c char-CSDN博客...
Ansible简单介绍及用法
一、简介 Ansible是一个简单的自动化运维管理工具,基于Python语言实现,由Paramiko和PyYAML两个关键模块构建,可用于自动化部署应用、配置、编排task(持续交付、无宕机更新等)。主版本大概每2个月发布一次。 Ansible与Saltstack最大的区别是…...
Mac 本地搭建自己的 DeepSeek
Mac 本地搭建自己的 DeepSeek 安装 Ollama通过Ollama命令安装 DeepSeek 模型安装一个UI客户端,提升体验 注:本文章完全参考网上教程,没有丝毫原创,只是记录一下我本人在安装DeepSeek 的步骤 安装 Ollama https://ollama.com/dow…...
深度学习-交易预测
下面为你详细介绍如何使用Python结合深度学习库TensorFlow和Keras来构建一个简单的交易预测模型。在这个示例中,我们以股票价格预测为例,假设我们要根据过去一段时间的股票价格数据来预测未来的价格走势。 步骤分析 数据准备:获取股票价格数…...
Prompt逆向工程:如何“骗“大模型吐露其Prompt?
提示词的“逆向工程”,让AI大语言模型帮你反推提示词 一、前言 在日常生活中,我们不时会遇到一些令人惊艳的文本,不论是一篇精彩绝伦的小说、一篇深入浅出的科普文章,还是一篇充满热情的音乐推荐,它们都能在我们的心…...
游戏手柄Type-c方案,支持一边充电一边传输数据
乐得瑞推出LDR6023SS,专门针对USB-C接口手机手柄方案,支持手机快充,支持任天堂游戏机,PS4等设备~同时支持手机充电跟数据传输 1、概述 LDR6023SS SSOP16 是乐得瑞科技针对 USB Type-C 标准中的 Bridge 设备而开发的双 USB-C DRP …...
Vue设计模式到底多少种?
Vue设计模式到底多少种? 很多同学问,Vue到底有多少种设计模式??各个模式到底是什么意思??又各自适合什么场景?? 这里我给大家直接说下,Vue的设计模式没有一个固定的数值…...
C++ 中的 std::timed_mutex 和 std::recursive_timed_mutex
1、背景 在多线程编程中,互斥锁(Mutex)是用于保护共享资源的重要工具。C 标准库提供了多种互斥锁类型,其中 std::timed_mutex 和 std::recursive_timed_mutex 是两种支持超时功能的互斥锁。在阅读FastDDS源码时,发现了…...
HAL库外设宝典:基于CubeMX的STM32开发手册(持续更新)
目录 前言 GPIO(通用输入输出引脚) 推挽输出模式 浮空输入和上拉输入模式 GPIO其他模式以及内部电路原理 输出驱动器 输入驱动器 中断 外部中断(EXTI) 深入中断(内部机制及原理) 外部中断/事件控…...
Kotlin实战经验:将接口回调转换成suspend挂起函数
在 Kotlin 协程中, suspendCoroutine 和 suspendCancellableCoroutine 是用于将回调或基于 future 的异步操作转换成挂起函数。 suspendCoroutine 用途:将回调式异步操作转换为可挂起函数 行为: 启动一个新的协程来处理基于回调的操作挂起当前协程,直到调用回调回调负责…...
银行国际结算
银行国结项目,即国际结算项目,是银行业务中的重要组成部分,它涉及跨国界的货币收付和资金转移。 一、银行国结项目的定义 银行国结项目是指银行为国际贸易、投资等活动提供的国际结算服务,包括各种国际支付和资金清算业务。这些…...
java后端开发day13--面向对象综合练习
(以下内容全部来自上述课程) 注意:先有javabean,才能创建对象。 1.文字版格斗游戏 格斗游戏,每个游戏角色的姓名,血量,都不相同,在选定人物的时候(new对象的时候&#…...
Vue解决父子组件传值,子组件改变值后父组件的值也改变的问题
vue开发过程中,父组件通过props传值给子组件,子组件在页面展示父组件的值,在操作子组件值以后,即使不点击确定按钮,父组件中的值也发生了变化,但是需求是操作子组件数据以后,必须点击"确定…...
【通俗解释,入门级】DeepSeek - R1 - Zero:强化学习提升LLM推理能力的奥秘
DeepSeek - R1 - Zero:强化学习提升LLM推理能力的奥秘 第一节:强化学习在DeepSeek - R1 - Zero中的基本概念与公式解释【通俗解释】 强化学习在DeepSeek - R1 - Zero里就像是一位“聪明的探险家”,在各种可能的推理路径中探索,通…...
《图解设计模式》笔记(六)访问数据结构
十三、Visitor 模式:访问数据结构并处理数据 Visitor:访问者 我们会“处理”在数据结构中保存着的元素,通常把“处理”代码放在表示数据结构的类中。 但每增加一种处理,就不得不去修改表示数据结构的类。 在 Visitor模式中&am…...
windows11上,使用pipx安装Poetry,Poetry的安装路径是什么?
当使用 pipx 安装 Poetry 时,pipx 会将 Poetry 安装到一个独立的虚拟环境中,并将其可执行文件链接到一个集中的目录中。以下是 pipx 安装 Poetry 时的路径信息: 1. Poetry 的安装路径 pipx 会为每个工具(如 Poetry)创…...
使用 vcpkg 简化 C++ 项目依赖管理
使用 vcpkg 简化 C 项目依赖管理 什么是 vcpkg? vcpkg 是微软推出的跨平台 C/C 包管理工具,支持 Windows/Linux/macOS。它可以帮助开发者: ✅ 一键安装 2000 开源库 ✅ 自动解决依赖关系 ✅ 生成 Visual Studio 集成配置 ✅ 支持自定义编译…...
怎样确定网站访问速度出现问题是后台还是服务器造成的?
网站的访问速度会影响到用户的体验感,当网络过于卡顿或访问速度较慢时,会给用户带来不好的体验感,但是网站访问速度不仅会是后台造成影响的,也可能是服务器的原因,那么我们该如何分辨呢? 当网站使用了数据库…...
【Elasticsearch】管道聚合
管道聚合就是在已有聚合结果之上在进行聚合,管道聚合是针对于聚合的聚合 在 Elasticsearch 中,管道聚合(Pipeline Aggregations)是一种特殊的聚合类型,用于对其他聚合的结果进行进一步的计算和处理,而不是直…...
CNN-GRU卷积神经网络门控循环单元多变量多步预测,光伏功率预测(Matlab完整源码和数据)
代码地址:CNN-GRU卷积神经网络门控循环单元多变量多步预测,光伏功率预测(Matlab完整源码和数据) CNN-GRU卷积神经网络门控循环单元多变量多步预测,光伏功率预测 一、引言 1.1、研究背景和意义 随着全球能源危机和环境问题的日…...
后端java工程师经验之谈,工作7年,mysql使用心得
mysql 工作7年,mysql使用心得 mysql1.创建变量2.创建存储过程2.1:WHILE循环2.2:repeat循环2.3:loop循环2.4:存储过程,游标2.5:存储过程,有输入参数和输出参数 3.三种注释写法4.case …...
综合评价 | 基于随机变异系数-TOPSIS组合法的综合评价模型(Matlab)
基于随机变异系数-TOPSIS组合法的综合评价模型 代码获取私信回复:综合评价 | 基于随机变异系数-TOPSIS组合法的综合评价模型(Matlab) 一、引言 1.1、研究背景与意义 在现代社会,随着信息量的不断增加和数据复杂性的提升&#…...
Visual Studio Code中文出现黄色框子的解决办法
Visual Studio Code中文出现黄色框子的解决办法 一、vsCode中文出现黄色框子-如图二、解决办法 一、vsCode中文出现黄色框子-如图 二、解决办法 点击 “文件”点击 “首选项”点击 “设置” 搜索框直接搜索unicode选择“文本编辑器”,往下滑动,找到“Un…...
手写一个C++ Android Binder服务及源码分析
手写一个C Android Binder服务及源码分析 前言一、 基于C语言编写Android Binder跨进程通信Demo总结及改进二、C语言编写自己的Binder服务Demo1. binder服务demo功能介绍2. binder服务demo代码结构图3. binder服务demo代码实现3.1 IHelloService.h代码实现3.2 BnHelloService.c…...
【AIGC】在VSCode中集成 DeepSeek(OPEN AI同理)
在 Visual Studio Code (VSCode) 中集成 AI 编程能力,可以通过安装和配置特定插件来实现。以下是如何通过 Continue 和 Cline 插件集成 DeepSeek: 一、集成 DeepSeek 获取 DeepSeek API 密钥:访问 DeepSeek 官方网站,注册并获取 …...
使用 Three.js 实现热力渐变效果
大家好!我是 [数擎 AI],一位热爱探索新技术的前端开发者,在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情,欢迎关注我的文章,我们一起成长、进步! 开发领域:前端开发 | A…...
Vue事件处理 - 绑定事件
Vue 渐进式JavaScript 框架 基于Vue2的学习笔记 - Vue事件处理 - 绑定事件及事件处理 目录 事件处理 绑定方式 函数表达式 绑定函数名 输入框绑定事件 拿到输入框的值 传值加事件源 事件第三种写法 总结 事件处理 绑定方式 函数表达式 在按钮上使用函数表达式绑定事…...
DVWA靶场通关——SQL Injection篇
一,Low难度下unionget字符串select****注入 1,首先手工注入判断是否存在SQL注入漏洞,输入1 这是正常回显的结果,再键入1’ You have an error in your SQL syntax; check the manual that corresponds to your MySQL server ver…...
DeepSeek 助力 Vue 开发:打造丝滑的步骤条
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
今日学习总结
1.完成了P2242公路维修问题 2.完成了P10605下头论文 1.P2242 思考:建立单向链表,使用qsort降序排序。 #include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<string.h> int n,m; int a[15005],b[15005],ans;…...
Transformer 的辉煌与大模型方向确立,点燃AGI之火把
GPT3,指明大模型发展方向,点燃大模型软件行业繁荣之火,目前大模型有100万个。 DeepSeek-V3,指明下一个阶段大模型发张方向,破壁: 资金壁垒:训练成本降低,适配丰富硬件,总…...
DeepSeek-Coder系列模型:智能编程助手的未来
文章目录 一、模型架构与核心功能1. 模型架构2. 核心功能 二、多语言支持与代码生成1. Python代码生成2. Java代码生成3. C代码生成4. JavaScript代码生成 三、仓库级代码理解1. 代码结构分析2. 上下文理解 四、FIM填充技术1. 函数自动填充2. 代码补全 五、应用场景1. 代码补全…...
微信小程序longpress以及touchend的bug,touchend不触发,touchend不执行
核心原因:bind:touchend里面不能放wx:if 举例: <view bind:longpress"longpressBtn" bind:touchend"touchendBtn"><view wx:if"{{isRecording}}" >松开发送</view><view wx:else"…...
多租户架构设计与实现:基于 PostgreSQL 和 Node.js
多租户架构设计与实现:基于 PostgreSQL 和 Node.js 引言 多租户架构(Multi-tenancy)是现代 SaaS(Software as a Service)应用的核心设计模式之一。它允许多个租户共享同一套应用实例,同时确保数据隔离和安全性。本文将详细介绍多租户架构的设计方案,并基于 PostgreSQL…...
四、OSG学习笔记-基础图元
前一章节: 三、OSG学习笔记-应用基础-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/145514021 代码:CuiQingCheng/OsgStudy - Gitee.com 一、绘制盒子模型 下面一个简单的 demo #include<windows.h> #include<osg/Node&…...
windows平台本地部署DeepSeek大模型+Open WebUI网页界面(可以离线使用)
环境准备: 确定部署方案请参考:DeepSeek-R1系列(1.5b/7b/8b/32b/70b/761b)大模型部署需要什么硬件条件-CSDN博客 根据本人电脑配置:windows11 + i9-13900HX+RTX4060+DDR5 5600 32G内存 确定部署方案:DeepSeek-R1:7b + Ollama + Open WebUI 1. 安装 Ollama Ollama 是一…...
功能架构元模型
功能架构的元模型是对功能架构进行描述和建模的基础框架,它有助于统一不同团队对系统的理解,并为系统的设计和开发提供一致的标准和规范。虽然具体的元模型可能因不同的应用领域和特定需求而有所差异,但一般来说,功能架构的元模型可以涵盖以下几个方面: 组件/模块元模型:…...
云计算——AWS Solutions Architect – Associate(saa)4.安全组和NACL
安全组一充当虚拟防火墙对于关联实例,在实例级别控制入站和出站流量。 网络访问控制列表(NACL)一充当防火墙关联子网,在子网级别控制入站和出站流量。 在专有网络中,安全组和网络ACL(NACL)一起帮助构建分层网络防御。 安全组在实例级别操作…...
Fiddler Classic(HTTP流量代理+半汉化)
目录 一、关于Fiddler (一) Fiddler Classic (二) Fiddler Everywhere (三) Fiddler Everywhere Reporter (四) FiddlerCore (五) 总结 二、 软件安全性 1. 软件安装包 2. 软件汉化dll 三、安装与半汉化 1. 正常打开安装包点击下一步安装即可,安装路径自…...
【hive】记一次hiveserver内存溢出排查,线程池未正确关闭导致
一、使用 MemoryAnalyzer软件打开hprof文件 很大有30G,win内存24GB,不用担心可以打开,ma软件能够生成索引文件,逐块分析内存,如下图。 大约需要4小时。 overview中开不到具体信息。 二、使用Leak Suspects功能继续…...
MySQL的字段类型
MySQL 字段类型可以简单分为三大类 数值类型:整型(TINYINT、SMALLINT、MEDIUMINT、INT 和 BIGINT)、浮点型(FLOAT 和 DOUBLE)、定点型(DECIMAL)字符串类型:CHAR、VARCHAR、TINYTEXT…...