golang单机锁实现
1、锁的概念引入
首先,为什么需要锁?
在并发编程中,多个线程或进程可能同时访问和修改同一个共享资源(例如变量、数据结构、文件)等,若不引入合适的同步机制,会引发以下问题:
-
数据竞争:多个线程同时修改一个资源,最终的结果跟线程的执行顺序有关,结果是不可预测的。
-
数据不一致:一个线程在修改资源,而另一个线程读取了未修改完的数据,从而导致读取了错误的数据。
-
资源竞争:多线程竞争同一个资源,浪费系统的性能。
因此,我们需要一把锁,来保证同一时间只有一个人能写数据,确保共享资源在并发访问下的正确性和一致性。
在这里,引入两种常见的并发控制处理机制,即乐观锁与悲观锁:
-
乐观锁:假定在并发操作中,资源的抢占并不是很激烈,数据被修改的可能性不是很大,那这时候就不需要对共享资源区进行加锁再操作,而是先修改了数据,最终来判断数据有没有被修改,没有被修改则提交修改指,否则重试。
-
悲观锁:与乐观锁相反,它假设场景的资源竞争激烈,对共享资源区的访问必须要求持有锁。
针对不同的场景需要采取因地制宜的策略,比较乐观锁与悲观所,它们的优缺点显而易见:
2、Sync.Mutex
Go对单机锁的实现,考虑了实际环境中协程对资源竞争程度的变化,制定了一套锁升级的过程。具体方案如下:
-
首先采取乐观的态度,Goroutine会保持自旋态,通过CAS操作尝试获取锁。
-
当多次获取失败,将会由乐观态度转入悲观态度,判定当前并发资源竞争程度剧烈,进入阻塞态等待被唤醒。
从乐观转向悲观的判定规则如下,满足其中之一即发生转变:
-
Goroutine自旋尝试次数超过4次
-
当前P的执行队列中存在等待被执行的G(避免自旋影响GMP调度性能)
-
CPU是单核的(其他Goroutine执行不了,自旋无意义)
除此之外,为了防止被阻塞的协程等待过长时间也没有获取到锁,导致用户的整体体验下降,引入了饥饿的概念:
-
饥饿态:若Goroutine被阻塞等待的时间>1ms,则这个协程被视为处于饥饿状态
-
饥饿模式:表示当前锁是否处于特定的模式,在该模式下,锁的交接是公平的,按顺序交给等待最久的协程。
饥饿模式与正常模式的转变规则如下:
-
普通模式->饥饿模式:存在阻塞的协程,阻塞时间超过1ms
-
饥饿模式->普通模式:阻塞队列清空,亦或者获得锁的协程的等待时间小于1ms,则恢复
接下来步入源码,观看具体的实现。
2.1、数据结构
位于包sync/mutex.go
中,对锁的定义如下:
type Mutex struct {state int32sema uint32
}
-
state
:标识目前锁的状态信息,包括了是否处于饥饿模式、是否存在唤醒的阻塞协程、是否上锁、以及处于等待锁的协程个数有多少。 -
seme
:用于阻塞和唤醒协程的信号量。
将state
看作一个二进制字符串,它存储信息的规则如下:
-
第一位标识是否处于上锁,0表示否,1表示上锁(mutexLocked)
-
第二位标识是否存在唤醒的阻塞协程(mutexWoken)
-
第三位标识是否处于饥饿模式(mutexStarving)
-
从第四位开始,记录了处于阻塞态的协程个数
const (mutexLocked = 1 << iota // mutex is lockedmutexWokenmutexStarvingmutexWaiterShift = iotastarvationThresholdNs = 1e6 //饥饿阈值
)
2.2、获取锁Lock()
func (m *Mutex) Lock() {if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {return}m.lockSlow()
}
尝试直接通过CAS操作直接获取锁,若成功则返回,否则说明锁被获取,步入LockSlow
。
2.3、LockSlow()
源码较长,进行拆分讲解:
var waitStartTime int64starving := falseawoke := falseiter := 0old := m.state
(1)定义了基本的常量,含义如下:
-
waitStartTime
:记录当前协程等待的时间,只有被阻塞才会使用 -
awoke
:标识当前协程是否被Unlock唤醒 -
iter
:记录当前协程自旋尝试次数 -
old
:记录旧的锁的状态信息
for {//处于上锁状态,并且不处于饥饿状态中,并且当前的协程允许继续自旋下去if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {awoke = true}runtime_doSpin()iter++old = m.statecontinue}//...}
(2)进入尝试获取锁的循环中,两个if表示:
-
若锁处于上锁状态,并且不处于饥饿状态中,并且当前的协程允许继续自旋下去(非单核CPU、自旋次数<=4、调度器P的本地队列不存在等待执行的G),则步入:若当前协程并非从等待队列唤醒、并且不存在被唤醒的等待协程、并且存在位于阻塞的协程、则尝试设置mutexWoken标识为1,若成功:标识当前的协程为被唤醒的协程。(虽然并非实际从阻塞中唤醒)告诉P,当前的协程处于自旋态更新
iter
计数器,与old
记录的当前锁的状态信息,进行下一次重试循环
这里存在的唯一疑惑为,为什么要将awoke标识为true?
首先,因为当前锁并非处于饥饿模式,因此当前的抢占锁的模式是不公平的,若当前锁的阻塞队列还没有被唤醒的协程,那就要求不要唤醒了,尝试让当前正在尝试的协程获取到锁,避免唤醒协程进行资源竞争。
for {//...new := oldif old&mutexStarving == 0 {new |= mutexLocked}if old&(mutexLocked|mutexStarving) != 0 {new += 1 << mutexWaiterShift}if starving && old&mutexLocked != 0 {new |= mutexStarving}if awoke {new &^= mutexWoken}//...
}
(3)进行状态更新:
当协程从步骤2走出来时,只能说明它位于以下两个状态之一:
-
旋不动了,或者锁进入饥饿模式了,锁要让给别人了,总之是获取不到锁了(悲观)。
-
锁被释放了。
不论如何,都需要进行一些状态的更新,为接下来的打算做准备。
用new存储一个锁即将要进入的新状态信息,更新规则:
-
若锁不处于饥饿模式:说明锁可能被释放了,也可能是自旋次数过多,不管接下来是否能拿到锁,锁都会被某一个协程获取,因此置
mutexLocked
为1。 -
若锁可能处于饥饿状态,或者锁没有被释放:那说明自己是抢不到锁了,即将进入阻塞态,阻塞协程计数器+1。
-
若当前的协程是被唤醒的,并且已经处在饥饿态中而且锁仍然锁着:锁进入绝对公平的饥饿模式。
-
若当前协程是被唤醒的:清除
mutexWoken
标识位,因为接下来可能需要有协程被唤醒(饥饿模式)。
虽然更新的有点多,但是可以归纳为:
-
若锁释放了,那就标识一下接下来锁要被获取即可。
-
若锁没有释放,并给当前协程等待了很久,那锁就进入饥饿状态,接下来需要有阻塞协程被唤醒。
(4)尝试更新信息:
if atomic.CompareAndSwapInt32(&m.state, old, new) {//...} else {old = m.state}
接下来尝试将new更新进state,若更新失败,说明当前有另一个协程介入了,为了防止数据的一致性丢失,要全部重来一次。
(5)状态更新成功,具体判断是要沉睡还是获取锁成功:
步入步骤4的if主支中,此时有两个状态:
if atomic.CompareAndSwapInt32(&m.state, old, new) {if old&(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS}//...} else {//...}
因为当前状态,可能是锁释放了,检查锁更新前是否已经被释放了并且不是饥饿模式,若是那说明获取锁成功了,函数结束了。
if atomic.CompareAndSwapInt32(&m.state, old, new) {if old&(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS}// If we were already waiting before, queue at the front of the queue.queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime()}runtime_SemacquireMutex(&m.sema, queueLifo, 2)//....} else {//...}
否则,说明当前协程要进入阻塞态了,记录一下开始阻塞的时间,用于醒来是判断是否饥饿。然后进入阻塞沉睡中。
(6)若步骤5进入阻塞,则被唤醒后:
if atomic.CompareAndSwapInt32(&m.state, old, new) {if old&(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS}// If we were already waiting before, queue at the front of the queue.queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime()}runtime_SemacquireMutex(&m.sema, queueLifo, 2)//唤醒starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNsold = m.state//若锁处于饥饿模式if old&mutexStarving != 0 {//锁的异常处理if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}//将要更新的信号量delta := int32(mutexLocked - 1<<mutexWaiterShift)if !starving || old>>mutexWaiterShift == 1 {delta -= mutexStarving}atomic.AddInt32(&m.state, delta)break}awoke = trueiter = 0//....} else {//...}
从阻塞中唤醒,首先计算一些协程的阻塞时间,以及当前的最新锁状态。
若锁处于饥饿模式:那么当前协程将直接获取锁,当前协程是因为饥饿模式被唤醒的,不存在其他协程抢占锁。于是更新信号量,将记录阻塞协程数-1,将锁的上锁态置1。若当前从饥饿模式唤醒的协程,等待时间已经不到1ms了或者是最后一个等待的协程,那么将将锁从饥饿模式转化为正常模式。至此,获取成功,退出函数。
否则,只是普通的随机唤醒,于是开始尝试进行抢占,回到步骤1。
2.4、释放锁Unlock()
func (m *Mutex) Unlock() {//直接释放锁new := atomic.AddInt32(&m.state, -mutexLocked)if new != 0 {m.unlockSlow(new)}
}
通过原子操作,直接将锁的mutexLocked
标识置为0。若置0后,锁的状态不为0,那就说明存在需要获取锁的协程,步入unlockSlow
。
2.5、unlockSlow()
func (m *Mutex) unlockSlow(new int32) {if (new+mutexLocked)&mutexLocked == 0 {fatal("sync: unlock of unlocked mutex")}if new&mutexStarving == 0 {old := newfor {if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {return}new = (old - 1<<mutexWaiterShift) | mutexWokenif atomic.CompareAndSwapInt32(&m.state, old, new) {runtime_Semrelease(&m.sema, false, 2)return}old = m.state}} else {runtime_Semrelease(&m.sema, true, 2)}
}
(1)首先进行了异常状态处理,若释放了一个已经释放了到锁,那么直接fatal,程序终止。
if (new+mutexLocked)&mutexLocked == 0 {fatal("sync: unlock of unlocked mutex")}
(2)若锁不处于饥饿状态:
-
若此时的等待协程数量为0,或者锁被上锁了、含有被唤醒的协程、锁处于饥饿模式:都说明有新的协程介入了流程,已经完成了交接,可以直接退出
-
唤醒一个处于阻塞态的协程。
否则,处于饥饿状态,唤醒等待最久的协程。
3、Sync.RWMutex
对于共享资源区的操作,可以划分为读与写两大类。假设在一个场景中,对共享资源区继续读的操作远大于写的操作,如果每个协程的读操作都需要获取互斥锁,这带来的性能损耗是非常大的。
RWMutex
是一个可以运用在读操作>写操作中的提高性能的锁,可以将它视为由一个读锁与一个写锁构成。其运作规则具体如下:
-
读锁允许多个读协程同时读取共享资源区,若有协程需要修改资源区的数据,那么它需要被阻塞。
-
写锁具有严格的排他性,当共享资源区被上了写锁时,任何其他goroutine都不得访问。
可见在最坏的情况下,所有的协程都是需要写操作时,读写锁会退化成普通的Mutex。
3.1、数据结构
type RWMutex struct {w Mutex // held if there are pending writerswriterSem uint32 // semaphore for writers to wait for completing readersreaderSem uint32 // semaphore for readers to wait for completing writersreaderCount atomic.Int32 // number of pending readersreaderWait atomic.Int32 // number of departing readers
}
const rwmutexMaxReaders = 1 << 30 //最大的读协程数量
-
w
:一个互斥的写锁 -
writerSem
:关联被阻塞的写协程的信号量 -
readerSem
:关联被阻塞的读协程的信号量 -
readerCount
:正常情况下,记录正在读取的协程数量;但若当前是写协程正在持有锁,那么实际记录读协程的数量为readerCount - rwmutexMaxReader
-
readerWait
:记录释放下一个写协程,还需要等待读协程完成的数量
3.2、读锁流程RLock()
func (rw *RWMutex) RLock() {if rw.readerCount.Add(1) < 0 {// A writer is pending, wait for it.runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)}
}
对readerCount
+1,表示新加入一个读协程。若结果<0,说明当前锁正在被写协程占据,令当前的读协程阻塞。
3.3、读释放锁流程RUnlock()
func (rw *RWMutex) RUnlock() {if r := rw.readerCount.Add(-1); r < 0 {// Outlined slow-path to allow the fast-path to be inlinedrw.rUnlockSlow(r)}
}
对readerCount
-1,表示减少一个读协程。若结果<0,说明当前锁正在被写协程占据,步入runlockslow。
3.4、rUnlockSlow()
func (rw *RWMutex) rUnlockSlow(r int32) {if r+1 == 0 || r+1 == -rwmutexMaxReaders {race.Enable()fatal("sync: RUnlock of unlocked RWMutex")}if rw.readerWait.Add(-1) == 0 {// The last reader unblocks the writer.runtime_Semrelease(&rw.writerSem, false, 1)}
}
首先进行错误处理,若发现当前协程为占用过读锁,或者读流程的协程数量上限,系统出现异常,fatal。
否则,对readerWait
-1,若结果为0,说明当前协程是最后一个介入读锁流程的协程,此时需要释放一个写锁。
3.5、写锁流程Lock()
func (rw *RWMutex) Lock() {// First, resolve competition with other writers.rw.w.Lock()// Announce to readers there is a pending writer.r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders// Wait for active readers.if r != 0 && rw.readerWait.Add(r) != 0 {runtime_SemacquireRWMutex(&rw.writerSem, false, 0)}
}
首先尝试获取写锁,若获取成功,需要将readerCount
-最大读协程数,表示现在锁被读协程占据。
r表示处于读流程的协程数量,若r不为0,那么就将readerWait
加上r,等这些读协程都读取完毕,再去写。将这个写协程阻塞。(读写锁并非读、写公平,读协程优先。)
3.6、写释放锁流程Unlock()
func (rw *RWMutex) Unlock() {// Announce to readers there is no active writer.r := rw.readerCount.Add(rwmutexMaxReaders)if r >= rwmutexMaxReaders {race.Enable()fatal("sync: Unlock of unlocked RWMutex")}// Unblock blocked readers, if any.for i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem, false, 0)}// Allow other writers to proceed.rw.w.Unlock()
}
重新将readerCount
置为正常指,表示释放了写锁。若读协程超过最大上限,则异常。
然后唤醒所有阻塞的读协程。(读协程优先)
解锁。
文章转载自:MelonTe
原文链接:golang单机锁实现 - MelonTe - 博客园
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
相关文章:
golang单机锁实现
1、锁的概念引入 首先,为什么需要锁? 在并发编程中,多个线程或进程可能同时访问和修改同一个共享资源(例如变量、数据结构、文件)等,若不引入合适的同步机制,会引发以下问题: 数据竞…...
面试中JVM常被问到的问题以及对应的答案
在面试中,关于JVM常被问到的问题以及对应的答案可能包括: 什么是JVM?它的作用是什么? 答:JVM是Java虚拟机的缩写,是Java程序运行的环境。它负责将Java源代码编译成字节码并运行在不同平台上。 请解释一下J…...
算法——广度优先搜索——跨步迷宫
原题链接 思路:找出最短路径,然后判断是否存在连续三个点是横纵坐标相等的,如果有就步数减1 但是有两个样例过不了 错误原因:在错误的测试案例中,最短路径可能有多条,而我刚好选了一条比较曲折的&#x…...
Python pyqt+flask做一个简单实用的自动排班系统
这是一个基于Flask和PyQt的排班系统,可以将Web界面嵌入到桌面应用程序中。 系统界面: 功能特点: - 读取员工信息和现有排班表 - 自动生成排班表 - 美观的Web界面 - 独立的桌面应用程序 整体架构: 系统采用前后端分离的架构…...
链表操作:分区与回文判断
目录 链表分区(Partition) 功能概述 代码实现 要点与难点 注意事项 链表回文判断(PalindromeList) 功能概述 代码实现 要点与难点 注意事项 总结 在链表相关的算法问题中,理解链表的基本结构和操作至关重要…...
基于大模型的腮腺多形性腺瘤全周期诊疗方案研究报告
目录 一、引言 1.1 研究背景与目的 1.2 研究现状与趋势 二、大模型预测原理与方法 2.1 大模型概述 2.2 数据收集与预处理 2.3 模型训练与优化 三、术前预测与评估 3.1 肿瘤特征预测 3.2 风险评估 3.3 案例分析 四、术中方案制定与实施 4.1 手术方案选择 4.2 面神…...
Vue3 界面设计插件 microi-pageengine 入门教程一
系列文章目录 一、Vue3空项目快速集成 microi-pageengine 插件 文章目录 系列文章目录一、前言二、排版布局2.1 功能导航区2.2 组件容器区2.3 属性面板区 三、数据来源配置3.1 json数据源3.2 html数据源 四、事件穿透五、数据保存持久化六、总结 一、前言 上一篇文章介绍了 v…...
如何选择合适的SSL服务器证书
在数字化时代,网络安全已成为企业不可忽视的重要环节。SSL(Secure Sockets Layer,安全套接层)服务器证书作为保障网站数据传输安全的关键工具,其选择和使用至关重要。 一、SSL证书类型:基础与进阶 SSL证书…...
STC89C52单片机学习——第26节: [11-2]蜂鸣器播放音乐
写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做! 本文写于:2025.03.19 51单片机学习——第26节: [11-2]蜂鸣器播放音乐 前言开发板说明引用解答和科普一…...
ABC396题解
A 算法标签: 模拟 问题陈述 给你一个长度为 N N N 的整数序列: A ( A 1 , A 2 , … , A N ) A (A_1, A_2, \ldots, A_N) A(A1,A2,…,AN)。 请判断在 A A A 中是否有相同元素连续出现三次或三次以上的位置。 更正式地说,请判断是否存在一个整…...
如何用Python和Selenium实现表单的自动填充与提交?
在今天的数字化时代,自动化工具可以极大地提高工作效率。很多人可能会觉得填表单是个繁琐的任务,不过你知道吗?用Python和Selenium可以轻松解决这一问题!本文将带你走进如何利用这两个强大的工具,实现表单的自动填充和…...
使用`plot_heatmap`绘制热力图时
在Python中,使用plot_heatmap绘制热力图时,颜色图例(colorbar)的定制化设置是关键步骤。以下是实现方法及优化建议: 一、基础图例绘制 自动生成颜色条 使用seaborn.heatmap()时,默认会生成颜色条࿰…...
极简桌面待办清单软件,❌不会增加工作量
工作邮件、会议安排、生活琐事……事情多到根本记不住,又怕错过重要事项,焦虑感油然而生。相信这是很多职场朋友遇到的问题,也急需一款好用的极简桌面待办清单软件来辅助我们。 今天我要给大家推荐一款拯救我们于水火的神器——好用便签&…...
QT学习笔记4
一、音视频播放(Qt Multimedia) 1. 多媒体框架架构 核心类关系 : mermaid #mermaid-svg-mwHLYcpaJDU14uFM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-mwHLYcpaJDU14uFM .e…...
软考 中级软件设计师 考点知识点笔记总结 day06
文章目录 6、树和二叉树6.1、树的基本概念6.2、二叉树的基本概念6.3、二叉树的遍历6.4、查找二叉树(二叉排序树)BST6.5、构造霍夫曼树6.6、线索二叉树6.7、 平衡二叉树 7、 图7.1 、 存储结构 - 邻接矩阵7.2 、 存储结构 - 邻接表7.3、图的遍历7.4、拓扑…...
爬虫基础之爬取猫眼Top100 可视化
网站: TOP100榜 - 猫眼电影 - 一网打尽好电影 本次案例所需用到的模块 requests (发送HTTP请求) pandas(数据处理和分析 保存数据) parsel(解析HTML数据) pyecharts(数据可视化图表) pymysql(连接和操作MySQL数据库) lxml(数据解析模块) 确定爬取的内容: 电影名称 电影主演…...
微信小程序面试内容整理-如何使用wx.request()进行网络请求
wx.request() 是微信小程序中用于发送网络请求的 API,类似于浏览器中的 fetch 或 XMLHttpRequest。通过 wx.request(),开发者可以向服务器发送 HTTP 请求,获取数据或者提交数据。 基本用法 wx.request() 通过提供一个配置对象来进行配置,配置对象中包括请求的 URL、请求方法…...
力扣100二刷——图论、回溯
第二次刷题不在idea写代码,而是直接在leetcode网站上写,“逼”自己掌握常用的函数。 标志掌握程度解释办法⭐Fully 完全掌握看到题目就有思路,编程也很流利⭐⭐Basically 基本掌握需要稍作思考,或者看到提示方法后能解答⭐⭐⭐Sl…...
该错误是由于`KuhnMunkres`类未定义`history`属性导致的
该错误是由于KuhnMunkres类未定义history属性导致的。以下是具体分析及解决方案: 错误原因分析 属性缺失 代码中试图访问km.history,但KuhnMunkres类未在初始化或算法执行过程中定义该属性,因此触发AttributeError。动画实现逻辑不完整 用户…...
DAPO:一个开源的大规模大型语言模型LLM强化学习系统
推断扩展赋予了大型语言模型前所未有的推理能力,强化学习作为激发复杂推理的核心技术,清华大学联合字节提出了解耦片段与动态采样策略优化(DAPO)算法,并全面开源了一个最先进的大规模强化学习系统,该系统使用Qwen2.5-32B基础模型在AIME 2024上取得了50分的高分。还开源了…...
数据结构中的引用管理对象体系
数据结构中的引用管理对象体系 (注:似复刻变量即实例对象) 引用管理对象的,有引用就能管理到它所指向的对象,我们拿引用最终的目的就是管理那些我们需要管理的最终直接对象,引用也是对象,同时…...
【自学笔记】智能合约基础知识点总览-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 智能合约基础知识点总览目录1. 智能合约简介2. 以太坊与Solidity示例代码:Hello World智能合约 3. Solidity基础语法示例代码:简单的计数器合…...
Linux的Shell编程
一、什么是Shell 1、为什么要学习Shell Linux运维工程师在进行服务器集群管理时,需要编写Shell程序来进行服务器管理。 对于JavaEE和Python程序员来说,工作的需要。Boss会要求你编写一些Shell脚本进行程序或者是服务器的维护,比如编写一个…...
【八股文】volatile关键字的底层原理是什么
volatile只能保证可见性和有序性 volatile如何保证可见性 当对volatile变量进行写操作的时候,JVM会向处理器发送一条lock前缀的命令,将这个缓存中的变量会写到系统内存中。 所以,如果一个变量被volatile所修饰,每次数据变化之后…...
一种基于大规模语言模型LLM的数据分析洞察生成方法
从复杂数据库中提取洞察对数据驱动决策至关重要,但传统手动生成洞察的方式耗时耗力,现有自动化数据分析方法生成的洞察不如人工生成的有洞察力,且存在适用场景受限等问题。下文将介绍一种新的方法,通过生成高层次问题和子问题,并使用SQL查询和LLM总结生成多表数据库中的见…...
三大供应链管理模式——解决方案与实操案例详解
库存压到喘不过气,紧急订单却总赶不上交付?这是许多企业的真实困境——推式供应链盲目备货导致积压,拉式供应链又因响应慢而丢单。供应链管理,本质上是在“计划”与“变化”之间把握平衡。上篇文章,我们系统梳理了供应…...
【C++】STL库面试常问点
STL库 什么是STL库 C标准模板库(Standard Template Libiary)基于泛型编程(模板),实现常见的数据结构和算法,提升代码的复用性和效率。 STL库有哪些组件 STL库由以下组件构成: ● 容器…...
集成学习之随机森林
目录 一、集成学习的含义 二、集成学习的代表 三、集成学习的应用 1、分类问题集成。(基学习器是分类模型) 2、回归问题集成。(基学习器是回归模型) 3、特征选取集成。 四、Bagging之随机森林 1、随机森林是有多个决策树&a…...
【SpringBatch】03步骤对象 (Step)控制与执行流程设计
目录标题 六、步骤对象 Step6.1 步骤介绍6.2 简单Tasklet6.3 chunk居于块Tasklet**ChunkTasklet 泛型** 6.4 步骤监听器6.5 多步骤执行6.6 步骤控制6.6.1 条件分支控制-使用默认返回状态6.6.2 条件分支控制-使用自定义状态值 6.7 步骤状态6.8 流式步骤 六、步骤对象 Step 前面…...
走进Java:String字符串的基本使用
❀❀❀ 大佬求个关注吧~祝您开心每一天 ❀❀❀ 目录 一、什么是String 二、如何定义一个String 1. 用双引号定义 2. 通过构造函数定义 三、String中的一些常用方法 1 字符串比较 1.1 字符串使用 1.2 字符串使用equals() 1.3 使用 equalsIgnoreCase() 1.4 cpmpareTo…...
AI如何变革亚马逊广告投放?DeepBI的智能优化解析
在亚马逊平台上,广告投放的方式正经历着从人工精细化运营到AI自动化优化的深刻变革。传统的广告投放依赖人工操作,涉及海量数据分析、手动调价、竞价策略优化等环节,既耗时又易受人为因素影响。而AI驱动的智能投放系统,如DeepBI&a…...
小程序电子画册制作,用户体验为王!
家人们,现在小程序电子画册超火,不管是展示产品还是分享故事都超实用。但在小程序电子画册制作过程中,用户体验真的得好好考量!今天就和大家唠唠这其中的门道。 1、加载速度:快才是王道 打开小程序的瞬间,…...
【商城实战(49)】解锁小程序端适配与优化,让商城飞起来
【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配…...
英伟达GTC 2025大会产品全景剖析与未来路线深度洞察分析
【完整版】3月19日,黄仁勋Nvidia GTC 2025 主题演讲|英伟达 英伟达GTC 2025大会产品全景剖析与未来路线深度洞察分析 一、引言 1.1 分析内容 本研究主要采用了文献研究法、数据分析以及专家观点引用相结合的方法。在文献研究方面,广泛收集了…...
《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用
《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用 《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用内核对象内核对象的定义内核对象归操作系统所有 基于 Windows 的线程创建进程与线程的关系Windows 中线程的创建方…...
线性规划的基本解、基本可行解和可行解
在线性规划中,基本解、基本可行解和可行解是非常重要的概念,特别是在使用单纯形法求解时。下面详细解释这些概念,并说明如何计算它们。 1. 线性规划问题的标准形式 线性规划的标准形式为: 其中: A 是 mn 的矩阵&…...
【AVRCP】服务发现互操作性:CT 与 TG 的 SDP 协议契约解析
目录 一、服务发现的核心目标:能力画像对齐 二、控制器(CT)服务记录:控制能力的声明 2.1 必选字段:角色与协议的刚性契约 2.1.1 服务类标识(Service Class ID List) 2.1.2 协议描述列表&am…...
[从零开始学习JAVA] Stream流
前言: 本文我们将学习Stream流,他就像流水线一样,可以对我们要处理的对象进行逐步处理,最终达到我们想要的效果,是JAVA中的一大好帮手,值得我们了解和掌握。(通常和lambda 匿名内部类 方法引用相…...
K8S学习之基础三十八:Kube-static-metrics监控
Kube-static-metrics监控 kube-static-metrics组件可以通过监听apiserver生成有关资源对象的状态指标,比如Node、Pod,需要注意的是kube-state-metrics只是简单的提供一个metrics数据,并不会存储这些指标数据,所以可以使用Prom…...
JAVA-多线程join()等待一个线程
引言:更多线程的认识可以看一篇博客: JAVA-Thread类实现多线程-CSDN博客 一、join()的作用 我们知道线程是随机调度执行的,但是有时候我们需要另一个任务完成了,我们才能继续,这个时候我们就可以使用join去等待线程结束…...
HashMap 常用方法
HashMap 常用方法 方法作用示例put(K key, V value)添加键值对map.put("apple", 10);get(Object key)获取指定键的值map.get("apple"); → 10remove(Object key)删除指定键的键值对map.remove("orange");containsKey(Object key)检查是否包含指…...
LogicFlow介绍
LogicFlow介绍 LogicFlow是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。LogicFlow支持前端自定义开发各种逻辑编排场景,如流程图、ER图、BPMN流程等。在工作审批流配置、机器人逻辑编排、无代码平…...
Docker搭建MySQL主从服务器
一、在主机上创建MySQL配置文件——my.cnf master服务器配置文件路径:/data/docker/containers/mysql-cluster-master/conf.d/my.cnf slave服务器配置文件路径: /data/docker/containers/mysql-cluster-master/conf.d/my.cnf master服务配置文件内容 …...
计算机二级web易错点(4)-选择题
选项 A:<input type"radio"> 用于创建单选按钮,同一组单选按钮中只能选择一个选项,所以该选项不符合要求。选项 B:HTML 中没有 type"check" 这种类型,是错误的写法,不能产生复选…...
3.19学习总结
学习了Java中的面向对象的知识点 完成一道算法题,找树左下角的值,错误的以为左下角只能是最底层的左节点,但指的是最底层最左边的节点...
Swagger-告别手写文档
文章目录 1. 引言2. Swagger是什么?3. SpringBoot2.7.3集成Swagger4. 常见注解 1. 引言 在RESTful API开发中,维护准确、易读的接口文档是团队协作的核心挑战,通常接口文档分为离线的和实时的。离线的接口文档工具有 YAPI等,其中…...
LeetCode-回文数
原题链接:9. 回文数 - 力扣(LeetCode) 首先我会想到的是,将这个数字转成字符串,然后通过前后指针判断是否相等,最终返回结果是否为回文数,时间复杂度:O(n),空间复杂度&am…...
数据结构之链表(双链表)
目录 一、双向带头循环链表 概念 二、哨兵位的头节点 优点: 头节点的初始化 三、带头双向链表的实现 1.双链表的销毁 2.双链表的打印 3.双链表的尾插和头插 尾插: 头插: 4.双链表的尾删和头删 尾删: 头删: …...
硬件基础(5):(2)二极管分类
文章目录 📌 二极管的分类与详细介绍1. **整流二极管(Rectifier Diode)**特点:选型依据:补充说明: 2. **快恢复二极管(Fast Recovery Diode)**特点:选型依据:…...
MQTT 和 Modbus 的优缺点对比
MQTT和Modbus协议是物联网(IoT)躲不开的两种协议,市面上覆盖了百分之98的产品。 MQTT由IBM在1999年发布。2014年,MQTT成为OASIS(结构化信息标准促进组织)的标准,后来被ISO/IEC 20922正式采纳&a…...