Linux组件之内存池的实现

news2024/11/27 4:00:28

文章目录

  • 一、为什么需要内存池
  • 二、内存池的工作流程
  • 三、内存池的实现
    • 3.1 数据结构
    • 3.2 接口设计
      • 3.2.1 创建内存池
      • 3.2.2 内存池销毁
      • 3.2.3 内存分配
        • 1. 分配小块内存
        • 2. 分配大块内存
      • 3.2.4 内存池的释放
      • 3.2.5 内存池重置
    • 3.3 完整代码

一、为什么需要内存池

应用程序使用内存,需要先申请内存,使用完之后再释放内存。比如c语言中使用malloc/free来分配/释放内存,c++中使用new/delete来分配/释放内存。内存申请与释放都需要与操作系统进行交互,具体来说就是系统维护一个内存链表,当有一个内存申请过来时,根据分配算法在链表找一个合适的内存分配给它。分配的内存块可能会大于所申请的内存大小,这样需要进行切割,将剩余的内存插入到空闲链表中。释放的时候,系统要判断释放的内存块的前后是否有空闲,若有的话还要进行合并。可见,调用库中的内存分配函数,十分耗时。

另外,频繁的申请、释放内存,会使得一块大内存可能被分割成多块不连续的小内存。当需要再分配连续的大内存时候,尽管剩余内存的总和可能大于所要分配的内存大小,但系统就找不到连续的内存。这就是内存碎片的存在,从而导致分配错误。

综上所述,总结一下使用内存池的几个主要原因:

  • 1)提高内存分配效率:传统的内存分配方式会涉及到频繁的系统调用和内存管理开销,而内存池可以在初始化时一次性地分配一块连续的内存空间,然后根据需要从该池中快速分配内存,避免了频繁的系统调用,从而提高了内存分配的效率。
  • 2)降低内存碎片化:内存池会事先分配一定数量的连续内存块,当需要分配内存时,直接从这些预分配的内存块中获取,不涉及内存的分配和释放,减少了内存碎片的产生,提高了内存的利用率。
  • 3)减少动态内存分配次数:内存池可以在程序初始化的时候一次性分配好所需的内存,然后在运行时通过内存池进行内存分配,避免了频繁的动态内存分配,从而减少了内存管理的开销和潜在的内存泄漏风险。
  • 4)控制内存分配的策略:内存池可以自定义内存分配的策略,例如按照固定大小的内存块进行分配,或者按需动态调整内存池的大小等。这可以根据具体应用场景和需求来选择最合适的内存管理策略。

总之,内存池是一种有效管理内存的机制,通过提高内存分配和释放的效率,减少内存碎片化,降低动态内存分配的次数,能够帮助提高程序的性能和可靠性。

二、内存池的工作流程

1)开始时,申请一块大的内存(在该块内存不够用时在二次分配),
2)需要时,从这块内存中取出,并标记下这块内存被用了
3)释放时,标记此内存被释放了。注意此时并不真的把内存释放给操作系统,而是当一大块内存都空闲的时候,才释放给操作系统。
4)调用内存分配函数的时,小块内存每次都分配固定大小的内存块,这样就避免了内存碎片产生的可能。

参考 nginx 内存池的设计,以大、小块作为内存管理分配的方式。
具体来说,内存池中分大小块,申请内存大小大于某个值定为大块、否则是小块,内部使用链表串联。

在这里插入图片描述

三、内存池的实现

3.1 数据结构

大块内存结构

struct mp_large_s {
	struct mp_large_s *next;	// 指向下一个大块内存
	void *alloc;				// 指向实际分配的大块内存
};

小块内存结构

struct mp_node_s {

	unsigned char *last;		// 指向内存池中已分配结点的末尾,即下次可分配内存结点的首地址
	unsigned char *end;			// 指向内存池的末尾
	
	struct mp_node_s *next;		// 指向下一个结点
	size_t failed;				// 当前的内存池分配失败的次数
};

内存池结构

struct mp_pool_s {

	size_t max;					// 内存池可分配的最大空间,超过的话用大块内存

