nginx 内存管理(二)
共享内存
- 共享内存结构与接口定义
- nginx共享内存在操作系统上的兼容性设计
- 互斥锁
- 锁的结构体
- 锁的一系列操作(core/ngx_shmtx.c)
- 创建锁
- 原子操作
- nginx的上锁操作
- 尝试加锁
- 获取锁
- 释放锁
- 强迫解锁
- 唤醒等待进程
- slab共享内存块管理
- nginx的slab大小规格
- 内存池结构体
- 共享内存池结构体slots
- 分配共享内存池
共享内存结构与接口定义
正常来说,通过malloc函数申请的内存都是进程私有的内存但是Linux会提供共享内存的系统调用,如mmap和munmap等
Nginx基于Linux提供的系统调用,封装了共享内存的数据结构以及共享内存的创建与释放函数,其共享内存结构和接口定义如下:
os/unix/ngx_shmem.h
typedef struct { u_char *addr; //指向申请的共享内存块首地址size_t size; //共享内存块大小ngx_str_t name; //共享内存块名字ngx_log_t *log; //共享内存块日志ngx_uint_t exists;//标志是否已经存在
} ngx_shm_t;//共享结构//以下共享接口
ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);//创建共享内存块
void ngx_shm_free(ngx_shm_t *shm);//释放共享内存块
ngx_int_tngx_shm_alloc(ngx_shm_t *shm)
{
shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); //创建
if (shm->addr == MAP_FAILED) { //错误处理ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size); return NGX_ERROR;
}
return NGX_OK;//成功,返回
}
void ngx_shm_free(ngx_shm_t *shm)
{
if (munmap((void *) shm->addr, shm->size) == -1)//是否成功
{ //失败处理,记录ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "munmap(%p, %uz) failed", shm->addr, shm->size);
}
}
nginx共享内存在操作系统上的兼容性设计
#if (NGX_HAVE_MAP_ANON)//匿名共享内存
………
#elif (NGX_HAVE_MAP_DEVZERO)//文件共享内存
………
#elif (NGX_HAVE_SYSVSHM)//IPC System V共享内存
………
#endif
互斥锁
- 并发进程访问共享内存时需要加锁。nginx提供了互斥锁的机制,保证了正确的共享内存的访问。nginx的进程主要是通过ngx_shmtx_t进行加锁、解锁等操作。
- nginx实现的时候,如果操作系统提供原子操作机制,就使用操作系统的原子操作实现互斥锁,否则nginx采用文件锁实现互斥。
互斥锁模型
锁的结构体
//core/ngx_shmtx.h
typedef struct { ngx_atomic_t lock;//0为锁开(空闲),其它(进程号)已上锁#if (NGX_HAVE_POSIX_SEM) //如果有SEM信号量ngx_atomic_t wait;//等待共享内存进程总数#endif
} ngx_shmtx_sh_t;
上锁、解锁的结构体模型:
typedef struct {#if (NGX_HAVE_ATOMIC_OPS) //若有原子操作…………#if (NGX_HAVE_POSIX_SEM) //如果有信号量 …………#endif#else …………#endif ngx_uint_t spin;
} ngx_shmtx_t;
typedef struct {#if (NGX_HAVE_ATOMIC_OPS) //若有原子操作ngx_atomic_t *lock;//进程内指向共享内存锁的地址#if (NGX_HAVE_POSIX_SEM) //如果有信号量 ngx_atomic_t *wait; //指向共享内存等待进程总数ngx_uint_t semaphore; //是否使用信号量,1使用sem_t sem;//sem_t信号量,可用于线程之中,也可用于进程#endif#else //操作系统无原子操作和信号量支持,用文件ngx_fd_t fd; u_char *name;#endif ngx_uint_t spin;
} ngx_shmtx_t;
锁的一系列操作(core/ngx_shmtx.c)
- ngx_int_t ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name);//创建锁
- void ngx_shmtx_destroy(ngx_shmtx_t *mtx);//销毁锁
- void ngx_shmtx_lock(ngx_shmtx_t *mtx);//获取锁
- ngx_uint_t ngx_shmtx_trylock(ngx_shmtx_t *mtx);//尝试加锁
- void ngx_shmtx_unlock(ngx_shmtx_t *mtx);//释放锁
- ngx_uint_t ngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid);//强制解锁
- static voidngx_shmtx_wakeup(ngx_shmtx_t *mtx)//唤醒等进程
临界区管理的基本思路
①找到临界区
②在临界区前面增加一段用于进行检查的代码,当不满足进入临界区的条件,就不进入,直到满足条件才进入,称为进入区(entry section)。
③在临界区后面加上一段称为离开区(exit section)的代码,作为善后处理。基本形式如下:
创建锁
ngx_int_t ngx_shmtx_create( ngx_shmtx_t *mtx,ngx_shmtx_sh_t *addr, u_char *name);//创建锁//ngx_shmtx_t *mtx,是进程操作锁结构地址
//ngx_shmtx_sh_t *addr,是共享内存中保存的锁结构地址
//u_char *name,名字(用于区别不同锁)地址{
mtx->lock = &addr->lock; //将共享内存锁信息储存到进程操作锁结构体中
if (mtx->spin == (ngx_uint_t) -1) { return NGX_OK; }
mtx->spin = 2048;//自旋次数指定#if (NGX_HAVE_POSIX_SEM) //如果是信号量,初始化sem为1,并将semaphore设为1mtx->wait = &addr->wait; if (sem_init(&mtx->sem, 1, 0) == -1) { ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno, "sem_init() failed"); } else { mtx->semaphore = 1; }
#endif
return NGX_OK;
}
- 进程操作锁结构需要获得(保存)共享内存锁的信息,对于自旋锁,
1)保存共享内存锁的lock;
2设置自旋锁的自旋次数;以便于后续进行加锁、解锁等操作;
mtx->lock = &addr->lock; if (mtx->spin == (ngx_uint_t) -1) { //已经加锁了 ?return NGX_OK; }
mtx->spin = 2048;//nginx设置的进程自旋次数
- 对于信号量,
1)保存共享内存锁的保存wait;
2设置信号量的semaphore或sem的值;以便于后续进行加锁、解锁等操作;
mtx->wait = &addr->wait; //保存指向保存共享内存进程总数指针
if (sem_init(&mtx->sem, 1, 0) == -1) //线程信号量初始化失败
{ ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno, "sem_init() failed");
}
else // 线程信号量初始化成功,初始化semaphore为1
{ mtx->semaphore = 1; //使用信号量
}
- 其它可能需要记录的调试信息以及可能的错误处理等
原子操作
计算机系统并发的基础
- 两个原子操作
ngx_atomic_cmp_set(a,old,new):如果*a==old,将*a赋值为new,返回1。否则返回0。ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid),(原子操作)若*mtx->lock为0,即将*mtx->lock赋值为ngx_pid。
ngx_atomic_fetch_add(old,v):将*old加上v,并返回*old。ngx_atomic_fetch_add(mtx->wait, 1),将*mtx->wait加上1,并返回加之前的*mtx->wait值。
nginx的上锁操作
当共享内存lock为0(表示空闲)时可以上锁。对于上锁的操作,Nginx将其标准化为将lock(当其为0时)设为进程的PID。即
*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)。
尝试加锁
ngx_uint_tngx_shmtx_trylock(ngx_shmtx_t *mtx)
{ return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}
获取锁
- 进程自旋锁的获取
-
当共享内存lock为0(表示空闲)时可以上锁。对于上锁的操作,nginx将其标准化为将lock(当其为0时)设为进程的PID。即
*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)。 -
因为*mtx->lock 为0时,可能有很多进程都来上锁,但只能有一个进程会成功上锁。因此对上锁进程来讲,以上上锁操作可能不成功。
-
此时,当有多个CPU时,上锁进程可以等待一段T时间后,再次尝试上锁操作。Ngnix对T的构造有其独特的方法。
-
上锁失败,放弃使用CPU
-
void ngx_shmtx_lock(ngx_shmtx_t *mtx)//自旋锁
{ngx_uint_t i, n; //初始化变量
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx lock");
for(;;){//不断循环进行自旋方式加锁;#if (NGX_HAVE_POSIX_SEM)信号量方式加锁,wait记录等待共享进程总数,等待进程挂入sem等待队列;#endifngx_sched_yield();//优化方式放弃CPU}
}//自旋式加锁
for ( ;; ) { if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) { return; } //成功上锁返回 if (ngx_ncpu > 1) { //当有多个CPU时,等待T时间后,再次尝试上锁 for (n = 1; n < mtx->spin; n <<= 1){//构造等待时间T,再多次尝试上锁{ for (i = 0; i < n; i++) { //每次都有等待时间T ,每次内循环等待次数不一样ngx_cpu_pause(); //(借用CPU机制)优化自旋等待} if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) { return; } //再次尝试上锁若成功,则返回} }
ngx_sched_yield();//(优化)上锁失败,放弃使用CPU 。调度选中后,再次自旋上锁(为啥?)。
}
- 信号量处理锁的获取
如果是信号量:- 等待共享内存进程总数(预先)(原子性操作)加一;
- 当lock为0(表示空闲)时可以上锁。按照nginx标准化上锁操作,也就是将lock(当其为0时)设为进程的PID。即
*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)。
如果成功,将等待共享内存进程数减一(因已成功上锁,预计加需扣除),返回。
(void) ngx_atomic_fetch_add(mtx->wait, 1); //原子操作预加一if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)){ //上锁成功了 (void) ngx_atomic_fetch_add(mtx->wait, -1); //原子操作减一 return; //返回}
- 如果上锁失败,将(该加锁进程)挂入sem的等待队列中。由于挂入sem的等待队列操作可能失败,为了确保1)中的加一操作与实际等待进程总数一致性,需要不断尝试挂入等待队列操作,直至成功挂入为止。否则数据将不一致。挂入等待队列的某进程,由释放锁某进程唤醒。
while (sem_wait(&mtx->sem) == -1) {//如果失败,再次进行挂入sem等待队列操作 ngx_err_t err; err = ngx_errno; //获取错误原因 if (err != NGX_EINTR) { //若是系统原因,进行错误日志处理后,终止尝试 ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err, "sem_wait() failed while waiting on shmtx");break; } } ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx awoke"); continue; //进行下一个循环
释放锁
void ngx_shmtx_unlock(ngx_shmtx_t *mtx);//释放锁
{ if (mtx->spin != (ngx_uint_t) -1) //调试信息处理{ ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,"shmtx unlock"); } if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {//将lock设为0就是释放ngx_shmtx_wakeup(mtx); //唤醒等待进程}
}
强迫解锁
ngx_uint_tngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid)//强迫解锁
{ ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, “shmtx forced unlock”); //记录调试信息if (ngx_atomic_cmp_set(mtx->lock, pid, 0)) { //共享内存lock为0(空闲) ngx_shmtx_wakeup(mtx); //唤醒等待共享进程进程 return 1; } return 0;//强制失败,返回0;
}
唤醒等待进程
- 如果有信号量支持://因为只有有信号量支持时,才有sem等待队列
- 如果没有标记使用信号量,(没有构造等待队列)返回。
#if (NGX_HAVE_POSIX_SEM)//由信号量支持
if (!mtx->semaphore) { return; }
………//剩下的其它操作实现
#endif
- 不断使用ngx_atomic_cmp_set(mtx->wait, wait, wait - 1)对mtx->wait减一操作,直至成功将mtx->wait(原子操作)减一。
for ( ;; ) { //不断尝试进行以下方式原子操作减一wait = *mtx->wait; if ((ngx_atomic_int_t) wait <= 0) { return; } //没有等待共享内存进程,返回。if (ngx_atomic_cmp_set(mtx->wait, wait, wait - 1)) { break; //成功原子操作减一,终止尝试原子减一。 } }
- 从sem等待队列中唤醒一个进程;
if (sem_post(&mtx->sem) == -1)
{ //失败唤醒一个进程,错误日志处理ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno, "sem_post() failed while wake shmtx"); }
slab共享内存块管理
nginx设计与实现了一种基于slab理念的共享内存块机制,并提供了创建共享内存块、从共享内存块中申请与释放内存的API。其结构体包括(core/ngx_slab.h以及core/ngx_slab.c):
- ngx_slab_page_s:内存块管理结构体
ngx_slab_stat_t:内存页使用信息管理结构体
ngx_slab_pool_t:共享内存块结构体
typedef struct ngx_slab_page_s ngx_slab_page_t;
nginx的slab大小规格
内存池结构体
typedef struct ngx_slab_page_s ngx_slab_page_t;
struct ngx_slab_page_s
{ uintptr_t slab; ngx_slab_page_t *next; //后向uintptr_t prev;//前向
};typedef struct { ngx_uint_t total; //总数 ngx_uint_t used; //使用总数 ngx_uint_t reqs; //请求总数 ngx_uint_t fails;//失败总数
} ngx_slab_stat_t;// 共享内存池结构体
typedef struct { ngx_shmtx_sh_t lock; //内存锁 size_t min_size; //可以分配最小内存大小,即为8size_t min_shift; //最小slab内存的幂数,即min_size=2^ min_shiftngx_slab_page_t *pages; //指向第一页的管理结构ngx_slab_page_t *last; //指向最后页的管理结构ngx_slab_page_t free; //指向空闲首页的一个结点ngx_slab_stat_t *stats; //指向记录各种规格slab统计信息链表ngx_uint_t pfree; //空闲总页数u_char *start; //空闲页始址u_char *end;//空闲末址ngx_shmtx_t mutex; //进程操作锁结构u_char *log_ctx; u_char zero; unsigned log_nomem:1; void *data; void *addr;//共享内存池结构地址
} ngx_slab_pool_t;
共享内存池结构体slots
1.初始化共享内存池管理结构体各数据成员的值,理清控制管理关系。
2.分出控制管理结构后,剩余的即为可以共享分配的内存池。
管理不同规格的ngx_slab_page_t的首地址,nginx用宏ngx_slab_slots(pool)描述了这一大小位置关系:
(按情形)初始化共享池为0xA5(除共享池管理结构外)
初始化管理不同大小slab的ngx_slab_page_t
(按情形)初始化ngx_slab_stat_t
计算总页数pages
初始化pool的pages
初始化pool的free
初始化管理空页的首个ngx_slab_page_t
初始化pool的start
初始化pool的start,因对齐,修正总空闲数
初始化pool的其它成员
分配共享内存池
- 理论上,每个大小为KB的系统物理页,可以包含k/m个大小为mB规格的slab块。
- 为了标明一个系统物理页中含有的大小为mB规格slab块的占有情况,Nginx为每个系统物理页使用bitmap描述其含有的每个slab块是否空闲。
- 这样,每个大小为KB的系统物理页,需要k/m位描述其每个slab的空闲占有情况,如位1表示占有,如位0表示空闲。
- 对于小块内存(
大小8Byte~32Byte
),需要较多位(512b~128b
)。nginx在内存首页开辟固定区域,码放这些bitmap。 - 对于精确内存(大小为64Byte),需要64b。nginx使管理内存页的ngx_slab_page_t结构体的slab字段作为bitmap。
- 对于大块内存(
大小128Byte~2048Byte
),需要(32b~2b
)使用slab的前32位作为bitmap
void* ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size)
{void *p; ngx_shmtx_lock(&pool->mutex);//互斥分配p = ngx_slab_alloc_locked(pool, size); ngx_shmtx_unlock(&pool->mutex); //互斥分配return p;
}
相关文章:

nginx 内存管理(二)
共享内存 共享内存结构与接口定义nginx共享内存在操作系统上的兼容性设计互斥锁锁的结构体锁的一系列操作(core/ngx_shmtx.c)创建锁 原子操作nginx的上锁操作尝试加锁获取锁释放锁强迫解锁唤醒等待进程 slab共享内存块管理nginx的slab大小规格内存池结构…...

【DevChat】智能编程助手 - 使用评测
写在前面:博主是一只经过实战开发历练后投身培训事业的“小山猪”,昵称取自动画片《狮子王》中的“彭彭”,总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域,如今终有小成…...

Geek challenge 2023 EzHttp
打开链接需要使用post请求提交username和password 查看源码得到提示,爬虫想到robots协议 访问robots.txt 访问得到的路径:/o2takuXXs_username_and_password.txt 拿到用户名和密码: username:admin password:dm1N123456r00t# 进行post传参…...

matlabR2021a正版免费使用
目录 matlab介绍: 安装: matlab介绍: MATLAB(Matrix Laboratory的缩写)是一种高级技术计算和编程环境,由MathWorks公司开发。它在科学、工程、数据分析和数学建模领域中广泛应用,为用户提供了…...

天气数据可视化平台-计算机毕业设计vue
天气变幻无常,影响着我们生活的方方面面,应用天气预报信息可以及时了解天气的趋势,给人们的工作、生活等带来便利,也可以为我们为未来的事情做安排和打算,所以一个精准的、易读 通过利用 程序对气象网站大量的气象信息…...
揭秘Java switch语句中的case穿透现象
揭秘Java switch语句中的case穿透现象 1. switch 语句简介2. case穿透现象的原因关于 goto 3. switch和if的区别4. 总结 导语:在 Java 开发中,我们经常使用switch语句来进行条件判断和分支选择。然而,有一个令人困惑的现象就是,当…...

Java-API简析_java.io.FilterOutputStream类(基于 Latest JDK)(浅析源码)
【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://blog.csdn.net/m0_69908381/article/details/134106510 出自【进步*于辰的博客】 因为我发现目前,我对Java-API的学习意识比较薄弱…...

C语言 每日一题 PTA 10.29 day7
1.特殊a串数列求和 给定两个均不超过9的正整数a和n,要求编写程序求a aa aaa⋯ aa⋯a(n个a)之和。 输入格式: 输入在一行中给出不超过9的正整数a和n。 输出格式: 在一行中按照“s 对应的和”的格式输出。 思路 n…...
持续集成部署-k8s-服务发现-Ingress 路径匹配与虚拟主机匹配
持续集成部署-k8s-服务发现-Ingress 路径匹配与虚拟主机匹配 1. 安装 Ingress-Nginx2. 创建要代理的 Service3. 创建一个新的 Ingress-Nginx1. 安装 Ingress-Nginx 要使用 Ingress-Nginx 首先第一步是需要先安装它,安装的步骤可以参考:持续集成部署-k8s-服务发现-Ingress 2…...

selenium工作原理和反爬分析
一、 Selenium Selenium是最广泛使用的开源Web UI(用户界面)自动化测试套件之一,支持并行测试执行。Selenium通过使用特定于每种语言的驱动程序支持各种编程语言。Selenium支持的语言包括C#,Java,Perl,PHP,Python和Ru…...

windows电脑安装系统后固态硬盘和机械硬盘的盘符号顺序显示错乱,解决方法
一、场景 由于电脑磁盘是SSD固态硬盘自己拓展的1T机械硬盘组成,固态硬盘分为C、D两个盘区,机械硬盘分为E、F两个盘区。为了提升运行速度,系统安装在C盘,安装完成后按照习惯盘区顺应该为C、D、E、F,但实际情况却是D、E…...
自定义控件的子控件布局(onLayout()方法)
onLayout()方法用于指定布局中子控件的位置,该方法通常在自定义的ViewGroup容器中重写。 重写onLayout()方法中的常用方法: getChildCount() 获取子控件数量 getChildAt( int index ) 获取指定index的子控件,返回View view.getVisibilit…...
vscode提取扩展出错xhr
在 Visual Studio Code (VSCode) 中提取扩展出现 XHR 错误通常意味着在下载扩展或进行扩展管理操作时出现了网络请求问题。XHR (XMLHttpRequest) 是一种用于在浏览器中进行 HTTP 请求的技术,通常用于获取数据或资源。在 VSCode 中,它也可用于管理扩展的下…...

Docker 笔记(上篇)
Docker 概述 Docker 概念 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows操作系统的机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之…...

python自动化测试(六):唯品会商品搜索-练习
目录 一、配置代码 二、操作 2.1 输入框“运动鞋” 2.2 点击搜索按钮 2.3 选择品牌 2.4 选择主款 2.5 适用性别 2.6 选择尺码 2.7 选择商品:(通过css的属性去匹配) 2.8 点击配送地址选项框 一、配置代码 # codingutf-8 from selen…...
深度强化学习用于博弈类游戏-基础测试与说明【1】
深度强化学习用于博弈类游戏-基础【1】 1. 强化学习方法2. 强化学习在LOL中的应⽤2.1 环境搭建2.2 游戏特征元素提取1)小地图人物位置:2)人物血量等信息3)在整个图像上寻找小兵、防御塔的位置4)自编码器提取3. 策略梯度算法简介参考资料1. 强化学习方法 伴随着人工智能的潮起…...

