C++项目 -- 高并发内存池(二)Thread Cache

C++项目 – 高并发内存池(二)Thread Cache

文章目录

  • C++项目 -- 高并发内存池(二)Thread Cache
  • 一、高并发内存池整体框架设计
  • 二、thread cache设计
    • 1.整体设计
    • 2.thread cache哈希桶映射规则
    • 3.TLS无锁访问
    • 4.thread cache代码


一、高并发内存池整体框架设计

现代很多的开发环境都是多核多线程,在申请内存的场景下,必然存在激烈的锁竞争问题。malloc本身其实已经很优秀,那么我们项目的原型tcmalloc就是在多线程高并发的场景下更胜一筹,所以这次我们实现的内存池需要考虑以下几方面的问题。

  1. 性能问题。
  2. 多线程环境下,锁竞争问题
  3. 内存碎片问题。

concurrent memory pool主要由以下3个部分构成:
在这里插入图片描述

  1. thread cache线程缓存每个线程独有的,用于小于256KB的内存的分配,线程从这里申请内存不需要加锁,每个线程独享一个cache,这也就是这个并发线程池高效的地方。
  2. central cache中心缓存所有线程所共享,thread cache是按需从central cache中获取的对象。central cache合适的时机回收thread cache中的对象,避免一个线程占用了太多的内存,而其他线程的内存吃紧,达到内存分配在多个线程中更均衡的按需调度的目的。central cache是存在竞争的,所以从这里取内存对象是需要加锁,首先这里用的是桶锁,其次只有thread cache的没有内存对象时才会找central cache,所以这里竞争不会很激烈
  3. page cache页缓存在central cache缓存上面的一层缓存,存储的内存是以页为单位存储及分配的,central cache没有内存对象时,从page cache分配出一定数量的page,并切割成定长大小的小块内存,分配给central cache。当一个span的几个跨度页的对象都回收以后,page cache会回收central cache满足条件的span对象,并且合并相邻的页,组成更大的页,缓解内存碎片的问题。

二、thread cache设计

1.整体设计

thread cache是哈希桶结构每个桶是一个按桶位置映射大小的内存块对象的自由链表。每个线程都会有一个thread cache对象,这样每个线程在这里获取对象和释放对象时是无锁的。

  • 类比定长内存池,一大块内存用来分配空间,freeList用于管理已经分配好的定长内存块
  • thread cache的freeList可以设计多个大小的定长list,如下图,8字节、16字节…;对象小于等于8字节的由8字节的freelist管理,9到16字节的由16字节的freeList管理;
    在这里插入图片描述
  • 但这样设计会造成空间浪费,比如一个对象大小为6字节,为它分配了8字节的空间,那么这剩下的2字节就会成为碎片,这叫做内碎片
  • thread cache整体是一个哈希桶结构,将对象的大小映射到对应大小的freeList中进行管理;

申请内存:

  • 当内存申请size<=256KB,先获取到线程本地存储的thread cache对象,计算size映射的哈希桶自由链表下标i。
  • 如果自由链表_freeLists[i]中有对象,则直接Pop一个内存对象返回。
  • 如果_freeLists[i]中没有对象时,则批量从central cache中获取一定数量的对象,插入到自由链表并返回一个对象。

释放内存:

  • 当释放内存小于256k时将内存释放回thread cache,计算size映射自由链表桶位置i,将对象Push到_freeLists[i]。
  • 当链表的长度过长,则回收一部分内存对象到central cache。

2.thread cache哈希桶映射规则

  • thread cache最大支持一个对象申请256KB的内存空间,则对象的大小范围是1 ~ 256KB,如果以8字节为对齐数,指定freeList,就是1 ~ 8字节大小的对象按照8字节对齐,分配8字节空间,连接到第一个freeList后面;9 ~ 16字节大小的对象分配16字节空间,连接到第二个freeList后面,这就是内存对齐
  • 每一个对齐的freeList都是一个哈希桶,这就是哈希映射
    在这里插入图片描述
  • 如果将所有的哈希桶对齐规则都为8字节,则一共需要32768个哈希桶,数量太多;