	struct mp_node_s *current;	// 指向当前内存池
	struct mp_large_s *large;	// 指向大块内存链表

	struct mp_node_s head[0];	// 指向小块内存链表

};

在这里插入图片描述

3.2 接口设计

3.2.1 创建内存池

内存池创建的时候分三块,一块存储mp_pool_s结构体,一块存储mp_node_s结构体,一块为申请的内存块。返回指向整个内存池的指针p。
在这里插入图片描述

struct mp_pool_s *mp_create_pool(size_t size) {

	struct mp_pool_s *p;
	// posix_memalign 用于分配内存块,并保证所分配的内存块以指定的对齐方式进行对齐。
	// 参数:1)一个指向指针的指针,用于保存分配得到的内存块的地址。2)所需的内存对齐方式. 3)要分配的内存块的大小
	// 一开始并不知道大块内存具体大小,因此只需要分配:申请空间的大小 + 内存池结构体大小 + 小块内存结点
	int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
	if (ret) {
		return NULL;
	}
	
	p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;	// 设置小块内存可分配的最大空间
	p->current = p->head; 														// 设置当前指向的内存块
	p->large = NULL;															// 设置大块内存

	p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
	p->head->end = p->head->last + size;
	p->head->failed = 0;

	return p;

}

3.2.2 内存池销毁

void mp_destory_pool(struct mp_pool_s *pool) {

	struct mp_node_s *h, *n;
	struct mp_large_s *l;

	// 释放大块内存
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	// 释放小块内存
	h = pool->head->next;
	while (h) {
		n = h->next;
		free(h);
		h = n;
	}

	// 释放内存池
	free(pool);

}

3.2.3 内存分配

通过mp_alloc向内存池申请内存,如果用户申请的内存大于pool->max,则认为是大块内存,直接向 OS 申请。否则就是小块内存,直接在内存池里面分配。

如果申请小块内存,遍历现有的小块内存链表,寻找是否有满足需求的内存块
1)如果有,返回待分配内存块的首地址
2)如果没有,创新新的小块内存

void *mp_alloc(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *p;

	// 1、申请小块内存
	if (size <= pool->max) {

		p = pool->current;

		do {
			// 遍历小块内存链表,寻找可用的空间分配内存
			m = mp_align_ptr(p->last, MP_ALIGNMENT);
			// 若当前结点的剩余空间足够分配
			if ((size_t)(p->end - m) >= size) {
				p->last = m + size;
				return m;
			}
			// 若当前内存块的剩余内存小于所需内存,则到下一个内存块中寻找
			p = p->next;
		} while (p);

		// 没找到合适的小块内存,则申请小块内存
		return mp_alloc_block(pool, size);
	}

	// 2、申请大块内存
	return mp_alloc_large(pool, size);
	
}

1. 分配小块内存

调用mp_alloc_block创建新的小块内存,并插入到小块内存链表的末尾。

有一种情况,每个小块内存最后都剩下一小块,比如A内存块剩下5,B内存块剩下16。current指向的是A。此时需要申请大小20的内存,从current开始查,A不满足。再申请大小10的内存,从current开始查,A还是不满足。每次都从A开始,就很费时间,因此引入failed
每次调用mp_alloc_block函数,代表内存池现有小块内存的空间分配失败,此时,所有可用的小块内存的 failed + 1,表示不满足用户的需求增加1次。若某个小块内存若连续 5 次不满足用户需求,则不再使用它,遍历时跳过。
在这里插入图片描述

