SGI 空间配置器
前言
空间配置器是 STL 六大组件之一,它总是隐藏在容器的背后,默默工作,默默付出。本文为《STL 源码剖析》读书笔记,主要讨论 SGI 版本空间的配置和释放,对代码进行解读时会改变一些写法,使其更易于阅读。
对象构造前的空间配置和对象析构后的空间释放,由 <stl_alloc.h> 负责,SGI 对此的设计哲学如下:
- 向系统堆申请空间
- 考虑多线程状态(本文不考虑多线程情况)
- 考虑内存不足时的应变措施
- 考虑小块内存过多造成的内存碎片问题
一级配置器
一级配置器并没有什么特殊的地方,就是调用 malloc() 和 free() 申请和释放内存。
__malloc_alloc_template 源码:
#if 0
# include <new>
# define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
# include <iostream.h>
# define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)
#endiftemplate <int inst>
class __malloc_alloc_template {private:static void *oom_malloc(size_t);static void *oom_realloc(void *, size_t);#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUGstatic void (* __malloc_alloc_oom_handler)();
#endifpublic:static void * allocate(size_t n)
{void *result = malloc(n);if (0 == result) result = oom_malloc(n);return result;
}static void deallocate(void *p, size_t /* n */)
{free(p);
}static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{void * result = realloc(p, new_sz);if (0 == result) result = oom_realloc(p, new_sz);return result;
}static void (* set_malloc_handler(void (*f)()))()
{void (* old)() = __malloc_alloc_oom_handler;__malloc_alloc_oom_handler = f;return(old);
}};// malloc_alloc out-of-memory handling#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
#endiftemplate <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{void (* my_malloc_handler)();void *result;for (;;) {my_malloc_handler = __malloc_alloc_oom_handler;if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }(*my_malloc_handler)();result = malloc(n);if (result) return(result);}
}template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)
{void (* my_malloc_handler)();void *result;for (;;) {my_malloc_handler = __malloc_alloc_oom_handler;if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }(*my_malloc_handler)();result = realloc(p, n);if (result) return(result);}
}typedef __malloc_alloc_template<0> malloc_alloc;
oom
什么是 oom(out of member)?
申请内存时,如果没有空闲的物理内存,那么内核就会开始进行回收内存的工作。如果内存回收后,空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会触发 OOM (Out of Memory)机制。
OOM 机制会根据算法选择一个占用物理内存较高的进程,然后将其杀死,以便释放内存资源,如果物理内存依然不足,OOM 会继续杀死占用物理内存较高的进程,直到释放足够的内存。
SGI 中的 __malloc_alloc_oom_handler 操作又是什么?
__malloc_alloc_oom_handler 指向内存不足时的处理函数,是 SGI 模仿 C++ 的 set_new_handler,因为没有使用 ::operator new 来分配内存,所以不能直接使用 set_new_handler。
一个设计良好的 new_handler 函数做以下事情:
- 让更多内存可以被使用
- 安装另一个 new_handler
- 卸载 new_handler
- 抛出 bad_alloc
- 不返回
SGI 中内存不足时调用 oom_malloc() 和 oom_realloc(),在它们内部不断调用「内存不足处理函数」,期望在某次调用后,就得到了足够的内存然后返回。但如果「内存不足处理函数」并没有被客户端设定,便会调用 __THROW_BAD_ALLOC,丢出异常信息并终止进程。
内存不足时的处理操作:
// 此处的条件编译一定会执行 elif 部分
// 最后尽力了也申请不到内存时,就打印错误语句,结束程序
#if 0
# include <new>
# define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
# include <iostream.h>
# define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)
#endif// 成员变量,指向内存不足时的处理函数,初始值为空
static void (* __malloc_alloc_oom_handler)();// 参数为新设置的内存不足处理函数
// 返回值为旧的内存不足处理函数
static auto set_malloc_handler(void (*f)()) -> void (*)() {void (* old)() = __malloc_alloc_oom_handler;__malloc_alloc_oom_handler = f;return(old);
}// 该非类型模板参数没有用处
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n) {void (* my_malloc_handler)();void *result; // 指向申请到的内存while(true) {// 获取内存不足时的处理函数my_malloc_handler = __malloc_alloc_oom_handler;if (0 == my_malloc_handler) { // 如果没有设置处理函数,终止进程__THROW_BAD_ALLOC; }(*my_malloc_handler)(); // 调用内存不足处理函数result = malloc(n); // 再次尝试申请if (result != nullptr) {return(result); // 申请成功返回}}
}template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n) {void (* my_malloc_handler)();void *result;while(true) {// 获取内存不足时的处理函数my_malloc_handler = __malloc_alloc_oom_handler;if (0 == my_malloc_handler) { // 如果没有设置处理函数,终止进程__THROW_BAD_ALLOC; }(*my_malloc_handler)(); // 调用内存不足处理函数result = realloc(p, n); // 再次尝试申请if (result != nullptr) {return(result); // 申请成功返回}}
}
申请内存
一级配置器申请内存直接调用 malloc() 和 realloc() 函数。
static void * allocate(size_t n) {void *result = malloc(n);if (0 == result) {result = oom_malloc(n); // 申请失败时调用 oom_malloc}return result;
}static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz) {void * result = realloc(p, new_sz);if (0 == result) {result = oom_realloc(p, new_sz); // 申请失败时调用 oom_realloc}return result;
}
释放内存
一级配置器释放内存直接调用 free() 函数。
// 第二个参数没有作用
static void deallocate(void *p, size_t /* n */) {free(p);
}
二级配置器
二级配置器就比一级配置器复杂的多,大于 128 字节的申请调用一级配置器,小于 128 字节的内存使用自由链表数组分配。整个二级配置器共享一个内存池,内存不足时从内存池获取。提供函数从下层获取内存,并向自由链表中填充内存。
自由链表如下,提供以 8 为倍数的小块内存,小块内存的头部 4/8 字节指向下一空闲节点。
__default_alloc_template 源码:
enum {__ALIGN = 8};
enum {__MAX_BYTES = 128};
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};template <bool threads, int inst>
class __default_alloc_template {private:static size_t ROUND_UP(size_t bytes) {return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));}
private:union obj {union obj * free_list_link;char client_data[1]; /* The client sees this. */};
private:static obj * volatile free_list[__NFREELISTS]; static size_t FREELIST_INDEX(size_t bytes) {return (((bytes) + __ALIGN-1)/__ALIGN - 1);}static void *refill(size_t n);static char *chunk_alloc(size_t size, int &nobjs);// Chunk allocation state.static char *start_free;static char *end_free;static size_t heap_size;public:/* n must be > 0 */static void * allocate(size_t n){obj * volatile * my_free_list;obj * result;if (n > (size_t) __MAX_BYTES) {return(malloc_alloc::allocate(n));}my_free_list = free_list + FREELIST_INDEX(n);result = *my_free_list;if (result == 0) {void *r = refill(ROUND_UP(n));return r;}*my_free_list = result -> free_list_link;return (result);}/* p may not be 0 */static void deallocate(void *p, size_t n){obj *q = (obj *)p;obj * volatile * my_free_list;if (n > (size_t) __MAX_BYTES) {malloc_alloc::deallocate(p, n);return;}my_free_list = free_list + FREELIST_INDEX(n);q -> free_list_link = *my_free_list;*my_free_list = q;}static void* reallocate(void *p, size_t old_sz, size_t new_sz){void * result;size_t copy_sz;if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) {return(realloc(p, new_sz));}if (ROUND_UP(old_sz) == ROUND_UP(new_sz)) return(p);result = allocate(new_sz);copy_sz = new_sz > old_sz? old_sz : new_sz;memcpy(result, p, copy_sz);deallocate(p, old_sz);return(result);}} ;template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{char * result;size_t total_bytes = size * nobjs;size_t bytes_left = end_free - start_free;if (bytes_left >= total_bytes) {result = start_free;start_free += total_bytes;return(result);} else if (bytes_left >= size) {nobjs = bytes_left/size;total_bytes = size * nobjs;result = start_free;start_free += total_bytes;return(result);} else {size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);// Try to make use of the left-over piece.if (bytes_left > 0) {obj * __VOLATILE * my_free_list =free_list + FREELIST_INDEX(bytes_left);((obj *)start_free) -> free_list_link = *my_free_list;*my_free_list = (obj *)start_free;}start_free = (char *)malloc(bytes_to_get);if (0 == start_free) {int i;obj * __VOLATILE * my_free_list, *p;// Try to make do with what we have. That can't// hurt. We do not try smaller requests, since that tends// to result in disaster on multi-process machines.for (i = size; i <= __MAX_BYTES; i += __ALIGN) {my_free_list = free_list + FREELIST_INDEX(i);p = *my_free_list;if (0 != p) {*my_free_list = p -> free_list_link;start_free = (char *)p;end_free = start_free + i;return(chunk_alloc(size, nobjs));// Any leftover piece will eventually make it to the// right free list.}}end_free = 0; // In case of exception.start_free = (char *)malloc_alloc::allocate(bytes_to_get);// This should either throw an// exception or remedy the situation. Thus we assume it// succeeded.}heap_size += bytes_to_get;end_free = start_free + bytes_to_get;return(chunk_alloc(size, nobjs));}
}template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{int nobjs = 20;char * chunk = chunk_alloc(n, nobjs);obj * volatile * my_free_list;obj * result;obj * current_obj, * next_obj;int i;if (1 == nobjs) return(chunk);my_free_list = free_list + FREELIST_INDEX(n);/* Build free list in chunk */result = (obj *)chunk;*my_free_list = next_obj = (obj *)(chunk + n);for (i = 1; ; i++) {current_obj = next_obj;next_obj = (obj *)((char *)next_obj + n);if (nobjs - 1 == i) {current_obj -> free_list_link = 0;break;} else {current_obj -> free_list_link = next_obj;}}return(result);
}template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::start_free = 0;template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::end_free = 0;template <bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;template <bool threads, int inst>
__default_alloc_template<threads, inst>::obj * __VOLATILE
__default_alloc_template<threads, inst> ::free_list[__NFREELISTS] =
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
成员变量
enum {__ALIGN = 8}; // 对齐数
enum {__MAX_BYTES = 128}; // 可以申请的最大字节数
enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; // 自由链表个数static obj * volatile free_list[__NFREELISTS]; // 自由链表数组
static char *start_free; // 内存池空间起始位置
static char *end_free; // 内存池空间结束位置
static size_t heap_size; // 多开辟的堆大小
还有一个比较特殊的成员,自由链表的节点结构。
该联合体从第一个字段看:它可以被视为一个指针,指向下一节点。从第二个字段看:它可以被视为一个指针,指向实际的数据空间。
实际上该联合体并没有实际的作用,只是为了便于理解。申请的一块内存,在没被使用的时候,可以用其头部 4/8 字节指向下一空闲节点,不用维护另外的指针。
union obj {union obj * free_list_link;char client_data[1]; /* The client sees this. */
};
工具部分
这部分提供内存对齐,获取在自由链表数组中下标的函数。
因为自由链表中提供以 8 为倍数的小块内存,因此需要将申请的内存对齐为 8 的倍数。
static size_t ROUND_UP(size_t bytes) {// (bytes) + __ALIGN - 1 保证向对齐数进一位// ~(__ALIGN - 1) 去掉低位的 1return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));
}
同时也需要知道对应大小在自由链表数组中的下标,以边获取内存和归还内存。
static size_t FREELIST_INDEX(size_t bytes) {// - 1 因为数组的下标从零开始// 等于 ROUND_UP(bytes) / _ALIGN - 1// 因为低位的数在除对齐数后没有影响return (((bytes) + __ALIGN - 1) / __ALIGN - 1);
}
申请内存
申请内存时首先判断大小,大于 128 字节就调用一级配置器,小于 128 字节就去自由链表中获取,自由链表中没有内存就调用 refill() 填充内存。
static void * allocate(size_t n) {obj * volatile * my_free_list; obj * result; // 带回申请的内存// 大于 128 字节调用一级配置器if (n > (size_t) __MAX_BYTES) {return(malloc_alloc::allocate(n));}// 找到对应大小的自由链表my_free_list = free_list + FREELIST_INDEX(n);result = *my_free_list;if (result == 0) {// 自由链表中没有内存可用,为其填充内存void *r = refill(ROUND_UP(n));return r;}// 取出一个节点,调整 free_list 指向下一节点*my_free_list = result -> free_list_link;return (result);
}
释放内存
释放时同样需要先判断内存大小,大于 128 字节调用一级配置器释放,小于 128 字节还给自由链表。
// p 为要释放的首地址,n 为对象的大小
static void deallocate(void *p, size_t n) {obj *q = (obj *)p;obj * volatile * my_free_list;// 大于 128 字节调用一级配置器if (n > (size_t) __MAX_BYTES) {malloc_alloc::deallocate(p, n);return;}// 找到要插入的位置my_free_list = free_list + FREELIST_INDEX(n);// 将节点头插进去q -> free_list_link = *my_free_list;*my_free_list = q;
}
填充自由链表
当申请内存时发现自由链表中没有可用内存后,就调用 refill()。refill() 的作用是为指定自由链表填充内存,新的内存从内存池中获取,默认情况下是填充 20 个节点,但万一内存池中内存不足,获取的节点可能小于 20 个。
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n) {int nobjs = 20; // 默认填充的个数// 从内存池获取内存,nobjs 为引用传参,带回实际申请到的个数char * chunk = chunk_alloc(n, nobjs);obj * volatile * my_free_list;obj * result; // 返回使用的节点obj * current_obj, * next_obj;int i; // 只申请到一个节点,将该节点直接返回,不用向 free_list 中新增节点if (1 == nobjs) {return(chunk);}// 调整 my_free_list 指向,指向要添加节点的自由链表my_free_list = free_list + FREELIST_INDEX(n);// 需要将多余的节点插入到自由链表中// 获取第一个节点,后续返回使用result = (obj *)chunk;// + n 指向第二个节点*my_free_list = (obj *)(chunk + n);next_obj = (obj *)(chunk + n);for (i = 1; ; ++i) {// 分别指向当前节点、下一节点current_obj = next_obj;// (char *)next_obj + n 取一个节点的大小next_obj = (obj *)((char *)next_obj + n);// 一共申请了 nobjs 个节点,需要插入 n - 1 个if (nobjs - 1 == i) {// 将最后一个插入的节点置空current_obj -> free_list_link = 0;break;} else {// 采用尾插的方式current_obj -> free_list_link = next_obj;}}return(result);
}
内存池
chunk_alloc() 首先检查内存池中是否有内存可用,如果没有就尝试调用 malloc() 申请内存,申请失败就去更大节点的自由链表中寻找内存。如果经过上述艰难的过程,还是没有获取到内存的话,就会调用一级配置器,祈祷「内存不足处理函数」有所作用。
template <bool threads, int inst>
char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs) {char * result; // 结果指针size_t total_bytes = size * nobjs; // 要申请的总字节数size_t bytes_left = end_free - start_free; // 内存池剩余空间if (bytes_left >= total_bytes) {// 内存池剩余空间满足需求result = start_free;start_free += total_bytes; // 将 start 向后移,表示内存已被使用return(result);} else if (bytes_left >= size) {// 内存池剩余空间不能满足要求,但足够一个以上的节点nobjs = bytes_left/size; // 能带回的节点个数total_bytes = size * nobjs; // 申请到的字节数result = start_free;start_free += total_bytes; // 调整 startreturn(result);} else {// 内存池剩余空间连一个节点也不能满足// 申请成功后后取走 total_bytes 字节,剩余部分留在内存池size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);if (bytes_left > 0) {// 内存池中还有剩余内存,把它分配到合适的自由链表中// 申请是 8 的倍数,使用也是 8 的倍数,因此可以找到合适的位置obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);((obj *)start_free) -> free_list_link = *my_free_list;*my_free_list = (obj *)start_free;}// 调用 malloc 向堆申请内存start_free = (char *)malloc(bytes_to_get);if (0 == start_free) {// 申请内存失败int i;obj * volatile * my_free_list, *p;for (i = size; i <= __MAX_BYTES; i += __ALIGN) {// 检查配有更大节点的自由链表是否有空间可用// 例如 32 字节自由链表没有内存可用,可以去 40、48 等自由链表查看my_free_list = free_list + FREELIST_INDEX(i);p = *my_free_list;if (0 != p) {// 更大的自由链表中有内存可用*my_free_list = p -> free_list_link; // 弹出一个节点start_free = (char *)p; // 将弹出的节点放入内存池中end_free = start_free + i; // 调整内存池大小return(chunk_alloc(size, nobjs)); // 此时已经有内存了,递归调用自己获取}}// 此时内存池没有内存,malloc 失败,也没有更大的可用节点// 尝试调用一级配置器,看内存不足处理函数是否有办法end_free = 0;start_free = (char *)malloc_alloc::allocate(bytes_to_get);} // end of if (0 == start_free)heap_size += bytes_to_get; // 随着次数而增大,不太理解含义end_free = start_free + bytes_to_get; // 调整内存池大小return(chunk_alloc(size, nobjs)); // 此时已经有内存了,递归调用自己获取} // end of if (bytes_left >= total_bytes)
}
相关文章:

SGI 空间配置器
前言 空间配置器是 STL 六大组件之一,它总是隐藏在容器的背后,默默工作,默默付出。本文为《STL 源码剖析》读书笔记,主要讨论 SGI 版本空间的配置和释放,对代码进行解读时会改变一些写法,使其更易于阅读。…...

2023年白酒行业研究报告
第一章 行业概况 白酒是中国传统的酿酒业之一,历史悠久,源远流长。白酒指以高粱等粮谷为主要原料,以大曲、小曲或麸曲及酒母等为糖化发酵剂,经蒸煮、糖化、发酵、蒸馏、陈酿、勾兑而制成的,酒精度(体积分数)在18%-68%…...
华为OD机试 -合规数组(Java) | 机试题+算法思路+考点+代码解析 【2023】
合规数组 题目 给定一个正整数数组 检查数组中是否存在满足规则的数组组合 规则: A = B + 2C 输入 第一行输出数组的元素个数 接下来一行输出所有数组元素,用空格隔开 输出 如果存在满足要求的数 在同一行里依次输出规则里A B C的取值,用空格隔开 如果不存在输出0 示…...
华为OD机试真题Python实现【英文输入法】真题+解题思路+代码(20222023)
英文输入法 主管期望你来实现英文输入法单词联想功能,需求如下: 依据用户输入的单词前缀,从已输入的英文语句中联想出用户想输入的单词。按字典序输出联想到的单词序列,如果联想不到,请输出用户输入的单词前缀。注意 英文单词联想时区分大小写缩略形式如"don’t&quo…...

