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

Go第三方框架--ants协程池框架

1. 背景介绍

1.1 goroutine

ants是站在巨人的肩膀上开发出来的,这个巨人是goroutine,这是连小学生都知道的事儿,那么为什么不继续使用goroutine(以下简称go协程)呢。这是个思考题,希望讲完本文大家可以有个答案。
go协程只涉及用户态的使用,不涉及内核态和两态的切换,所以非常轻便,通常一个协程大概只占用2k的内存,比线程更轻量级,而且其还有特别高效的GMP协程调度算法,使得go语言编写并发程序简单和高效。但是官方没有提供协程池包,虽然go协程有如此多的优点,go语言也支持垃圾自动回收,但是不断地对资源进行创建和回收是一种犯罪行为;为了更好的支持协程资源空间重复使用、并发控制、提升性能,并为开发者提供更简便和使用的功能,有大量的第三方协程池框架应运而生,ants是其中的佼佼者。

2. ants简介

ants是github托管的一个高效的协程池库,其star已经有12k了,足见其受欢迎的程度。本ants版本使用的是v2.8.2 目前最新版是2.9.1。其主要committer是潘建锋,根据公开资料(应该没侵犯隐私)曾任职腾讯,现在在亚马逊上班,未婚,个人博客。八卦完了,现在看看ants的发展史吧。ps:为了还原历史,下文引用ants的readme 相信很多博客都介绍过了,纯属凑字数。

2.1 简介

ants是一个高性能的 goroutine 池,实现了对大规模 goroutine 的调度管理、goroutine 复用,允许使用者在开发并发程序的时候限制 goroutine 数量,复用资源,达到更高效执行任务的效果。

2.2 功能
  • 自动调度海量的 goroutines,复用 goroutines
  • 定期清理过期的 goroutines,进一步节省资源
  • 提供了大量有用的接口:任务提交、获取运行中的 goroutine 数量、动态调整 Pool 大小、释放 Pool、重启 Pool
  • 优雅处理 panic,防止程序崩溃
  • 资源复用,极大节省内存使用量;在大规模批量并发任务场景下比原生 goroutine 并发具有更高的性能
  • 非阻塞机制
2.3 ants是如何运行的

流程图
在这里插入图片描述

2.4 使用
// demoPoolFunc1 执行任务
func demoPoolFunc1() {time.Sleep(2 * time.Millisecond)
}// TestAntsPoolWaitToGetWorker ants运行简单例子.
func TestAntsPoolWaitToGetWorker(t *testing.T) {var wg sync.WaitGroupp, _ := NewPool(1000) // 初始化一个协程池 容量是 1000defer p.Release()     // 执行成功手动释放协程池for i := 0; i < 10000; i++ { // 启动10000个任务 让协程池来运行wg.Add(1)             // wg++_ = p.Submit(func() { // 提交任务demoPoolFunc1()wg.Done() // 完成后 wg--})}wg.Wait() // 阻塞等待任务完成
}

可以看到 暴露给外面的 就是 图2.3的 pool池初始化、任务提交和pool池回收 剩下的 都在水面之下 . ps:submit 也可以使用协程启动 这样 下方代码中出现的锁 就可以解释的通了。
以下 源码讲解就拿上述代码为例子

ps: 还有一些内容 请看客移步代码的readme

3. 几种重要的结构体

3.1 pool

pool核心结构体,其结构体 如下