static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *h = pool->head;
	// 第一个小块内存中,可分配的内存大小
	size_t psize = (size_t)(h->end - (unsigned char *)h);
	
	// 申请内存,大小与第一个小块内存一样
	int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
	if (ret) return NULL;

	struct mp_node_s *p, *new_node, *current;

	// 初始化新的内存块
	new_node = (struct mp_node_s*)m;
	new_node->end = m + psize;
	new_node->next = NULL;
	new_node->failed = 0;

	// 将指针m移动到可分配内存的开始位置
	m += sizeof(struct mp_node_s);
	m = mp_align_ptr(m, MP_ALIGNMENT);
	new_node->last = m + size;

	// 从当前指向的内存块开始,寻找最后一个内存块链表的结点
	current = pool->current;
	for (p = current; p->next; p = p->next) {
		// 若某个小块内存连续5次都分配失败,则跳过这个小块内存,下次不再遍历它
		if (p->failed++ > 4) { //
			current = p->next;
		}
	}

	// 将新创建的内存块,尾插到小内存块链表
	p->next = new_node;

	// 更新pool->current指针,判断 current 是否为空?
	// 若非空,指向current指向的结点;若为空,代表之前所有的内存块都分配失败,则指向新的内存块
	pool->current = current ? current : new_node;

	return m;

}

2. 分配大块内存

调用mp_alloc_large()分配一个新的大块内存,插入到链表的开头,头插法。

static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {

	void *p = malloc(size);
	if (p == NULL) return NULL;

	size_t n = 0;
	struct mp_large_s *large;

	// 遍历大块内存链表,找到可以挂载 p 的位置
	for (large = pool->large; large; large = large->next) {
		// 找到可以挂载的地方
		if (large->alloc == NULL) {
			large->alloc = p;
			return p;
		}
		// 若连续 4 次都没找到,就不再寻找了
		if (n ++ > 3) break;
	}

	// 创建一个新的大块内存结点,用来挂载p
	large = mp_alloc(pool, sizeof(struct mp_large_s));
	if (large == NULL) {
		free(p);
		return NULL;
	}

	// 将新创建的结点,头插到大块内存链表中
	large->alloc = p;
	large->next = pool->large;
	pool->large = large;

	return p;
}

3.2.4 内存池的释放

Nginx 内存池内部仅提供大块内存的释放接口

void mp_free(struct mp_pool_s *pool, void *p) {

	struct mp_large_s *l;
	for (l = pool->large; l; l = l->next) {
		if (p == l->alloc) {
			free(l->alloc);
			l->alloc = NULL;

			return ;
		}
	}
	
}

3.2.5 内存池重置

nginx的小块内存是无法释放的,因为这个小块内存的分配都是通过last指针偏移的。
如果1和3分配出去了。但现在2不使用了,怎么归还到小块内存里?mp_node_s 结构通过指针last和end标识空闲内存,因此不能把2这块内存给拉进去空闲内存空间里面。
在这里插入图片描述
但是如果不释放小块内存,小块内存随着分配越来越大,系统的内存分配失败的概率越大。因此引入内存池重置。

当一个内存池被使用过一段时间后,可能会有一些内存块被分配出去,但是在后续的使用中,这些内存块可能已经不再需要了。此时,可以选择重置内存池,将所有已经分配的内存块释放回来,并重新初始化内存池的状态,使其恢复到最初的状态,以便重新利用这些内存空间。

void mp_reset_pool(struct mp_pool_s *pool) {

	struct mp_node_s *h;
	struct mp_large_s *l;

	// 释放大块内存
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	pool->large = NULL;

	// 通过指针复位重置小块内存(不调用free归还内存)
	for (h = pool->head; h; h = h->next) {
		h->last = (unsigned char *)h + sizeof(struct mp_node_s);
	}

}

3.3 完整代码




#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <fcntl.h>



#define MP_ALIGNMENT       		32
#define MP_PAGE_SIZE			4096
#define MP_MAX_ALLOC_FROM_POOL	(MP_PAGE_SIZE-1)

// 按指定的对齐方式 alignment 对齐。
#define mp_align(n, alignment) (((n)+(alignment-1)) & ~(alignment-1))
#define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))


// 大块内存结构
struct mp_large_s {
	struct mp_large_s *next;	// 指向下一个大块内存
	void *alloc;				// 指向实际分配的大块内存
};

// 小块内存结构
struct mp_node_s {

	unsigned char *last;		// 指向内存池中已分配结点的末尾,即下次可分配内存结点的首地址
	unsigned char *end;			// 指向内存池的末尾
	