改进YOLO系列 | 添加轻量化Decouple_Head 和 ASFF_Head
绿色为ASFF_Head,浅蓝色Decoupled_Head,深蓝色是第三步加的_initialize_dh_biases方法后的效果。 参数量与计算量对比 模型参数量 parameters计算量GFLOPsyolov5s_Head723538916.5ASFF_Head1267484725.0Decoupled_Head892869722.0结构图 本篇介绍的这个Decouple_Head和YOLOX…...

LLFlow沦为和代码解读
LLFlow沦为和代码解读 1.测试时代码的运行位置 sr:什么意思 sr 和 z 这里又将模型设置为了训练模式 所以下面这部分代码应该是测试时运行的所有代码 这个就是测试时使用的网络模型框架 下面应该就是self.netG的网络模型框架 但是这个z的网络模型框架代码还不…...
C语言学习及复习笔记-【9】数组
目录9. 数组9.1 数组的定义9.2 数组与指针的区别9.3 数组大小计算9.4 数组的赋值9.5多维数组寻址方式9.6 函数数组9. 数组 9.1 数组的定义 C 语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往…...

Kubernetes入门教程 --- 使用kubeadm进行集群安装
Kubernetes入门教程 --- 使用kubeadm和二进制安装1. Introduction1.1 架构图1.2 关键字介绍1.3 简述2. 使用Kubeadm Install2.1 申请三个虚拟环境2.2 准备安装环境2.3 配置yum源2.4 安装Docker2.5 时间同步2.6 安装组件2.7 部署集群2.8 Master安装网络插卡3. 查询状态3.1 查询n…...
华为OD机试真题Python实现【相对开音节】真题+解题思路+代码(20222023)
相对开音节 题目 相对开音节构成的结构为辅音+元音(aeiou)+辅音(r除外) 常见的单词有bike cake 给定一个字符串,以空格为分隔符 反转每个单词的字母 若单词中包含如数字等其他非字母时不进行反转 反转后计算其中含有相对开音节结构的子串个数 (连续子串中部分字符可以重复) …...