因此我们需要制定一个对齐规则:
在这里插入图片描述
分段安排对齐数的大小;

  • 对象大小在1 ~ 128字节之间的,按照8字节对齐,即每个哈希桶大小增长8字节;
  • 那么8字节对齐的哈希桶就有16个,即对应freeLists[0] - freeList[15];
    第一个桶链接对象大小在1 ~ 8字节之间的,第二个链接9 ~ 16字节之间,依次类推;
  • 对象大小在129 - 1024之间的,按照16字节对齐,即每个哈希桶大小增长16字节,以此类推;
  • 最终分配下来,8字节对齐的哈希桶共16个,16字节对齐的哈希桶共56个,128字节对齐的哈希桶共56个,1024字节对齐的哈希桶共56个,8KB字节对齐的哈希桶共56个,所有的哈希桶加起来一共208个,也就是说一共有208个freeList;
  • 根据上面的对齐规则可以将对象的size按照对齐数进行对齐,对齐的逻辑:
    在这里插入图片描述
    如果size不是对齐数alignNum的倍数,就需要根据对齐数调整最终分配空间的大小,否则size就是最终分配空间的大小;
  • 上面的计算过程可以使用下面的位运算进行代替,因为这段代码会被频繁调用,位运算的效率更高:
    在这里插入图片描述

这样分配的好处:

  • 能够减少哈希桶的数量
  • 能够将内碎片浪费控制在10%左右
    • 以16字节对齐为例,16字节对齐数能浪费的空间最大为15字节;如果一个对象分配到了129字节的内存,其对应的对齐数是16,则最终系统会为该对象分配145字节的空间,那么就有15字节的空间浪费,则内碎片为15字节,浪费率 = 15 / 145 = 0.1034 ,后面128字节等的对齐规则类似
    • 前面8字节对齐的部分可能不止10%,但是从16字节开始就能够控制在10%左右;

在制定好字节对齐规则后,还需要制订哈希映射规则,将不同大小的对象映射到对应的freeList中:

  • 根据字节对齐规则,1 ~ 128字节大小的对象,映射在8字节对齐的哈希桶,其映射逻辑如下:
    在这里插入图片描述
    129 ~ 1024字节大小的对象也是这样的规则;
  • 上面的代码可以使用位运算代替,因为这段代码会被频繁调用,位运算的效率更高:
    在这里插入图片描述

3.TLS无锁访问

在多线程环境下,ThreadCache的创建和访问会涉及到锁的问题,我们希望每个线程都有独立的ThreadCache,并且访问自己的ThreadCache都无须加锁,这样就需要使用TLS;
线程局部存储(TLS),是一种变量的存储方法,这个变量在它所在的线程内是全局可访问的,但是不能被其他线程访问到,这样就保持了数据的线程独立性。而熟知的全局变量,是所有线程都可以访问的,这样就不可避免需要锁来控制,增加了控制成本和代码复杂度。

Linux下TLS
win下TLS

TLS —— thread local storage:线程本地存储,我们这里使用静态的TLS:
声明以下代码:

_declspec(thread) DWORD data=0;

声明了_declspec(thread)的变量,会为每一个线程创建一个单独的拷贝。

  • 静态TLS的原理
    在×86CPU上,将为每次引用的静态TLS变量生成3个辅助机器指令。如果在进程中创建了另一个线程,那么系统就要将它捕获并且自动分配另一个内存块,以便存放新线程的静态TLS变量。新线程只拥有对它自己的静态TLS变量的访问权,不能访问属于其他线程的TLS变量。

ThreadCache.h
在这里插入图片描述

  • 在该头文件下定义一个全局的ThreadCache*类型的静态指针pTLSThreadCache使用_declspec(thread)声明后,每一个线程都会为该指针创建一个单独的拷贝,本线程在访问pTLSThreadCache指针时是能够全局访问的,但是其他线程不能访问本线程的pTLSThreadCache指针,这样就实现了多线程环境下的无锁访问;
  • 其实就是每一个线程都有一个独立的pTLSThreadCache,线程之间访问互不干扰

ConcurrentAlloc.h
在这里插入图片描述

  • 上面的代码只是声明了TLS的指针,并没有指向实际的ThreadCache对象,实际上每个线程在运行的时候,都需要将TLS指针指向自己的ThreadCache对象;
  • 这个头文件是对多线程环境下ThreadCache申请和释放内存的功能进行了封装,保证每个线程的ThreadCache都是本线程独有的,
  • ConcurrentAlloc函数会检测pTLSThreadCache是否为空,如果为空,证明初次调用,就需要构建一个新的ThreadCache对象,并将pTLSThreadCache指针指向该对象,这样本线程独有的ThreadCache对象就创建好了,再通过pTLSThreadCache指针去调用Allocate函数开辟空间;