	struct mp_node_s *next;		// 指向下一个结点
	size_t failed;				// 当前的内存池分配失败的次数
};

// 内存池结构
struct mp_pool_s {

	size_t max;					// 内存池可分配的最大空间,超过的话用大块内存

	struct mp_node_s *current;	// 指向当前内存池
	struct mp_large_s *large;	// 指向大块内存链表

	struct mp_node_s head[0];	// 指向小块内存链表

};

struct mp_pool_s *mp_create_pool(size_t size);
void mp_destory_pool(struct mp_pool_s *pool);
void *mp_alloc(struct mp_pool_s *pool, size_t size);
void *mp_nalloc(struct mp_pool_s *pool, size_t size);
void *mp_calloc(struct mp_pool_s *pool, size_t size);
void mp_free(struct mp_pool_s *pool, void *p);

// 创建内存池
struct mp_pool_s *mp_create_pool(size_t size) {

	struct mp_pool_s *p;
	// posix_memalign 用于分配内存块,并保证所分配的内存块以指定的对齐方式进行对齐。
	// 参数:1)一个指向指针的指针,用于保存分配得到的内存块的地址。2)所需的内存对齐方式. 3)要分配的内存块的大小
	// 一开始并不知道大块内存具体大小,因此只需要分配:申请空间的大小 + 内存池结构体大小 + 小块内存结点
	int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
	if (ret) {
		return NULL;
	}
	
	p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;	// 设置小块内存可分配的最大空间
	p->current = p->head; 														// 设置当前指向的内存块
	p->large = NULL;															// 设置大块内存

	p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
	p->head->end = p->head->last + size;
	p->head->failed = 0;

	return p;

}

// 内存池销毁
void mp_destory_pool(struct mp_pool_s *pool) {

	struct mp_node_s *h, *n;
	struct mp_large_s *l;

	// 释放大块内存
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	// 释放小块内存
	h = pool->head->next;
	while (h) {
		n = h->next;
		free(h);
		h = n;
	}

	// 释放内存池
	free(pool);

}

// 重置内存池
void mp_reset_pool(struct mp_pool_s *pool) {

	struct mp_node_s *h;
	struct mp_large_s *l;

	// 释放大块内存
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	pool->large = NULL;

	// 通过指针复位重置小块内存(不调用free归还内存)
	for (h = pool->head; h; h = h->next) {
		h->last = (unsigned char *)h + sizeof(struct mp_node_s);
	}

}

// 分配小块内存
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *h = pool->head;
	// 第一个小块内存中,可分配的内存大小
	size_t psize = (size_t)(h->end - (unsigned char *)h);
	
	// 申请内存,大小与第一个小块内存一样
	int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
	if (ret) return NULL;

	struct mp_node_s *p, *new_node, *current;

	// 初始化新的内存块
	new_node = (struct mp_node_s*)m;
	new_node->end = m + psize;
	new_node->next = NULL;
	new_node->failed = 0;

	// 将指针m移动到可分配内存的开始位置
	m += sizeof(struct mp_node_s);
	m = mp_align_ptr(m, MP_ALIGNMENT);
	new_node->last = m + size;

	// 从当前指向的内存块开始,寻找最后一个内存块链表的结点
	current = pool->current;
	for (p = current; p->next; p = p->next) {
		// 若某个小块内存连续5次都分配失败,则跳过这个小块内存,下次不再遍历它
		if (p->failed++ > 4) { //
			current = p->next;
		}
	}

	// 将新创建的内存块,尾插到小内存块链表
	p->next = new_node;

	// 更新pool->current指针,判断 current 是否为空?
	// 若非空,指向current指向的结点;若为空,代表之前所有的内存块都分配失败,则指向新的内存块
	pool->current = current ? current : new_node;

	return m;

}