海思SD3403/SS928V100开发(5)MIPI_YUV相机vio sample开发----修改思路
1. 前言 sensor输出格式: YUV422 8bit 硬件连接: MIPI_YUV相机(4lane MIPI) -> SS928V100 MIPI0(4lane) 框图: 2. 几个问题 基于SS928 SDK中的 vio sample修改; 但是sample里面都是基于RAW RGB sensor开发的sample, 没有现成的MIPI_YUV sensor的参考,需要自己…...

javaee之node.js与es6
问题1:在IDEA控制台为什么node显示不会出来命令 修改完之后记得重新启动电脑 问题2:response.end()作用 在Web开发中,浏览器端的请求到达服务器进行处理的时候,Response.End的作用就是让request执行到此结束,输出到客户…...

11 nacos源码开篇
nacos核心功能点: 服务注册:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存…...

13.Jenkins集成sonarqube
Jenkins集成sonarqube1.jenkins集成sonarqube2.jenkins以maven的方式集成sonarqube scanner报错1报错21.jenkins集成sonarqube 1.在jenkins插件管理中安装sonarqube scanner 2.jenkins要知道sonarqube服务端是谁,需要配置(系统管理–系统配置–sonarq…...

JavaWeb13-线程休眠和指定唤醒:LockSupport
目录 1.LockSupport.park():休眠当前线程 2.LockSupport.unpark(线程对象):唤醒某一个指定的线程 3.扩展:LockSupport.parkUntil(long)等待最大时间是一个固定时间 4.LockSupport和Interrupt 5.LockSupport VS wait 相同点:…...

【第一章:Spring概述、特点、IOC容器、IOC操作bean管理(基于xml方式创建对象,注入属性)】
第一章:Spring概述、特点、IOC容器、IOC操作bean管理(基于xml方式创建对象,注入属性) 1.Spring是什么? ①Spring是一款主流的java EE 轻量级开源框架。 ②广义的Spring:Spring技术栈,Spring不再…...
前端react面试题(边面边更)
展示组件(Presentational component)和容器组件(Container component)之间有何不同 展示组件关心组件看起来是什么。展示专门通过 props 接受数据和回调,并且几乎不会有自身的状态,但当展示组件拥有自身的状态时,通常也只关心 UI 状态而不是…...
产品3C认证在哪里办理
一、什么是CCC认证 3C认证的全称为“中国强制性产品认证”,英文名称China Compulsory Certification,英文缩写CCC。它是中国政府为保护消费者人身安全和国家安全、加强产品质量管理、依照法律法规实施的一种产品合格评定制度。年12月,国家…...

UA-DETRAC数据集转YOLO格式
一: 数据集下载 原官方数据集 链接:https://pan.baidu.com/s/1P_CeSIpJIYSA1dykmFhgYw 提取码: 7f4g 处理完成数据集(每10帧取一张) 链接:后续添加 提取码:后续添加 二: 处理标…...

代码随想录---二叉树的总结和二叉树的定义
二叉树的种类: 满二叉树:树的所有节点都是满,即都有左右孩子。 这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。 完全二叉树:完全二叉树的定义如下:在完全二叉树中,…...
Hive SQL 执行计划
我们在写Hive SQL的时候,难免会在运行的时候有报错,所以知道Hive SQL的执行计划具体是什么,然后假如在之后的运行过程中有报错,可以根据执行计划定位问题,调试自己的SQL开发脚本。 一、含义 Hive SQL的执行计划描述S…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...