// Pool 接受来自客户端的任务,通过循环利用 goroutines 限制了总数量为给定值。
type Pool struct {// pool 的容量,负值表示 pool 容量无限,使用无限 pool 是为了避免由于 pool 的嵌套使用(向同一个 pool 提交任务,该任务又向同一个 pool 提交新任务)可能引起的无限阻塞问题。capacity int32// 当前运行的 goroutines(goworker的items长度) 数量。running int32// 保护 worker 队列的锁。lock sync.Locker// 存储可用 worker 的切片,是任务执行器的队列,是一个接口 workers workerQueue// 状态用于通知 pool 关闭自身。state int32// 等待获取空闲 worker 的条件。cond *sync.Cond// workerCache  池化技术 不断生成 新worker , 但是他不是 2.3工作池。它在woker数量没达到容量阈值时,// 会不断生成。一旦达到了,就不会再工作,因为这时 workerStack 中的 items 满了后会自己维持(因为一般不会有过期,又有容量限制),后续会讲解。// 直到将过期的worker放入队列时,woker数量减少,才又继续工作生成新的worker。但是 一般情况下 过期时间都特别长,当过期时间短时,// 工作池就是 workers + workerCache  这两个属性 在不断向items补充 gowoker。workers回收可用的goworker,workerCache 补齐不足容量的部分。// 但是一般不会让goworker过期,所以为了方便 我们一般吧 items叫做工作池workerCache sync.Pool// waiting 是已经在 pool.Submit() 上被阻塞的 goroutines 数量(因为本2.3例子没有使用协程提交submit,所以最大为1),受 pool.lock 保护。waiting int32purgeDone int32stopPurge context.CancelFunc // cancel 用来停止 goPurge 函数ticktockDone int32stopTicktock context.CancelFunc // cancel 用来停止 goTicktock 函数now atomic.Value  // 存储现在时间// 存放一些参数 过期时间 最大阻塞任务等options *Options}
3.2 workerStack

任务的执行队列

type workerStack struct {items  []worker // 工作池, 对应 2.3 图中的 工作池 每个工作池 运行一个 协程 expiry []worker // 已经过期的任务执行器队列(工作池)
}

这个结构体实现了 workerQueue 结构体的接口 也就是 3.1pool里的 workerQueue 。

type workerQueue interface {len() intisEmpty() boolinsert(worker) error    // 向工作池中 加入一个 任务执行器detach() worker   // 从工作池中 拿到一个 任务执行器refresh(duration time.Duration) []worker //  清理掉过期的goworkerreset()  // 重置 将所有goworker 任务执行完毕后 结束
}

接口中都是操作工作执行器(goworker)队列的一些函数

3.3 goWorker

goWorker 是实际任务执行器

// goWorker 是实际执行任务的执行器,
// 它启动一个 goroutine 来接受任务并执行函数调用。
type goWorker struct {// 拥有该 worker 的池 也就是 3.1中的 pool, 将pool传给 worker 以便调用pool的函数和属性。pool *Pool// task 存放 执行任务的chan。 将任务输送到chan来执行。task chan func()// lastUsed 在将 worker 放回队列时将更新,有多个任务则时间是最后一个任务执行时间。lastUsed time.Time
}

其实现了下面的 worker 结构体


type worker interface {run()   // 运行 goworkerfinish() // 让任务执行完毕后 结束goworker lastUsedTime() time.TimeinputFunc(func()) // 向 goworker 中的 chan 传递任务inputParam(interface{})
}

接口中都是操作工作执行器的一些函数

ps: 之所以需要执行器和其队列的接口 是因为有不同的执行器和执行器队列,例如goWorkerWithFunc执行器和 loopQueue执行器队列,因为不是本文重点不做介绍,但其大体执行逻辑是一致的,感兴趣的看官可以自行研究。

如上我们就可以猜到执行的一个大体的脉络,控制逻辑是 pool–>workerStack(2.3中的工作池,主要是属性items)–>goworker(worker 任务执行器),接下来我们来看下源码 验证下

4. ants 协程池初始化

见 2.4 代码部分 p, _ := NewPool(1000) ,这样可以初始化一个pool池。我们看下其源码,

// NewPool generates an instance of ants pool.
func NewPool(size int, options ...Option) (*Pool, error) {if size <= 0 {size = -1}opts := loadOptions(options...) // opts 没有传 跳过if !opts.DisablePurge { // 值是 true 走这里if expiry := opts.ExpiryDuration; expiry < 0 {return nil, ErrInvalidPoolExpiry} else if expiry == 0 {opts.ExpiryDuration = DefaultCleanIntervalTime // 过期时间}}if opts.Logger == nil { // logger 选择默认的opts.Logger = defaultLogger}p := &Pool{ // 初始化 pool 将size赋值给capacitycapacity: int32(size),lock:     syncx.NewSpinLock(), // 这里是自己实现的指数退避的自旋锁,为啥要自己实现我想可能是官方的性能作者不满意吧 有空细研究下options:  opts,}p.workerCache.New = func() interface{} { //  从缓存池中可以获取新的 goworkerreturn &goWorker{pool: p,task: make(chan func(), workerChanCap), // 创建一个 大小是1的任务chan,注意大小是0和1不一样,感兴趣的自行gpt3.5}}if p.options.PreAlloc { // false 不走这里if size == -1 {return nil, ErrInvalidPreAllocSize}p.workers = newWorkerQueue(queueTypeLoopQueue, size)} else {p.workers = newWorkerQueue(queueTypeStack, 0) // 初始化 workerStack(工作池) 结构体 大小是0}p.cond = sync.NewCond(p.lock)p.goPurge()    // 启动一个协程 不断检查执行器队列,将过期的执行器(goworker)的任务释放,任务对应的任务执行器放入 pool中,工作执行器协程退出;p.goTicktock() // 启动协程更新pool的时间,用来更新 goworker的最后使用时间return p, nil
}

到这里 一个新的 pool就建立起来了 ,这其中需要进一步剖析的是 p.goPurge() 其 值得关注的调用链 如下

在这里插入图片描述

purgeStaleWorkers(ctx context.Context) 函数代码如下

// 启动一个协程 不断检查执行器队列,将过期的执行器的任务释放,任务对应的任务执行器放入 workercache 中,工作执行器协程退出;
func (p *Pool) purgeStaleWorkers(ctx context.Context) {ticker := time.NewTicker(p.options.ExpiryDuration) // 使用过期时间 建一个 tickerdefer func() {ticker.Stop()atomic.StoreInt32(&p.purgeDone, 1)}()for {select {case <-ctx.Done():returncase <-ticker.C: // 到了过期时间 就开始执行 查找过期woker的逻辑}if p.IsClosed() {break}var isDormant boolp.lock.Lock()                                               //  为什么加锁  (因为submit可以使用 协程启动) staleWorkers := p.workers.refresh(p.options.ExpiryDuration) // 将 items里面 过期的任务队列 复制到 expires队列里 并 返回n := p.Running()isDormant = n == 0 || n == len(staleWorkers)p.lock.Unlock()// Notify obsolete workers to stop.// This notification must be outside the p.lock, since w.task// may be blocking and may consume a lot of time if many workers// are located on non-local CPUs.for i := range staleWorkers { // 将过期队列 依次调用 finish() 使得本 woker停止(因为本worker的任务执行完毕后 会 阻塞,当调用finish()时,会给chan传递一个 nil 使得任务停止 详细 请看 worker.run()) 并加入workercache 中staleWorkers[i].finish()staleWorkers[i] = nil}// There might be a situation where all workers have been cleaned up (no worker is running),// while some invokers still are stuck in p.cond.Wait(), then we need to awake those invokers.if isDormant && p.Waiting() > 0 { // 如果 不存在运行的协程(调用 run()会启动一个协程 数量 跟执行器相等 可以看做一个协程就是一个运行着的执行器 也就是 len(items)),或者 等待的协程>1 就广播 通知等待的协程来运行(主要是来提交任务)p.cond.Broadcast()}}
}

到这里 goPurge()就讲解 完毕了 。

5. 任务提交

任务 提交 到 任务 处理的 调用链 如下
在这里插入图片描述

run 函数是具体执行 任务 的函数,它启动一个协程来处理submit函数提交的任务
池化完毕后 就是 任务提交 p.Submit(…),这是核心执行逻辑 见 2.3 中 提交后 后续的执行逻辑都在这个函数里。 老规矩,我们继续追踪源码

5.1 任务提交
func (p *Pool) Submit(task func()) error {  // 其接受一个 任意类型的 任务 if p.IsClosed() {      // 如果协程池关闭了 则报错return ErrPoolClosed}w, err := p.retrieveWorker()  // 获取一个工作执行器(用 goworker 简称),规则:大体逻辑 如果工作池有,就拿来用,如果没有就新建(还有阻塞等)见 2.3流程图if w != nil {w.inputFunc(task)  // 将 任务 加入到 获取的 goworker 的任务chan 中(这时 goworker的run()函数可能正在 chan处阻塞),开始执行任务。大家思考下这边会不会阻塞?? }return err
}

其中 retrieveWorker() (w worker, err error) 函数如下

5.2 任务获取
// 这个应该是核心执行逻辑,返回一个可以使用的w或者阻塞
// retrieveWorker returns an available worker to run the tasks.
func (p *Pool) retrieveWorker() (w worker, err error) {p.lock.Lock() retry:// First try to fetch the worker from the queue.if w = p.workers.detach(); w != nil { //  items(工作池) 头部goworker出栈 赋值给 w,如果有空闲的w(w!=nil) 则返回p.lock.Unlock()//  为什么得到 w 后不run() 而是直接返回   ps: 因为只要可以获取到 工作执行器 则 必定 有一个 协程正在运行,否则这个执行器就是过期的退出了 不在 items中。return}// If the worker queue is empty and we don't run out of the pool capacity,// then just spawn a new worker goroutine.// 如果工作队列是空的或者 正在跑的 协程数(items大小)没达到 容量,任务刚启动时,会走这边,一旦items满了后,基本不走这边 要么从 items直接获取任务(上面代码),要么阻塞(下方代码 p.addWaiting)if capacity := p.Cap(); capacity == -1 || capacity > p.Running() {p.lock.Unlock()w = p.workerCache.Get().(*goWorker) // 从pool缓冲池中获取一个 工作队列,注意 workerCache 不是工作池,而是 对应2.3 “新启动一个 woker 来执行任务 ”的前半句话。w.run()  // 这边 开始 启动协程 处理 任务。思考题:items数量和协程数量是否是一致的(一致)return}// Bail out early if it's in nonblocking mode or the number of pending callers reaches the maximum limit value.if p.options.Nonblocking || (p.options.MaxBlockingTasks != 0 && p.Waiting() >= p.options.MaxBlockingTasks) { p.lock.Unlock()return nil, ErrPoolOverload}// Otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool.p.addWaiting(1) //能走到这里,证明items 达到最大容量了。p.cond.Wait()   // block and wait for an available worker// 程序阻塞等待唤醒,任务执行完毕或者goworker过期都会唤醒。p.addWaiting(-1)if p.IsClosed() {p.lock.Unlock()return nil, ErrPoolClosed}goto retry  // 被唤醒后 走到 retry 重新开始 为任务选 gowoker
}

在上述代码 w = p.workerCache.Get().(*goWorker) 处,我们可以发现 ,每次从缓存池获取一个 goworker 都要调用 run()函数,来启动goworker,这个函数才是执行任务的核心代码。它会启动一个协程 并采用chan阻塞等待任务得到来。下面介绍run()函数

5.3 任务执行

这里开始执行任务

func (w *goWorker) run() {w.pool.addRunning(1) // 运行的 goworker 数量+1go func() {defer func() { // 当 goworker过期 或者 批量任务执行完毕 调用 p.Release(), 下方 for 循环 退出,然后则调用 本deferw.pool.addRunning(-1)     // 正在运行的 goworker 数量-1w.pool.workerCache.Put(w) // 将不用的 goworker 放入 缓存池中if p := recover(); p != nil {if ph := w.pool.options.PanicHandler; ph != nil {ph(p)} else {w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())}}// Call Signal() here in case there are goroutines waiting for available workers.w.pool.cond.Signal() // 唤醒一个 在 retrieveWorker()函数 中 代码	p.cond.Wait()  处阻塞的程序,开始提交任务}()for f := range w.task { // 从 5.1submit()函数的 inputFunc(func())调用 处获取任务if f == nil { // 如果 chan 传递的是 nil (调用 finish()),则这个goworker退出return}f()                                    // 开始执行任务 执行2.4中例子中的 demoPoolFunc1()if ok := w.pool.revertWorker(w); !ok { // 任务执行完毕,将本goworker 入栈 items ,这时 协程不退出 继续for循环,这边就实现了 重复利用。return}}}()
}

任务执行完成后,对应的goworker需要入栈 在代码 w.pool.revertWorker(w) 处执行,下面介绍下这个函数

5.4 工作执行器(goworker)入栈

本小结主要是将闲下来的goworker入栈 涉及到 revertWorker(worker *goWorker) 函数,其代码如下:

// revertWorker puts a worker back into free pool, recycling the goroutines.
// 直接翻译: 将goworker放入 items中,循环使用这个协程
func (p *Pool) revertWorker(worker *goWorker) bool {if capacity := p.Cap(); (capacity > 0 && p.Running() > capacity) || p.IsClosed() { p.cond.Broadcast()return false}worker.lastUsed = p.nowTime() //  更新goWorker协程的最后使用时间p.lock.Lock()// To avoid memory leaks, add a double check in the lock scope.// Issue: https://github.com/panjf2000/ants/issues/113if p.IsClosed() {p.lock.Unlock()return false}if err := p.workers.insert(worker); err != nil { // 将 goWorker 入栈p.lock.Unlock()return false}// Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue.p.cond.Signal() /// 唤醒一个 在 retrieveWorker()函数 中 代码	p.cond.Wait()  处阻塞的程序(如果是协程启动的submit,则将此协程唤醒)p.lock.Unlock()return true
}

到此 我们2.3的流程图涉及到的所有模块都梳理完毕,接下来就等着任务执行完毕后,将协程池释放。

6. 协程池释放

释放所有跟pool相关的资源,我们来梳理下 有几处在运行或阻塞的程序
a. 提交任务时,当多于items容量时,阻塞在 5.2 函数的 p.cond.Wait() 处(要是submit是协程提交的话这里阻塞更多,,现在最多阻塞1个)
b. 新建 池时 启动的两个协程 见 标题4
c. submit提交任务时,启动的 items里的协程
所以有四处需要关闭,我们看下源码

func (p *Pool) Release() {if !atomic.CompareAndSwapInt32(&p.state, OPENED, CLOSED) { // 使用 cas算法 将 pool状态 修改为关闭,为续资源关闭做准备。// 则所有协程任务执行完毕后,入 items 队列时会退出见 revertWorker() 函数 第一行)// 所有阻塞的协程 会退出(见 5.2 retrieveWorker()函数 最后部分 p.IsClosed()),其他见 对 p.IsClosed() 的调用return}if p.stopPurge != nil { // 停止 goPurge()函数 对应 --> bp.stopPurge()p.stopPurge = nil}p.stopTicktock() //  对应 --> bp.stopTicktock = nilp.lock.Lock()p.workers.reset() // 将所有goworker停止 对应 --> cp.lock.Unlock()// There might be some callers waiting in retrieveWorker(), so we need to wake them up to prevent// those callers blocking infinitely.p.cond.Broadcast() // 广播给 在 p.cond.wait()处阻塞的协程 继续运行,后续调用 p.IsClosed()退出。 这里对应   --> a

到这里ants的真个生命流程就梳理完毕了。

7.总结

感觉还是得梳理源码,才能有长进。这次只是梳理了最常用的核心部分,还有其他一些功能或结构体没有梳理到,希望有时间再添加吧。由于本人水平有限,本次梳理难免有疏漏,还请各位大佬指正,互相学习,谢谢。

相关文章:

Go第三方框架--ants协程池框架

1. 背景介绍 1.1 goroutine ants是站在巨人的肩膀上开发出来的&#xff0c;这个巨人是goroutine&#xff0c;这是连小学生都知道的事儿&#xff0c;那么为什么不继续使用goroutine(以下简称go协程)呢。这是个思考题&#xff0c;希望讲完本文大家可以有个答案。 go协程只涉及用…...

【原创】springboot+vue个人财务记账管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…...

MySQL基础练习题:习题2-3

这部分主要是为了帮助大家回忆回忆MySQL的基本语法&#xff0c;数据库来自于MySQL的官方简化版&#xff0c;题目也是网上非常流行的35题。这些基础习题基本可以涵盖面试中需要现场写SQL的问题。上期帮助大家建立数据库&#xff0c;导入数据&#xff0c;接下来让我们继续练习。 …...

超图SuperMap-Cesium,地形图层,可以渲染一个或多个地形(地形可缓存DEM,TIN方式),webGL代码开发(2024-04-08)

1、缓存文件类型TIN格式&#xff0c;TIN的地形sct只能加一个 const viewer new Cesium.Viewer(cesiumContainer); viewer.terrainProvider new Cesium.CesiumTerrainProvider({isSct: true, // 是否为iServer发布的TIN地形服务,stk地形设置为falserequestWaterMask : true,…...

PCB学习记录---原理图

一、注释 NC&#xff1a;no connect,默认不连接 NF: no fix&#xff0c;默认不安装 0R: 0R的电阻&#xff0c;即可以短路 二、看图流程 1、看标题&#xff0c;了解功能 2、浏览有几个模块 3、找芯片对应的数据手册&#xff0c;了解芯片功能和使用 例如CH224&#xff…...

结构型模式--3.组合模式【草帽大船团】

1. 好大一棵树 路飞在德雷斯罗萨打败多弗朗明哥之后&#xff0c;一些被路飞解救的海贼团自愿加入路飞麾下&#xff0c;自此组成了草帽大船团&#xff0c;旗下有7为船长&#xff0c;分别是&#xff1a; 俊美海贼团75人 巴托俱乐部56人 八宝水军1000人 艾迪欧海贼团4人 咚塔塔海…...

网络基础三——其他周边问题

3.1ARP原理 ​ ARP不是一个单纯的数据链路层的协议&#xff0c;而是一个介于数据链路层和网络层之间的协议&#xff1b; ​ 以广播的形式(主机号填成全1)构建Mac帧&#xff0c;发送ARP请求包&#xff0c;告诉所有在局域网的主机我的IP地址和Mac帧&#xff0c;与目的IP相同的主…...

学习周报:文献阅读+Fluent案例+水力学理论学习

目录 摘要 Abstract 文献阅读&#xff1a;物理信息的神经网络与湍流传质的非封闭机制模型相结合 文献摘要 提出问题 提出方案 实验设置 所需方程介绍 雷诺时均方程&#xff08;RANS&#xff09; K-epsilon两方程模型 神经网络框架 DNN部分 损失函数定义 PINN部分…...

Redis(持久化 -- RDB AOF)

持久化 通常我们认为持久化为: 重启进程/重启主机之后, 数据仍然存在不丢失 把数据存储在硬盘上 – 持久 把数据存储在内存中 – 不持久 Redis 持久化 redis 是一个内存数据库, 也就是说本身是不持久的(但是快[效率高]), 于是 Redis 提供了持久化机制 — RDB 和 AOF 二者都是对…...

LDR6328助力Type-C普及,便捷充电,绿色生活更精彩

随着科技的进步和全球统一接口的需求&#xff0c;Type-C接口正日益受到青睐。越来越多的设备正选择采纳这一先进的接口设计&#xff0c;它的普及无疑在改善着我们的日常生活。 在过往&#xff0c;许多小功率设备如小风扇、蓝牙音箱、桌面台灯以及家用加湿器等&#xff0c;都普遍…...

redis主从复制、哨兵模式、集群

文章目录 redis主从复制主从复制的配置**安装Redis**配置主服务器配置从服务器验证主从效果 哨兵模式哨兵的工作机制哨兵模式的搭建启动哨兵 集群分布式集群的搭建 redis主从复制 Redis主从复制&#xff08;Redis replication&#xff09;是Redis提供的一种数据备份和故障转移…...

shell免登陆脚本

#!/bin/bash ## 脚本接收的参数&#xff0c;也就是要互相配置 SSH 免密登录的服务器列表参数 BASE_HOST_LISTip ## 密码&#xff0c;默认用户是当前运行脚本的用户&#xff0c;比如 root 用户 ## 这里改成你的用户对应的密码 BASE_PASSWORD"password" ## shell 函…...

基于springboot+vue+Mysql的职称评审管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…...

GitLab教程(一):安装Git、配置SSH公钥

文章目录 序一、Git安装与基本配置&#xff08;Windows&#xff09;下载卸载安装基本配置 二、SSH密钥配置 序 为什么要使用代码版本管理工具&#xff1a; 最近笔者确实因为未使用代码版本管理工具遇到了一些愚蠢的问题&#xff0c;笔者因此认为代码版本管理工具对于提高团队…...

【算法】无序数组的两数之和 - map标记

题目 在一个无序数组中找到两个数&#xff0c;两个数之和为给定的一个数&#xff0c;返回两个数在数组中的下标。 原理 遍历数组&#xff0c;遍历到一个数字的时候&#xff0c;记录下这个数及其下标&#xff0c;遍历时判断给定数减去这个数为key在map中是否存在&#xff0c;…...

Prime (2021): 2

前言 这个靶机有亿点难,收获很多。打靶的时候&#xff0c;前面很顺&#xff0c;到创建ssh公钥之后就一点不会了。 1 01 arp扫描&#xff0c;发现有一个130&#xff0c;再查看端口 有22&#xff0c;80&#xff0c;129&#xff0c;445&#xff0c;10123 dirb扫描目录 这…...

React 状态管理:安全高效地修改对象属性的 3 种方法

在 React 应用程序中,状态(state)是驱动整个应用程序的核心。当应用程序的状态发生变化时,React 会自动重新渲染相应的组件,以确保用户界面的更新。 与数组状态一样,对象状态在 React 中也需要特别处理。直接修改对象属性是不被允许的,因为 React 的不可变性原则要求我们创建一…...

python实现pdf的页面替换

利用第三方库PyPDF2&#xff0c;下面例子中进行的是将 origin.pdf 的第17页替换为 s17.pdf 的第1页&#xff1a; import PyPDF2def replace_pages(original_pdf_path, replacement_pages):with open(original_pdf_path, rb) as original_file:original_pdf PyPDF2.PdfReader(…...

[AIGC] Java List和Map常用API以及其Python实现方式对照介绍

Java和Python作为当今非常浅显易懂的编程语言&#xff0c;其数据结构中对于List和Map&#xff08;Java&#xff09;或List和Dict&#xff08;Python&#xff09;的操作无疑是每个程序员都非常必需的知识。本文将介绍在Java中对List和Map常用的一些操作&#xff0c;并给出在Pyth…...

零基础如何闯入IT的神秘大门?

前言 随着信息技术的飞速发展&#xff0c;IT行业成为了许多有志之士梦寐以求的职业领域。但对于零基础的人来说&#xff0c;如何成功进入这个行业却是一个不小的挑战。下面&#xff0c;我将结合自身的C语言专业知识&#xff0c;为大家详细阐述一条可行的学习路径&#xff0c;并…...

java程序 .exe启动nginx防止重复启动,已解决

java代码生成好的.exe启动nginx服务程序 根据nginx占用端口来解决nginx服务重复启动问题&#xff08;下面代码了解代码逻辑后根据自己的业务需求修改即可&#xff09; 代码&#xff1a; package org.example;import javax.swing.*; import java.awt.*; import java.io.*; …...

二十一、Rust 反射 获取类型

不同于 java 中的反射&#xff0c;Rust 没有提供以往意义上的运行时反射&#xff0c;取而代之的是 “编译期反射”&#xff0c;如 类型分析、类型转换、类型签名。但即便如此&#xff0c;也已经能对 Rust元编程 提供很多助力了。 这种操作&#xff0c;主要通过 Any 来实现&…...

Flutter Engine引擎概念

1.Flutter是Google提供的开源框架。 2.本身由C编写并兼容iOS(底层C)/Android(底层C)平台的FlutterEngine框架负责UI渲染、数据转移、调用DartVM虚拟机。 3.FlutterEngine框架由Skia图形库、Dart运行时、Flutter框架代码组成。Skia是用于图形绘制和文本显示的2D图形引擎库&#…...

【运行环境】加载资源的形式

相关资源&#xff1a;性能优化原则 1 加载资源的形式 html代码 媒体文件&#xff0c;如图片&#xff0c;视频等 javasccript css 2 加载资源的过程 DNS解析&#xff1a;域名-> ip地址 浏览器根据IP地址向服务器发送http 请求 服务器处理http 请求&#xff0c;并返回给浏览器…...

备战蓝桥杯Day40 - 第11届python组真题 - C跑步锻炼

一、题目描述 二、思路 1、使用datetime库中的方法可以很好的解决这个问题。 2、定义起始时间和结束时间&#xff0c;判断是否是周一或者是1号&#xff0c;结果res加上相应的里程数。 3、最后输出 res 即为本题答案。 三、代码实现 import datetimestart datetime.date(2…...

书生·浦语大模型第二期实战营第二课笔记和基础作业

来源&#xff1a; 作业要求:Homework - Demo 文档教程:轻松玩转书生浦语大模型趣味 Demo B站教程:轻松玩转书生浦语大模型趣味 Demo 1. 笔记 2.基础作业 2.1 作业要求 2.2 算力平台 2.3 新建demo目录&#xff0c;以及新建目录下的文件&#xff0c;下载模型参数 2.4 Intern…...

成功解决> 错误: 无效的源发行版:17

运行项目的时候出现下面的报错&#xff1a; Execution failed for task ‘:device_info_plus:compileDebugJavaWithJavac’. 错误: 无效的源发行版&#xff1a;17 原因&#xff1a;没有设置好自己项目的JDK版本 解决&#xff1a;1.检查自己项目的JDK版本 将自己的项目改为JDK 1…...

深度剖析:网络安全中的红蓝对抗策略

红蓝对抗 红蓝对抗服务方案 在蓝队服务中&#xff0c;作为攻击方将开展对目标资产的模拟入侵&#xff0c;寻找攻击路径&#xff0c;发现安全漏洞和隐患。除获取目标系统的关键信息&#xff08;包括但不限于资产信息、重要业务数据、代码或管理员账号等&#xff09;外&#x…...

Java异常处理之旅:解救迷失的程序员(二)

本系列文章简介&#xff1a; 在编程世界中&#xff0c;程序员们常常会遇到各种各样的问题和挑战。有时候&#xff0c;这些问题很容易解决&#xff0c;而有时候&#xff0c;它们却会让我们感到迷失和无助。 在这个旅程中&#xff0c;我们将探索Java异常处理的世界&#xff0c;解…...

网络安全介绍

网络安全是指网络系统的硬件、软件及其系统中的数据受到保护&#xff0c;不因偶然的或者恶意的原因而遭受到破坏、更改、泄露&#xff0c;系统能够连续可靠正常地运行&#xff0c;网络服务不中断。以下是一些网络安全相关的方面&#xff1a; 首先&#xff0c;随着科学技术的进…...