// 分配大块内存
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {

	void *p = malloc(size);
	if (p == NULL) return NULL;

	size_t n = 0;
	struct mp_large_s *large;

	// 遍历大块内存链表,找到可以挂载 p 的位置
	for (large = pool->large; large; large = large->next) {
		// 找到可以挂载的地方
		if (large->alloc == NULL) {
			large->alloc = p;
			return p;
		}
		// 若连续 4 次都没找到,就不再寻找了
		if (n ++ > 3) break;
	}

	// 创建一个新的大块内存结点,用来挂载p
	large = mp_alloc(pool, sizeof(struct mp_large_s));
	if (large == NULL) {
		free(p);
		return NULL;
	}

	// 将新创建的结点,头插到大块内存链表中
	large->alloc = p;
	large->next = pool->large;
	pool->large = large;

	return p;
}

// 对齐分配内存池
void *mp_memalign(struct mp_pool_s *pool, size_t size, size_t alignment) {

	void *p;
	
	int ret = posix_memalign(&p, alignment, size);
	if (ret) {
		return NULL;
	}

	struct mp_large_s *large = mp_alloc(pool, sizeof(struct mp_large_s));
	if (large == NULL) {
		free(p);
		return NULL;
	}

	large->alloc = p;
	large->next = pool->large;
	pool->large = large;

	return p;
}


// 内存分配
void *mp_alloc(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *p;

	// 1、申请小块内存
	if (size <= pool->max) {

		p = pool->current;

		do {
			// 遍历小块内存链表,寻找可用的空间分配内存
			m = mp_align_ptr(p->last, MP_ALIGNMENT);
			// 若当前结点的剩余空间足够分配
			if ((size_t)(p->end - m) >= size) {
				p->last = m + size;
				return m;
			}
			// 若当前内存块的剩余内存小于所需内存,则到下一个内存块中寻找
			p = p->next;
		} while (p);

		// 没找到合适的小块内存,则申请小块内存
		return mp_alloc_block(pool, size);
	}

	// 2、申请大块内存
	return mp_alloc_large(pool, size);
	
}

// 非对齐分配内存池
void *mp_nalloc(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *p;

	if (size <= pool->max) {
		p = pool->current;

		do {
			m = p->last;
			if ((size_t)(p->end - m) >= size) {
				p->last = m+size;
				return m;
			}
			p = p->next;
		} while (p);

		return mp_alloc_block(pool, size);
	}

	return mp_alloc_large(pool, size);
	
}

// 分配并清零内存空间。
void *mp_calloc(struct mp_pool_s *pool, size_t size) {
	// 在给定的内存池中进行内存分配
	void *p = mp_alloc(pool, size);
	if (p) {
		//将分配到的内存块清零
		memset(p, 0, size);
	}

	return p;
	
}

// 内存池释放,只针对大块内存
void mp_free(struct mp_pool_s *pool, void *p) {

	struct mp_large_s *l;
	for (l = pool->large; l; l = l->next) {
		if (p == l->alloc) {
			free(l->alloc);
			l->alloc = NULL;
			return ;
		}
	}
	
}