通过requests库使用HTTP编写的爬虫程序
使用Python的requests库可以方便地编写HTTP爬虫程序。以下是一个使用requests库的示例: import requests# 发送HTTP GET请求 response requests.get("http://example.com")# 检查响应状态码 if response.status_code 200:# 获取响应内容html response.…...
550MW发电机变压器组继电保护的整定计算及仿真
摘要 电力系统继电保护设计是根据系统接线图及要求选择保护方式,进行整定计算,电力系统继电保护的设计与配置是否合理直接影响到电力系统的安全运行。如果设计与配置不当,保护将不能正确工作,会扩大事故停电范围,造成…...

Linux 命令|服务器相关
1. 在公共 linux 上创建 python 虚拟环境 【精选】在公共Linux服务器上创建自己的python虚拟环境_服务器创建自己的环境-CSDN博客 2. 查看现存的状态,看有没有程序在跑 nvidia-smi命令详解-CSDN博客 3. 上传本地文件到服务器 在本地 Mac 计算机的终端中&#x…...

node 第十三天 express初见
express概念 Fast, unopinionated, minimalist web framework for Node.js 快速、独立、极简的 Node.js Web 框架。 express相当于前端的jquery, 在不更改不侵入原生node的基础上封装了大量易用且实用的服务端api, express框架的封装原理就是前面第十天我们自己封装的简易服务器…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...