【Go 基础】并发相关
并发相关
CAS
CAS算法(Compare And Swap),是原⼦操作的⼀种,,CAS 算法是⼀种有名的⽆锁算法。⽆锁编程,即不使⽤锁的情况下实现多线程之间的变量同步。可⽤于在多线程编程中实现不被打断的数据交换操作,从⽽避免多线程同时改写某⼀数据时由于执⾏顺序不确定性以及中断的不可预知性产⽣的数据不⼀致问题。
Go中的CAS操作是借⽤了CPU提供的原⼦性指令来实现。 CAS操作修改共享变量时候不需要对共享变量加锁,⽽是通过类似乐观锁的⽅式进⾏检查,本质还是不断的占⽤CPU 资源换取加锁带来的开销(⽐如上下⽂切换销)。
package mainimport ("fmt""sync""sync/atomic"
)var (counter int32 //计数器wg sync.WaitGroup //信号量
)func main() {threadNum := 5wg.Add(threadNum)for i := 0; i < threadNum; i++ {go incCounter(i)}wg.Wait()
}
func incCounter(index int) {defer wg.Done()spinNum := 0for {// 原⼦操作old := counterok := atomic.CompareAndSwapInt32(&counter, old, old+1)if ok {break} else {spinNum++}}fmt.Printf("thread,%d,spinnum,%d\n", index, spinNum)
}
当主函数 main ⾸先创建了5个信号量,然后开启五个线程执⾏ incCounter ⽅法,incCounter 内部执⾏, 使⽤ CAS 操作递增 counter 的值,atomic.CompareAndSwapInt32 具有三个参数,第⼀个是变量的地址,第⼆个是变量当前值,第三个是要修改变量为多少,该函数如果发现传递的 old 值等于当前变量的值,则使⽤第三个变量替换变量的值并返回 true,否则返回 false。
这⾥之所以使⽤⽆限循环是因为在⾼并发下每个线程执⾏ CAS 并不是每次都成功,失败了的线程需要重写获取变量当前的值,然后重新执⾏ CAS 操作。
go 中 CAS 操作可以有效的减少使⽤锁所带来的开销,但是需要注意在⾼并发下这是使⽤ cpu 资源做交换的。
什么是并发编程
首先,要明确并行和并发的概念。
并行,是指两个或者多个事件在同一时刻发生。并发,是指两个或多个事件在同一时间间隔发生。
并发偏重于多个任务交替执行,而多个任务之间还可能是串行的。而并行才是真正意义上的“同时执行”。
**并发编程是指在一台机器上“同时”处理多个任务。**并发是在同一实体上的多个事件,多个事件在同一时间间隔发生。并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。
go 并发机制及 CSP 并发模型
CSP 模型
CSP 模型是上个世纪七十年代提出的,不同于传统的多线程通过共享内存来通信,CSP 讲究的是“以通信的方式来共享内存”。用于描述两个独立的并发实体通过共享的通讯 channel 进行通信的并发模型。CSP 中的 channel 是第一类对象,它不关注发送消息的实体,而是关注发送消息时使用的 channel。
go 中 channel 一般创建后,用于协程之间传递信息,类似于一个消息队列。
go 并发机制
goroutine 是 go 实际并发执行的实体,它底层使用协程实现并发,协程是一种运行在用户态的用户线程,类似于 greenthread,go 底层选择使用协程的原因如下:
- 它在用户空间操作,避免了内核态和用户态的切换成本
- 可以由语言和框架层进行调度
- 相较于线程,拥有更小的栈空间,允许创建大量的协程
goroutine 的特性
go 中有三个对象:
- G(goroutine):我们说的协程,是用户级的轻量级线程,每个 goroutine 对象中的 sched 保存着它的上下文信息
- M(work thread):对 OS 内核级线程的封装,数量对应真实的 CPU 数量(一般远大于)
- P(processor):逻辑处理器,即为 G 和 M 的调度对象,用来调度 G 和 M 之间的关联关系,数量可以通过 GOMAXPROCS() 来设置,默认为核心数
正常情况下,一个 CPU 对象启动一个 M,M 检查并执行 G。遇到 G 阻塞的时候,会启动一个新的 M,以充分利用 CPU 资源。因此,有时候 M 数量会远大于 CPU个数。
GPM 如何调度
- 新创建的 G 会先保存在 P 的本地队列中,如果本地队列已满,那么就会保存在全局的 G 队列中,等待被 P 执行。
- M 和 P 绑定之后,M 会不断从 P 的本地队列中无锁地取出 G,并切换到这个 G 的堆栈执行。当 P 的本地 G 队列空了,就会从全局 G 队列获取一批 G。当全局队列中也没有待运行的 G 时,就会尝试从其他的 P 窃取部分 G 来执行,相当于 P 之间的负载均衡。
一轮调度
其实,一般我们知道上面的调度逻辑就差不多了。不过,如果需要深入了解的话,还是从最开始的一轮调度开始理一下吧。
引导程序会为 Go 程序的运行建立必要的环境,在完成一系列初始化工作之后,Go 程序的 main 函数才会真正执行。引导程序会在最后让调度器进行一轮调度,这样才能让封装了 main 函数的 G 能马上执行。
-
**调度器先判断当前 M 是否被锁定。**一般来说,Go 的调度器会按照一定策略动态地关联 M、P 和 G,并以此高效的执行并发程序,一般来说并不需要用户程序的任何干预。但是在极少情况下,用户程序不得已需要对 Go 的运行时调度进行干预。
**锁定 M 和 G 可以说是为了 CGO 准备的。**因为有些 C 语言的函数库(如 OpenGL)会用到线程本地存储技术,将数据存储在当前内核线程的私有缓存中。因此,包含了调用此类 C 函数库的代码的 G 会变得特殊:**在特定时间只能与同一个 M 产生关联,这样会对调度效率造成负面影响。**从上面的流程图可以看出,就算锁定了,一定要尽量减少锁定的时间。
-
如果发现当前 M 已经和某个 G 锁定了,那么会立即停止调度并停止当前 M。一旦和它锁定的 G 处于可运行状态,它就会被唤醒并继续运行这个 G。
-
如果判断当前 M 未与任何 G 锁定,那么调度器会检查是否有运行时串行任务正在等待执行(此类任务执行时需要停止 Go 调度器,STW)。如果 gcwaiting 不为 0,那么会停止并阻塞当前 M 以等待运行时串行任务执行完成。
-
否则,开始真正的可运行 G 寻找之旅。一旦找到一个可运行的 G,调度器会在判定该 G 未与任何 M 锁定之后,立即让当前 M 运行它。(查找的过程:先尝试获取执行 GC 任务的 G -> 从全局可运行 G 队列获取 G -> 从本地 P 得可运行 G 队列获取 G,最后到全力查找可运行的 G)
全力查找可运行的 G
概括来说,就是会多次尝试从各个地方查找可运行的 G:
- **获取执行终结器的 G:**所有的终结函数的执行都会由一个专用的 G 负责,如果这个专用 G 已完成任务,调度器就会获取它,把它置为 Grunable 状态并放入本地 P 的可运行 G 队列
- **从本地 P 得可运行 G 队列获取 G:**调度器尝试从这里获取一个 G,并返回
- **从调度器的可运行 G 队列获取 G:**调度器尝试从这里获取一个 G,并返回
- **从网络 I/O 轮询器获取 G:**如果 netpoller 已经被初始化且有过网络 I/O 操作,那么调度器就会尝试从这里获取一个 G 列表,并且把表头的 G 返回,同时把其他的 G 都放入调度器的可运行 G 队列。如果没有初始化或者没有过网络 I/O 操作,就跳过。这里没有获取成功也不会阻塞。
- **从其他 P 的可运行 G 队列获取 G:**条件允许时,调度器会使用伪随机算法从全局 P 列表中选取 P,然后尝试从它们的可运行 G 队列中盗取一半的 G 到本地 P 的可运行 G 队列。选取 P 和盗取 G 会重复多次,成功就停止。如果一直没有成功,那么第一阶段结束。
- **获取执行 GC 标记任务的 G:**第二阶段,调度器会先判断是否正处于 GC 的标记阶段,以及本地 P 是否可以用于 GC 标记任务,如果都是 true,就将本地 P 持有的 GC 标记专用 G 置为 Grunnable 并返回
- 从调度器的可运行 G 队列获取 G:调度器再次尝试从这里获取一个 G,并返回。如果还是找不到,就会解除本地 P 与当前 M 地关联,并把该 P 放入调度器的空闲 P 列表
- **从全局 P 列表中的每个 P 得可运行 G 队列获取 G:**遍历全局 P 中的所有 P,检查他们的可运行 G 队列。只要某个 P 的可运行 G 队列不为空,那就从调度器的空闲 P 列表中取一个 P,判定其可用后,将其和当前 M 关联在一起,然后返回第一阶段(第五步),否则继续后续;
- **获取执行 GC 标记任务的 G:**判断是否正处于 GC 的标记阶段,以及与 GC 标记任务相关的全局资源是否可用,如果都是 true,调度器从空闲 P 列表拿出一个 P。如果这个 P 持有一个 GC 标记专用 G,就关联该 P 与当前 M,然后再次执行第二阶段(从6 开始)
- **从网络 I/O 轮询器获取 G:**此步骤与步骤4 基本相同,但是这里的获取是阻塞的。只有当 netpoller 那里有可用的 G 时,阻塞才会解除。同样的,如果没有初始化或者没有过网络 I/O 操作,就跳过。
如果经过上述10个步骤之后,还没有找到可运行的 G,调度器就会停止当前 M。后续重新唤醒时,会重新进入查找可运行的 G 的子流程。
自旋状态: 意味着它还没有找到 G 来运行。一般来说,运行时系统中至少会有一个自旋的 M,调度器也会尽量保证有一个自旋的 M 存在。除非发现没有自旋的 M,调度器是不会新启用或恢复一个 M 去运行新 G 的。
为什么要有 P
- GM 模型的问题
- 存在单一的全局 mutex 和集中状态管理:mutex 需要保护所有与 goroutine 相关的操作,导致锁竞争严重;
- goroutine 交接问题:M 之间经常交接可运行的 goroutine,而且可能会导致延迟增加和额外开销。每个 M 必须能够执行任何可运行的 G;
- 每个 M 都需要做内存缓存:会导致资源消耗过大,数据局部性差
- 存在系统调用的情况下,线程经常会被阻塞和解阻塞。这增加了很多额外的性能开销
- GMP 模型的优化
- 每个 P 都有自己的本地队列,大幅度减轻了对全局队列的直接依赖,所带来的效果就是锁竞争的减少,而 GM 模型的性能开销大头就是锁竞争;
- 每个 P 相对的平衡上,在 GMP 模型中也实现了 Work Stealing 算法,如果 P 的本地队列为空,会从全局队列或者其他 P 得本地队列窃取可运行的 G 来运行,减少空转,提高了资源利用率;
一般来说,M 的数量都会多于 P。在 Go 中,M 的数量默认是 10000,P 的默认数量是 CPU 核数。由于 M 的属性,也就是如果存在系统阻塞调用,阻塞了 M,又不够用的情况下,M 会不断增加。
M 不断增加的话,如果本地队列挂载在 M 上,那么就意味着本地队列也会随之增加。这显然是不合理的,因为本地队列的管理会变得复杂,且 Work Stealing 性能会大幅度下降。
M 被系统调用阻塞之后,我们是期望他能将未执行的任务分配给其他 M 继续运行,而不是全部停止。因此,使用 M 是不合理的,引入新的组件 P,把本地队列关联到 P 上,就能很好的解决这个问题。
go 的 CSP 并发模型
go 的 CSP 并发模型,是通过 goroutine 和 channel 来实现的。
goroutine 是 go 中并发的执行单位,channel 用于各个并发结构体之间的通信。
go 中常用的并发模型
通过 channel 通知实现并发控制
无缓冲的通道指的是通道的大小为 0,也就是说,这种类型的通道在接收前没有能力保存任何值,它要求发送 goroutine 和接收 goroutine 同时准备好,才可以完成发送和接收操作。无缓冲的通道我们也称之为同步通道,通过它接可以简单实现并发控制。
func main() {ch := make(chan struct{})go func() {fmt.Println("start working")time.Sleep(time.Second * 1)ch <- struct{}{}}()<- chfmt.Println("finished")
}
通过 sync 包中的 WaitGroup 实现并发控制
goroutine 是异步执行的,有的时候为了防止在结束 main 函数的时候结束掉 goroutine,所以需要同步等待,这时候就需要使用 WaitGroup 了。在 Sync 包中,提供了 WaitGroup,它会等待它收集的所有 goroutine 任务全部完成。
需要注意的是,WaitGroup 在第一次使用后,不能被拷贝。
func main() {wg := sync.WaitGroup{}for i := 0; i < 5; i++ {wg.Add(1)go func(wg sync.WaitGroup, i int) {fmt.Printf("i:%d", i)wg.Done()}(wg, i)}wg.Wait()fmt.Println("exit")
}
上面代码运行之后会出现死锁。是因为 wg 给拷贝传递到了 goroutine 中,导致只有 Add 操作,其实 Done 操作是在 wg 的副本执行的。因此,Wait 就会死锁。
有两个修改方式:1️⃣ 将匿名函数中的 wg 传入类型改为 *sync.WaitGroup,这样就能引用到正确的 WaitGroup 了。2️⃣ 将匿名函数中的 wg 的的传入参数去掉,因为 go 支持闭包类型,在匿名函数中可以直接使用外面的 wg 变量。
Go 1.7 之后的 Context
通常,一些简单场景下使用 channel 和 WaitGroup 已经足够了,但是当面临一些复杂多变的网络并发场景下 channel 和 WaitGroup 就显得有些力不从心了。
⽐如⼀个⽹络请求 Request,每个 Request 都需要开启⼀个 goroutine 做⼀些事情,这些 goroutine ⼜可能会开启其他的 goroutine,⽐如数据库和RPC服务。
所以我们需要⼀种可以跟踪 goroutine 的⽅案,才可以达到控制他们的⽬的,这就是 Go 语⾔为我们提供的 Context,称之为上下⽂⾮常贴切,它就是 goroutine 的上下⽂。
它是包括⼀个程序的运⾏环境、现场和快照等。每个程序要运⾏时,都需要知道当前程序的运⾏状态,通常Go 将这些封装在⼀个 Context ⾥,再将它传给要执⾏的 goroutine 。context 包主要是⽤来处理多个 goroutine 之间共享数据,及多个 goroutine 的管理。
context 包的核⼼是 struct Context,接⼝声明如下:
// A Context carries a deadline, cancelation signal, and requestscoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {// Done returns a channel that is closed when this `Context` is canceled// or times out.// Done() 返回⼀个只能接受数据的channel类型,当该context关闭或者超时时间到了的时候,该channel就会有⼀个取消信号Done() <-chan struct{}// Err indicates why this Context was canceled, after the Done channel// is closed.// Err() 在Done() 之后,返回context 取消的原因。Err() error// Deadline returns the time when this Context will be canceled, if any.// Deadline() 设置该context cancel的时间点Deadline() (deadline time.Time, ok bool)// Value returns the value associated with key or nil if none.// Value() ⽅法允许 Context 对象携带request作⽤域的数据,该数据必须是线程安全的。Value(key interface{}) interface{}
}
Context 对象是协程安全的,你可以把⼀个 Context 对象传递给任意个数的 gorotuine,对它执⾏取消操作时,所有 goroutine 都会接收到取消信号。
⼀个 Context 不能拥有 Cancel ⽅法,同时我们也只能 Done channel 接收数据。其中的原因是⼀致的:接收取消信号的函数和发送信号的函数通常不是⼀个。
典型的场景是:⽗操作为⼦操作操作启动 goroutine,⼦操作也就不能取消⽗操作。
相关文章:

【Go 基础】并发相关
并发相关 CAS CAS算法(Compare And Swap),是原⼦操作的⼀种,,CAS 算法是⼀种有名的⽆锁算法。⽆锁编程,即不使⽤锁的情况下实现多线程之间的变量同步。可⽤于在多线程编程中实现不被打断的数据交换操作,从…...
数据质量规则(Data Quality Rules)
数据质量规则(Data Quality Rules)是指用来确保数据的准确性、完整性、一致性和可用性的标准或逻辑规则。这些规则通常在数据集成、数据存储和数据分析过程中执行,以保证数据符合预期的业务需求或技术规范。 以下是数据质量规则的分类及其内…...
stm32延时
1. void delay_config(void) {SysTick->CTRL | SysTick_CTRL_CLKSOURCE_Msk; //时钟源为系统时钟168MHzSysTick->LOAD 167; //重载值为168-1,每1us溢出一次 }void delay_ms(u32 nTime) {nTime * 1000;SysTick->CTRL | SysTick_CTRL_ENABLE_Msk; //…...
工作随笔2024,12.9
1.关于connect重复连接,会导致当该信号发出时槽函数会执行对应的次数,所以在添 加init相关名称的函数要查看内部是否有connect,是否会造成重复连接. 2. 建议如果是唯一一个连接的,可以使用uni Que connection这个属性 3. 有关事…...
【PGCCC】 pg_query 6.0:使用 Postgres 自己的解析器解析、反解析和规范化 SQL 查询的 Ruby 库
pg_query 这个 Ruby 扩展使用实际的 PostgreSQL 服务器源来解析 SQL 查询并返回内部 PostgreSQL 解析树。 此外,该扩展允许您规范化查询(用 $n 替换常量值)并将这些规范化的查询再次解析为解析树。 当您构建此扩展时,它会构建 …...
18.Vue 3 + OpenLayers:实现添加全屏显示功能示例
前言 在地图应用中,全屏显示功能可以为用户提供更好的视觉体验和交互感受。本文将带大家实现一个基于 Vue 3 和 OpenLayers 的全屏显示地图功能,适合初学者或开发者快速上手。 项目准备 1. 项目搭建 如果尚未创建 Vue 3 项目,可以通过以下…...

04_掌握Python基础语句
学习完本篇内容,你将掌握以下技能: 掌握 Python 中的基础类型,包括整数、浮点数、布尔值、字符串等。掌握 Python 中的运算符,包括算术运算符、比较运算符、逻辑运算符、位运算符等。掌握 Python 中的语句,包括赋值语句、选择语句、循环语句等。掌握 Python 中的控制流语句…...

iOS如何自定义一个类似UITextView的本文编辑View
对于IOS涉及文本输入常用的两个View是UITextView和UITextField,一个用于复杂文本输入,一个用于简单文本输入,在大多数开发中涉及文本输入的场景使用这两个View能够满足需求。但是对于富文本编辑相关的开发,这两个View就无法满足自…...
【时时三省】(NIT计算机考试)Word的使用方法
山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 一、软件简介 Microsoft Word,简称Word,是微软公司开发的一款文字处理软件,广泛应用于文档编辑、排版、打印等领域。无论是撰写论文、报告、简历…...
openjdk17 jvm加载class文件,解析字段和方法,C++源码展示
##构造方法ClassFileParser,parse_stream解析文件流 ClassFileParser::ClassFileParser(ClassFileStream* stream,Symbol* name,ClassLoaderData* loader_data,const ClassLoadInfo* cl_info,Publicity pub_level,TRAPS) :_stream(stream),_class_name(NULL),_load…...

驱动断链的研究
准备 source insight 从现在开始我们正式进入内核编程,但是很多内核里面的结构和类型是需要我们额外声明的,我们就需要一个工具来快速的阅读WIn内核源码。这里我贴出我所参考的博客 羽夏看Win系统内核——SourceInsight 配置 WRK - 寂静的羽夏 - 博客…...

在 Windows WSL 上部署 Ollama 和大语言模型:从镜像冗余问题看 Docker 最佳实践20241208
🛠️ 在 Windows WSL 上部署 Ollama 和大语言模型:从镜像冗余问题看 Docker 最佳实践 ⭐ 引言 随着大语言模型(LLM)和人工智能技术的迅猛发展,开发者们越来越多地尝试在本地环境中部署模型进行实验。 但部署过程中常…...

做题时HashSet、TreeSet、LinkedHashSet的选择
一、HashSet 此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。 代码: import java.util.HashSet; import java.util.LinkedHashSet; import ja…...

Manus手套动作捕捉AI训练灵巧手
随着人工智能(AI)和机器人技术的融合日益紧密,使用真实动作数据AI扩容训练机器人的方式正在被用于开发更富表现力的机器人。Manus手套凭借精准的动作捕捉技术和导出数据的强大兼容性,在灵巧手的研发和应用中发挥了重要作用。 手部…...

嵌入式驱动开发详解4(内核定时器)
文章目录 前言通用定时器系统节拍节拍数与时间转换基本框架定时器使用代码展示通用定时器特点 高精度定时器 前言 LInux内核定时器是一种基于未来时间点的计时方式,以当前时刻来启动的时间点,以未来的某一时刻为终止点。比如,现在是10点5分&…...

Linux:信号的预备和产生
引入: 比如当前快递小哥需要通知你下来取快递(产生信号),然后通过电话或短信告知了你(发送信号),但是当前你正在打游戏,所以你并不会马上去处理,但是你会记得这件事&…...

国城杯2024——Curve
相关知识链接:https://tangcuxiaojikuai.xyz/post/187210a7.html #sagemath from Crypto.Util.number import *def add(P, Q):(x1, y1) P(x2, y2) Qx3 (x1*y2 y1*x2) * inverse(1 d*x1*x2*y1*y2, p) % py3 (y1*y2 - a*x1*x2) * inverse(1 - d*x1*x2*y1*y2, p…...

AI生成不了复杂前端页面?也许有解决方案了
在2024年,编程成为了人工智能领域最热门的赛道。AI编程技术正以惊人的速度进步,但在生成前端页面方面,AI的能力还是饱受质疑。自从ScriptEcho平台上线以来,我们收到了不少用户的反馈,他们表示:“生成的页面…...

常见矩阵分析法(BCG、GE、IE、SPACE、TOWS、优先、战略优先级、安索夫、风险矩阵):如何通过系统化方法助力战略决策与数据驱动决策
在快速变化的商业环境中,企业决策者面临着诸多复杂的选择与挑战。矩阵分析法作为战略分析的重要工具,能够系统化地分析企业的内外部环境,帮助管理层做出更加科学、合理的决策。本文将全面解析常见的矩阵分析法,并探讨它们在数据驱…...
JWT 在 SaaS 系统中的作用与分布式 SaaS 系统设计的最佳实践
在现代 SaaS(软件即服务) 系统中,随着服务规模的扩大和用户需求的多样化,如何高效、安全地进行用户身份验证、权限控制以及租户隔离,成为了系统架构中的核心问题之一。**JWT(JSON Web Token)**作…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...