int main(int argc, char *argv[]) {

	int size = 1 << 12;

	struct mp_pool_s *p = mp_create_pool(size);

	int i = 0;
	for (i = 0;i < 10;i ++) {

		void *mp = mp_alloc(p, 512);
	}

	printf("mp_create_pool: %ld\n", p->max);
	printf("mp_align(123, 32): %d, mp_align(17, 32): %d\n", mp_align(24, 32), mp_align(17, 32));

	int j = 0;
	for (i = 0;i < 5;i ++) {

		char *pp = mp_calloc(p, 32);
		for (j = 0;j < 32;j ++) {
			if (pp[j]) {
				printf("calloc wrong\n");
			}
			printf("calloc success\n");
		}
	}

	printf("mp_reset_pool\n");
	for (i = 0;i < 5;i ++) {
		void *l = mp_alloc(p, 8192);
		mp_free(p, l);
	}
	mp_reset_pool(p);

	printf("mp_destory_pool\n");
	for (i = 0;i < 58;i ++) {
		mp_alloc(p, 256);
	}

	mp_destory_pool(p);

	return 0;

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/722272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ArcPy学习心得系列(6)Arcpy计算DOM影像数据范围

需求分析 DOM影像有黑边怎么办? 下图为DOM影像 一张DOM图,问有没有方法能够计算出这张图的有效数据的范围 通过图中显示的范围,可以很明显的看到左边多出来的那一块区域,仔细排查了一遍,辉仔发现原始数据实际上是下面这样的。 原始DOM影像 其中,蓝色区域的值为255,…

Django中如何正确使用 redis

文章目录 问题起源&#xff1a;AsyncWebsocketConsumer 中的 channel_layer解决方案安装 & 启动 redis安装 channel-redis更新 settings.py 的 redis 设置 问题起源&#xff1a;AsyncWebsocketConsumer 中的 channel_layer 在构建 websocket 的过程中&#xff0c;我在 cons…

云原生之深入解析K8S的请求和限制

一、Kubernetes 限制和请求 在 Kubernetes 中使用容器时&#xff0c;了解涉及的资源是什么以及为何需要它们很重要。有些进程比其它进程需要更多的 CPU 或内存&#xff0c;这很关键&#xff0c;永远不应该让进程饥饿&#xff0c;知道了这一点&#xff0c;那么应该正确配置容器…

MQTT 5.0 中的安全认证机制:增强认证介绍

在本系列之前的文章中我们提到&#xff0c;借助 MQTT CONNECT 报文中的 Username 和 Password 字段&#xff0c;我们可以实现一些简单的认证&#xff0c;比如密码认证、Token 认证等。为了进一步保障物联网系统的安全&#xff0c;在本期文章中&#xff0c;我们将一起了解另一种…

tty(四)tty框架分析

基于linux-3.14.16 重要文件&#xff1a;tty_io.c 一、tty子系统 开机添加了2个次设备号为0和1的字符设备&#xff0c;即/dev/tty和/dev/console。 二、分配tty驱动接口alloc_tty_driver 最终调用的__tty_alloc_driver分配 先分配一个tty_driver 因为flags为0&#xff0c…

从编程开发角度比较电机驱动芯片:DRV8833、TB6612、A4950、L298N

这几款驱动芯片都是用于控制直流电机的常见驱动芯片&#xff0c;下面是它们的相同点和不同点的比较&#xff1a; 相同点&#xff1a; 都可以用于控制直流电机的转速和方向。 都支持PWM控制方式&#xff0c;可以实现电机的速度调节。 都提供了使能引脚&#xff0c;可以通过使…

(ARM)7/5

1.串口发送单个字符 2.串口发送字符串 uart4.h #ifndef __UART4_H__ #define __UART4_H__#include "stm32mp1xx_uart.h" #include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h"//初始化相关操作 void hal_uart4_init();//发送一个字符 v…

uniapp学习之【uniapp的返回事件 onBackPress 在微信小程序中不生效的问题】

uniapp 的返回事件 onBackPress 在微信小程序中不生效的问题 场景&#xff1a;页面中点击左上角的返回按钮,监听返回操作,页面返回前执行了一些操作, uniapp 页面生命周期中有 onBackPress ,因此将操作写在了 onBackPress () 页面生命周期钩子当中, H5 测试一切正常,但是微信开…

7、架构:模板设计

在低代码开发中&#xff0c;除了基础组件与物料之外&#xff0c;模板也是必不可少的模块&#xff0c;是基于物料之上的更高级的产物&#xff0c;除了具备业务属性之外会更偏向专属的技术领域&#xff0c;例如可视化大屏、数据分析、中台管理等。 此外模板通常是比较完整的应用…

k8s Label 2

在 k8s 中&#xff0c;我们会轻轻松松的部署几十上百个微服务&#xff0c;这些微服务的版本&#xff0c;副本数的不同进而会带出更多的 pod 这么多的 pod &#xff0c;如何才能高效的将他们组织起来的&#xff0c;如果组织不好便会让管理微服务变得混乱不堪&#xff0c;杂乱无…

作物计数方法汇总

在研究农情的方向中&#xff0c;作物计数是一个很重要的方向&#xff0c;需要用到很多方法&#xff0c;这里做一个小小的总结 (1)地理栅格数据(tif图片)裁剪并生成带地理坐标的切片 如图需要将下图所示的tif图裁剪并生成切片&#xff08;截图一部分&#xff09; 源码来自&…

微服务 02-rabbitmq在springboot中如何使用(上篇)

目录 前言: 上文传送 -> 安装rabbitmq传送门: -> rabbitmq使用出现问题解决传送门: 1. rabbitmq的六大模式: 1.1 简单模式: (一对一) -> 业务场景: 1.2 工作模式: (一对多) -> 业务场景: 1.3 发布与订阅模式: (广播) 1.4 路由模式: -> 业务场景 …

计算机系统的层次结构

计算机系统 计 算 机 系 统 { 计 算 机 软 件 { 系 统 软 件 应 用 软 件 计 算 机 硬 件 { 存 储 器 运 算 器 控 制 器 输 入 设 备 输 出 设 备 计算机系统 \begin{cases} 计算机软件\begin{cases}系统软件\\应用软件\end{cases}\\计算机硬件\begin{cases}存储器\\运算器\…

YoloV8改进---注意力机制:引入瓶颈注意力模块BAM,对标CBAM

目录 ​编辑 1.BAM介绍 2.BAM引入到yolov8 2.1 加入modules.py中&#xff1a; 2.2 加入tasks.py中&#xff1a; 2.3 yolov8_BAM.yaml 1.BAM介绍 论文&#xff1a;https://arxiv.org/pdf/1807.06514.pdf 摘要&#xff1a;提出了一种简单有效的注意力模块&#xff0c;称为瓶颈…

06、Nginx反向代理与负载均衡

反向代理&#xff1a; 这种代理方式叫做&#xff0c;隧道代理。有性能瓶颈&#xff0c;因为所有的数据都经过Nginx&#xff0c;所以Nginx服务器的性能至关重要 负载均衡&#xff1a; 把请求&#xff0c;按照一定算法规则&#xff0c;分配给多台业务服务器&#xff08;即使其中…

【论文笔记】Skill-based Meta Reinforcement Learning

【论文笔记】Skill-based Meta Reinforcement Learning 文章目录 【论文笔记】Skill-based Meta Reinforcement LearningAbstract1 INTRODUCTION2 RELATED WORKMeta-Reinforcement LearningOffline datasetsOffline Meta-RLSkill-based Learning 3 PROBLEM FORMULATION AND PRE…

CPU大小端和网络序的理解

引子 Big/Little Endian是Host CPU如何去理解在内存中的数据&#xff0c;内存中的数据是没有Big/Little Endian之分的&#xff08;内存仅仅作为存储介质&#xff09;&#xff0c;而Host CPU才有Big/Little Endian之分。 不同Endian的CPU&#xff0c;从内存读取数据的时候&#…

Linux进度条

Linux进度条 一.基本概念1.回车和换行2.缓冲区2.实现倒计时 二.进度条1.前置工作2.代码实现 一.基本概念 1.回车和换行 回车&#xff1a;指光标移到该行的起始位置&#xff08;\r&#xff09;。 换行&#xff1a;换到下一行&#xff08;\n&#xff09;。 在c语音里\n将回车和换…

spring.boot 随笔0 springFactoriesInstance入门

0. 其实也没有那么入门 明天还要上班&#xff0c;速度write&#xff0c;直接放一张多样性比较好的 spring.factories 文件(取自 spring-boot-2.3.4.RELEASE.jar) # PropertySource Loaders org.springframework.boot.env.PropertySourceLoader\ org.springframework.boot.env…

知识图谱实战应用19-基于Py2neo的英语单词关联记忆知识图谱项目

大家好,我是微学AI,今天给大家介绍一下知识图谱实战应用19-基于Py2neo的英语单词关联记忆知识图谱项目。基于Py2neo的英语单词关联记忆知识图谱项目可以帮助用户更系统地学习和记忆英语单词,通过图谱的可视化展示和智能推荐功能,提供了一种全新的、更高效的记忆方法,并促进…