4.thread cache代码

Common.h

  • 公共的头文件,共有部分的代码可以写在这里面;
  • NextObj函数用于获取当前obj对象指向的下一个对象的指针:
  • 将自由链表定义为一个类FreeList,实现链表的基本操作
  • 定义一个管理字节对齐和哈希映射规则的类SizeClass
    • 类中的所有成员函数都定义为静态的内联函数,方便外部直接调用;
    • RoundUp是用来计算当前对象size字节对齐之后对应的size,先判断对象的size在哪个对齐区间,再根据对齐数来计算对齐后的size(调用子函数_RoundUp
    • Index函数用来计算对象size映射到哪一个哈希桶(freelist),根据对象size所属的对齐区间和对齐数,调用_Index函数计算该对象映射到的哈希桶下标;
      注意从第二个对齐区间开始,由于前面部分的对齐数是不同的,因此在计算下标的时候,先要用size减去前面不同对齐数的区间,带入_Index函数计算该对象在当前区间内的相对下标,最后再加上前面所有的哈希桶个数,得到最终的下标;
      例如:如果映射到的是16字节对齐的区域,先要用分配空间减去128,因为前面的128字节是8字节对齐的,要减去,剩下的按照16字节对齐计算,再加上前面减去的桶的数量,以此类推
#pragma once
//公共头文件

#include <iostream>
#include <vector>
#include <assert.h>
#include <thread>
using std::cout;
using std::endl;
using std::vector;

static const size_t MAX_BYTES = 256 * 1024; //ThreadCache能分配对象的最大字节数
static const size_t NFREELIST = 208; // 最大的哈希桶数量

// 访问obj的前4 / 8字节地址空间
static void*& NextObj(void* obj) {
	return *(void**)obj;
}

//自由链表类,用于管理切分好的小内存块
class FreeList {
public:
	void Push(void* obj) {
		assert(obj);
		//头插
		NextObj(obj) = _freeList;
		_freeList = obj;
	}

	void* Pop() {
		assert(_freeList);

		//头删
		void* obj = _freeList;
		_freeList = NextObj(obj);

		return obj;
	}

	bool Empty() {
		return _freeList == nullptr;
	}
private:
	void* _freeList = nullptr;
};


// 管理对齐和哈希映射规则的类
class SizeClass {
public:
	//对齐规则
	// 整体控制在最多10%左右的内碎片浪费
	// [1,128]				8byte对齐			freelist[0,16)
	// [128+1,1024]			16byte对齐			freelist[16,72)
	// [1024+1,8*1024]		128byte对齐			freelist[72,128)
	// [8*1024+1,64*1024]	1024byte对齐			freelist[128,184)
	// [64*1024+1,256*1024] 8*1024byte对齐		freelist[184,208)

	//RoundUp的子函数,根据对象大小和对齐数,返回对象对齐后的大小
	static inline size_t _RoundUp(size_t size, size_t align) {
		//if (size % align == 0) {
		//	return size;
		//}
		//else {
		//	return (size / align + 1) * align;
		//}

		//使用位运算能够得到一样的结果,但是位运算的效率很高
		return ((size + align - 1) & ~(align - 1));
	}

	//计算当前对象size字节对齐之后对应的size
	static inline size_t RoundUp(size_t size) {
		assert(size <= MAX_BYTES);

		if (size <= 128) {
			//8字节对齐
			_RoundUp(size, 8);
		}
		else if (size <= 1024) {
			//16字节对齐
			_RoundUp(size, 16);
		}
		else if (size <= 8 * 1024) {
			//128字节对齐
			_RoundUp(size, 128);
		}
		else if (size <= 64 * 1024) {
			//1024字节对齐
			_RoundUp(size, 1024);
		}
		else if (size <= 256 * 1024) {
			//8KB字节对齐
			_RoundUp(size, 8 * 1024);
		}
		else {
			assert(false);
		}
		return -1;
	}

	//Index的子函数,用于计算映射的哈希桶下标
	static inline size_t _Index(size_t size, size_t alignShift) {
		//if (size % align == 0) {
		//	return size / align - 1;
		//}
		//else {
		//	return size / align;
		//}

		//使用位运算能够得到一样的结果,但是位运算的效率很高
		//使用位运算需要将输入参数由对齐数改为对齐数是2的几次幂、

		return ((size + (1 << alignShift) - 1) >> alignShift) - 1;
	}

	//计算对象size映射到哪一个哈希桶(freelist)
	static inline size_t Index(size_t size) {
		assert(size <= MAX_BYTES);

		//每个区间有多少个哈希桶
		static int groupArray[4] = { 16, 56, 56, 56 };
		if (size <= 128) {
			return _Index(size, 3);
		}
		else if (size <= 1024) {
			//由于前128字节不是16字节对齐,因此需要减去该部分,单独计算16字节对齐的下标
			//再在最终结果加上全部的8字节对齐哈希桶个数
			return _Index(size - 128, 4) + groupArray[0];
		}
		else if (size <= 8 * 1024) {
			return _Index(size - 1024, 7) + groupArray[0] + groupArray[1];
		}
		else if (size <= 64 * 1024) {
			return _Index(size - 8 * 1024, 10) + groupArray[0] + groupArray[1] + groupArray[2];
		}
		else if (size <= 256 * 1024) {
			return _Index(size - 64 * 1024, 13) + groupArray[0] + groupArray[1] + groupArray[2] + groupArray[3];
		}
		else {
			assert(false);
		}
		return -1;
	}
};

ThreadCache.h

  • 用于声明ThreadCache类的头文件
  • ThreadCache类包括一个FreeList类型的数组,这就是哈希桶的数组,还有完成ThreadCache功能的成员函数的声明;
  • 定义了由_declspec(thread)声明的TLS指针,用于实现无锁访问
#pragma once
#include "Common.h"

class ThreadCache {
public:
	//申请和释放对象内存
	void* Allocate(size_t size);
	void Deallocate(void* obj, size_t size);

	//从中心缓存获取对象
	void* FetchFromCentralCache(size_t index, size_t alignSize);

private:
	FreeList _freeLists[];
};

//声明_declspec(thread)后,会为每一个线程创建一个单独的拷贝
//使用_declspec(thread)声明了ThreadCache*指针变量,则该指针在该线程中会创建一份单独的拷贝
//pTLSThreadCache指向的对象在本线程内是能够全局访问的,但是无法被其他线程访问到,这就做到了多线程情景下的无锁访问
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

ThreadCache.cpp

  • 这个.cpp文件中来实现ThreadCache中的成员函数:
  • Allocate函数为对象申请内存空间
    • 先获取对齐后的size和对应的哈希桶下标
    • 如果该哈希桶的freeList不为空,就Pop一个内存块给该对象,如果为空就需要向CentralCache申请空间
  • Deallocate函数用于归还对象的内存空间
    • 先获取对象对应的freeList的下标
    • 直接将该内存块插入对应的freeList中
  • FetchFromCentralCache用于从中心缓存获取对象空间
#include "ThreadCache.h"

void* ThreadCache::Allocate(size_t size) {
	assert(size <= MAX_BYTES);
	//获取对齐后的大小及对应的哈希桶下标
	size_t alignSize = SizeClass::RoundUp(size);
	size_t index = SizeClass::Index(size);

	if (!_freeLists[index].Empty()) {
		//若对应的freeList桶不为空,直接pop一个内存块给该对象
		return _freeLists[index].Pop();
	}
	else {
		//否则需要从CentralCache获取内存空间
		return ThreadCache::FetchFromCentralCache(index, alignSize);
	}
}

void ThreadCache::Deallocate(void* obj, size_t size) {
	assert(obj);
	assert(size <= MAX_BYTES);

	//找该对象对应的freeList的桶,直接插入
	size_t index = SizeClass::Index(size);
	_freeLists[index].Push(obj);
}

void* ThreadCache::FetchFromCentralCache(size_t index, size_t alignSize) {
	return nullptr;
}

ConcurrentAlloc.h

  • 该头文件用于进一步封装ThreadCache的功能,进而使其能够实现多线程情况下的无锁访问
#pragma once
#include "Common.h"
#include "ThreadCache.h"

static void* ConcurrentAlloc(size_t size) {
	if (pTLSThreadCache == nullptr) {
		//如果pTLSThreadCache指针是空的,就构造一个ThreadCache对象,并指向它
		//则这个ThreadCache对象就是本线程专属的ThreadCache对象
		pTLSThreadCache = new ThreadCache;
	}
	
	//使用pTLSThreadCache访问本线程专属的ThreadCache对象来开辟空间
	return pTLSThreadCache->Allocate(size);
}

static void ConcurrentFree(void* obj, size_t size) {
	assert(pTLSThreadCache);

	pTLSThreadCache->Deallocate(obj, size);
}

UnitTest.cpp

  • 该cpp文件用于测试ThreadCache的功能,重点测试TLS无锁访问的功能
  • 创建两个线程,分别使用ThreadCache申请空间,使用并行监视窗口监视每个进程的内容;
  • c++11的多线程,是将一个线程封装成一个对象
    在这里插入图片描述
    构造thread对象时,传入该线程执行的函数的指针以及参数;
#include "ObjectPool.h"
#include "ConcurrentAlloc.h"
#include "ThreadCache.h"

void Alloc1() {
	for (int i = 0; i < 5; i++) {
		void* obj = ConcurrentAlloc(5);
	}
}

void Alloc2() {
	for (int i = 0; i < 5; i++) {
		void* obj = ConcurrentAlloc(8);
	}
}

void TestTLS() {
	std::thread t1(Alloc1);
	std::thread t2(Alloc2);

	t1.join();
	t2.join();
}


int main() {
	TestTLS();
	return 0;
}

测试结果:
在这里插入图片描述

  • 两个不同的线程都获取和ThreadCache对象,但是通过TLS获取到的是两个不同的ThreadCache,每个线程各一个,两个线程通过pTLSThreadCache指针访问各自的ThreadCache对象

  • 也可以通过输出线程id和pTLSThreadCache指针来观察验证
    在这里插入图片描述
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据分享】1米分辨率土地覆盖数据集SinoLC-1

数据链接 SinoLC-1: the first 1-meter resolution national-scale land-cover map of China created with the deep learning framework and open-access data (Update data: August, 2023) (zenodo.org)https://zenodo.org/records/8214467 数据分享 数据分享到了公众号&…

2024/2/4

一&#xff0e;选择题 1、下列不能作为类的成员的是&#xff08;B&#xff09; A. 自身类对象的指针 B. 自身类对象 C. 自身类对象的引用 D. 另一个类的对象 2、假定AA为一个类&#xff0c;a()为该类公有的函数成员&#xff0c;x为该类的一个对象&#xff0c;则访问x对象中…

你今年过年回去吗?

#过年 我是一名21岁刚毕业的大学生&#xff0c;专业是软件技术&#xff0c;主修c#&#xff0c;之前在上海实习了一年&#xff0c;正式工作后来到了深圳&#xff0c;进入了一家电商公司实习。至于我为什么转行了&#xff0c;大家懂的都懂 现在是20240203晚上19.39&#xff0c;还…

WordPress Plugin HTML5 Video Player SQL注入漏洞复现(CVE-2024-1061)

0x01 产品简介 WordPress和WordPress plugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPress plugin是一个应用插件。 0x02 漏洞概述 WordPress Plugin HTML5 Video Player 插件 get_v…

2024美赛数学建模F题思路源码

赛题目的 赛题目的&#xff1a; 问题描述&#xff1a; 解题的关键&#xff1a; 问题一. 问题分析 问题解答 问题二. 问题分析 问题解答 问题三. 问题分析 问题解答 问题四. 问题分析 问题解答 问题五. 问题分析 问题解答

华为机考入门python3--(8)牛客8-合并表记录

分类&#xff1a;字典排序 知识点&#xff1a; 将输入转成int的列表 my_list list(map(int, input().strip().split( ))) 将列表转为元组 tuple(my_list) 访问元素为元组的列表 for first, second, third in my_list: 对字典进行排序 sorted(my_dict.items())…

微软Azure-OpenAI 测试调用及说明

本文是公司在调研如何集成Azure-openAI时&#xff0c;调试测试用例得出的原文&#xff0c;原文主要基于官方说明文档简要整理实现 本文已假定阅读者申请部署了模型&#xff0c;已获取到所需的密钥和终结点 变量名称值ENDPOINT从 Azure 门户检查资源时&#xff0c;可在“密钥和…

【C语言】static关键字的使用

目录 一、静态本地变量 1.1 静态本地变量的定义 1.2 静态本地变量和非静态本地变量的区别 二、静态函数 2.1 静态函数的定义 2.2 静态函数与非静态函数的区别 三、静态全局变量 3.1 静态全局变量的定义 3.2 静态全局变量和非静态全局变量的区别 四、静态结构体变量 …

【C++入门学习指南】:函数重载提升代码清晰度与灵活性

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; C入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、函数重载1.1 函数重载的概念1.2 函数重载的作用1.3 C支持函数重载的原理1.4 扩展 &…

寒假作业-day3

1>请编程实现双向链表的头插&#xff0c;头删、尾插、尾删 请编程实现双向链表按任意位置插入、删除、修改、查找 代码&#xff1a; #include<stdio.h> #include<stdlib.h> #include<string.h>typedef int datatype; typedef struct Node{datatype data…

PHP入门指南:起步篇

PHP入门指南&#xff1a;起步篇 PHP入门指南&#xff1a;起步篇什么是PHP&#xff1f;PHP 的优点PHP 开发环境搭建选择本地服务器软件包安装PHP环境配置Web服务器和PHP测试PHP安装 第一个PHP脚本PHP基础语法标记注释变量数据类型常量条件语句循环函数 PHP入门指南&#xff1a;起…

python算法与数据结构---动态规划

动态规划 记不住过去的人&#xff0c;注定要重蹈覆辙。 定义 对于一个模型为n的问题&#xff0c;将其分解为k个规模较小的子问题&#xff08;阶段&#xff09;&#xff0c;按顺序求解子问题&#xff0c;前一子问题的解&#xff0c;为后一子问题提供有用的信息。在求解任一子…

【MySQL】- 09 Select Count

【MySQL】- 09 Select Count 1认识COUNT2 COUNT(列名)、COUNT(常量)和COUNT(*)之间的区别3 COUNT(*)的优化 4 COUNT(*)和COUNT(1)5 COUNT(字段)总结 数据库查询相信很多人都不陌生&#xff0c;所有经常有人调侃程序员就是CRUD专员&#xff0c;这所谓的CRUD指的就是数据库的增删…

产业热点 | 从 Vision Pro 发售,洞见空间计算时代新机遇

*图源&#xff1a;Apple 官网 近日首批 Vision Pro 启动预约发售&#xff0c;短短一周就预估售出 20 万台&#xff0c;如今正式发售在即&#xff0c;再度受到各界的热切关注。 *图源&#xff1a;Apple 官网 同样作为空间计算赛道企业&#xff0c;ALVA Systems 在过去十余年始…

IP数据云识别真实IP与虚假流量案例

随着互联网的普及&#xff0c;企业在数字领域面临着越来越复杂的网络威胁。为了保护网站免受虚假流量和恶意攻击的影响&#xff0c;许多企业正在采用IP数据云。本文将结合一个真实案例&#xff0c;深入探讨IP数据云如何成功准确地识别真实用户IP和虚假流量IP&#xff0c;提高网…

ESU毅速丨3D打印技术引领模具制造创新革命

随着科技的飞速发展&#xff0c;3D打印技术已经成为制造业的新宠。而在模具制造领域&#xff0c;3D打印技术更是带来了巨大的创新价值&#xff0c;引领着模具制造的革命性变革。 传统模具制造过程中&#xff0c;需要经过多道繁琐工序&#xff0c;而3D打印技术简化了这一过程。3…

python接口自动化(五)--接口测试用例和接口测试报告模板(详解)

简介 当今社会在测试领域&#xff0c;接口测试已经越来越多的被提及&#xff0c;被重视&#xff0c;而且现在好多招聘信息要对接口测试提出要求。区别于传统意义上的系统级别测试&#xff0c;很多测试人员在接触到接口测试的时候&#xff0c;也许对测试执行还可以比较顺利的上手…

基于场景文字知识挖掘的细粒度图像识别算法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;基于场景文字知识挖掘的细粒度图像识别算法1、研究背景2、方法提出方法模块 3、试验4、文章贡献 二、RNN代码学习2.1、什么是RNN2…

day43_jdbc

今日内容 0 复习昨日 1 SQL注入问题 2 PreparedStatement 3 完成CRUD练习 4 ORM 5 DBUtil (properties) 6 事务操作 0 复习昨日 已经找人提问… 1 SQL注入 1.1 什么是SQL注入 用户输入的数据中有SQL关键词,导致在执行SQL语句时出现一些不正常的情况.这就是SQL注入! 出现SQL注入…

两种方式实现文本超出指定行数显示展开收起...

需要实现这样一个功能 默认高度下文本超出隐藏&#xff0c;点击展开可查看所有内容&#xff0c;点击收起可折叠 方法一&#xff1a;通过html和css实现 代码部分 html:<div className"expand-fold"><input id"check-box" type"checkbox&qu…