【lesson4】高并发内存池ThreadCache(线程缓存)层实现

news2024/9/21 0:28:19

文章目录

  • ThreadCache层的结构
  • 申请内存逻辑
  • 释放内存逻辑
  • 自由链表的实现
    • 自由链表的成员变量
    • 自由链表的成员函数
    • 自由链表的完整实现
  • ThreadCache申请内存过程的实现
    • ThreadCache需要的成员变量
    • ThreadCache需要的成员函数
    • ThreadCache.h文件代码
    • Allocate的实现
    • Deallocate的实现
  • 封装ThreadCache层可以多线程访问
  • ThreadCache层完整代码
    • Common.h
    • ThreadCache.h
    • ThreadCache.cpp
    • ConcurrentAlloc.h

ThreadCache层的结构

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

在这里插入图片描述

申请内存逻辑

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

释放内存逻辑

释放逻辑之后再完整实现,现在先了解释放流程

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

自由链表的实现

从上面我们可以知道,ThreadCache存放内存块需要自由链表,所以我们要先实现自由链表。

自由链表的成员变量

在这里插入图片描述
之前学过定长内存池就知道用一个_freeList指针就能管理好多个内存块。

自由链表的成员函数

在这里插入图片描述
Push:可以让内存块链入自由链表中
Pop:可以将内存块从自由链表中移除(也就是该内存块被申请出去了)
Empty:可以判断该自由链表是否为空。

Push的过程
在这里插入图片描述
Push的实现

void Push(void* obj)
	{
		assert(obj);//断言保证obj不为空

		// 头插
		*(void**)obj = _freeList;
		_freeList = obj;
	}

因为*(void**)obj在后面很多地方都会用到,所以我们对其封装成函数

static void*& NextObj(void* obj)
{
	return *(void**)obj;
}
//这里用static的原因是因为,NextObj是
//被放进行Common.h文件的,而Common.h文件
//肯被对个.cpp文件包含,那么到时编译器就会
//报错
//而static修饰函数表示只在Common.h文件内有效

所以我们Push就可改成

void Push(void* obj)
	{
		assert(obj);//断言保证obj不为空

		// 头插
		//*(void**)obj = _freeList;
		NextObj(obj) = _freeList;
		_freeList = obj;
	}

如果大家不理解*(void**)obj,可以去之前的博客观看,这里不再做讲解。

Pop的过程:
在这里插入图片描述
Pop的实现:

void* Pop()
	{
		assert(_freeList);//_freeList一定不能为空

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

		return obj;
	}

Empty的实现

bool Empty()
	{
		return _freeList == nullptr;
	}

自由链表的完整实现

// 管理切分好的小对象的自由链表
class FreeList
{
public:
	void Push(void* obj)
	{
		assert(obj);

		// 头插
		//*(void**)obj = _freeList;
		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;
};

ThreadCache申请内存过程的实现

首先做项目,我们肯定要定义一个包含头文件的.h文件一般命名为Common.h文件
Common.h

//Common.h
#pragma once
#include <iostream>
#include <assert.h>
using std::cout;
using std::endl;

// thread cache 和 central cache自由链表哈希桶的表大小
static const size_t NFREELISTS = 208;

static void*& NextObj(void* obj)
{
	return *(void**)obj;
}

// 管理切分好的小对象的自由链表
class FreeList
{
public:
	void Push(void* obj)
	{
		assert(obj);

		// 头插
		//*(void**)obj = _freeList;
		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;
};

这里我们先定义这几个,等其它有用到再慢慢添加。
然后实现ThreadCache也要定义两个文件。
一个是ThreadCache.h,ThreadCache.cpp
ThreadCache定义两个文件是为了声明和定义分离,这样更符合实际的项目场景。

ThreadCache.h

//ThreadCache.h
#include "Common.h"

ThreadCache.cpp

//ThreadCache.cpp
#include "ThreadCache.h"

ThreadCache需要的成员变量

在这里插入图片描述
从ThreadCache的结构中我们就可以知道,我们肯定要定义一个指针数组,而每个指针都用来管理内存块,所以这些指针都是自由链表指针

class ThreadCache
{
public:
	
private:
	FreeList _freeLists[NFREELIST];
};

NFREELIST是thread cache自由链表哈希桶的表大小,为208个,所以我们还要Common.h中定义。

ThreadCache需要的成员函数

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

	// 从中心缓存获取对象
	void* FetchFromCentralCache(size_t index, size_t size);
private:
	FreeList _freeLists[NFREELIST];
};

Allocate:申请内存对象
Deallocate:释放内存对象
FetchFromCentralCache:哈希桶如果内存空间不够,向中心缓存(central cache)申请内存空间

ThreadCache.h文件代码

#pragma once

#include "Common.h"

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

	// 从中心缓存获取对象
	void* FetchFromCentralCache(size_t index, size_t size);
private:
	FreeList _freeLists[NFREELIST];
};

那么接下来就是实现这些成员函数了。

Allocate的实现

首先我们要知道申请的空间的字节数大小是多少。
其次我们要对该大小进行对齐,我们用自己设定的对齐规则对原字节数大小对齐
最后我们要根据字节数大小找到对应的哈希桶。

那么这是我们就要设计一个类帮助我们解决这些问题。

//放在common.h中
// 计算对象大小的对齐映射规则
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)

	//方法一:
	/*size_t _RoundUp(size_t size, size_t alignNum)
	{
		size_t alignSize;
		if (size % alignNum != 0)
		{
			alignSize = (size / alignNum + 1)*alignNum;
		}
		else
		{
			alignSize = size;
		}

		return alignSize;
	}*/
	// 1-8 
	//方法二:
	static inline size_t _RoundUp(size_t bytes, size_t alignNum)
	{
		return ((bytes + alignNum - 1) & ~(alignNum - 1));
	}
	
	//字节对齐
	static inline size_t RoundUp(size_t size)
	{
		if (size <= 128)
		{
			return _RoundUp(size, 8);
		}
		else if (size <= 1024)
		{
			return _RoundUp(size, 16);
		}
		else if (size <= 8*1024)
		{
			return _RoundUp(size, 128);
		}
		else if (size <= 64*1024)
		{
			return _RoundUp(size, 1024);
		}
		else if (size <= 256 * 1024)
		{
			return _RoundUp(size, 8*1024);
		}
		else
		{
			assert(false);
			return -1;
		}
	}

	//方法一:
	/*size_t _Index(size_t bytes, size_t alignNum)
	{
	if (bytes % alignNum == 0)
	{
	return bytes / alignNum - 1;
	}
	else
	{
	return bytes / alignNum;
	}
	}*/

	//方法二:
	static inline size_t _Index(size_t bytes, size_t align_shift)
	{
		return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;
	}

	// 计算映射的哪一个自由链表桶
	static inline size_t Index(size_t bytes)
	{
		assert(bytes <= MAX_BYTES);

		// 每个区间有多少个链
		static int group_array[4] = { 16, 56, 56, 56 };
		if (bytes <= 128){
			return _Index(bytes, 3);
		}
		else if (bytes <= 1024){
			return _Index(bytes - 128, 4) + group_array[0];
		}
		else if (bytes <= 8 * 1024){
			return _Index(bytes - 1024, 7) + group_array[1] + group_array[0];
		}
		else if (bytes <= 64 * 1024){
			return _Index(bytes - 8 * 1024, 10) + group_array[2] + group_array[1] + group_array[0];
		}
		else if (bytes <= 256 * 1024){
			return _Index(bytes - 64 * 1024, 13) + group_array[3] + group_array[2] + group_array[1] + group_array[0];
		}
		else{
			assert(false);
		}

		return -1;
	}


};

Allocate的实现

void* ThreadCache::Allocate(size_t size)
{
	//size是要申请空间的字节大小
	//MAX_BYTES为最大申请的空间字节数 256*1024字节
	assert(size <= MAX_BYTES);
	//首先计算出对齐字节数和对应桶的下标
	size_t alignSize = SizeClass::RoundUp(size);
	size_t index = SizeClass::Index(size);
	
	//如果对应桶不为空,则拿对应的字节空间
	if (!_freeLists[index].Empty())
	{
		return _freeLists[index].Pop();
	}
	else//如果对应桶为空,则向中心缓存申请空间
	{
		return FetchFromCentralCache(index, alignSize);
	}
}

Deallocate的实现

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

	// 找对映射的自由链表桶,对象插入进入
	size_t index = SizeClass::Index(size);
	_freeLists[index].Push(ptr);

封装ThreadCache层可以多线程访问

只要在ThreadCache.h文件添加下列代码,就可以支持多线程访问。

// TLS thread local storage
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

然后用ConcurrentAlloc.h封装ThreadCache层,不让外面直接可以访问到ThreadCache的函数。保证安全性

pragma once

#include "Common.h"
#include "ThreadCache.h"

static void* ConcurrentAlloc(size_t size)
{
	// 通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象
	if (pTLSThreadCache == nullptr)
	{
		pTLSThreadCache = new ThreadCache;
	}

	return pTLSThreadCache->Allocate(size);
}

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

	pTLSThreadCache->Deallocate(ptr, size);
}

ThreadCache层完整代码

Common.h

#pragma once

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

static const size_t MAX_BYTES = 256 * 1024;
static const size_t NFREELIST = 208;

static void*& NextObj(void* obj)
{
	return *(void**)obj;
}

// 管理切分好的小对象的自由链表
class FreeList
{
public:
	void Push(void* obj)
	{
		assert(obj);

		// 头插
		//*(void**)obj = _freeList;
		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)

	/*size_t _RoundUp(size_t size, size_t alignNum)
	{
		size_t alignSize;
		if (size % alignNum != 0)
		{
			alignSize = (size / alignNum + 1)*alignNum;
		}
		else
		{
			alignSize = size;
		}

		return alignSize;
	}*/
	// 1-8 
	static inline size_t _RoundUp(size_t bytes, size_t alignNum)
	{
		return ((bytes + alignNum - 1) & ~(alignNum - 1));
	}

	static inline size_t RoundUp(size_t size)
	{
		if (size <= 128)
		{
			return _RoundUp(size, 8);
		}
		else if (size <= 1024)
		{
			return _RoundUp(size, 16);
		}
		else if (size <= 8*1024)
		{
			return _RoundUp(size, 128);
		}
		else if (size <= 64*1024)
		{
			return _RoundUp(size, 1024);
		}
		else if (size <= 256 * 1024)
		{
			return _RoundUp(size, 8*1024);
		}
		else
		{
			assert(false);
			return -1;
		}
	}

	/*size_t _Index(size_t bytes, size_t alignNum)
	{
	if (bytes % alignNum == 0)
	{
	return bytes / alignNum - 1;
	}
	else
	{
	return bytes / alignNum;
	}
	}*/

	// 1 + 7  8
	// 2      9
	// ...
	// 8      15
	
	// 9 + 7 16
	// 10
	// ...
	// 16    23
	static inline size_t _Index(size_t bytes, size_t align_shift)
	{
		return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;
	}

	// 计算映射的哪一个自由链表桶
	static inline size_t Index(size_t bytes)
	{
		assert(bytes <= MAX_BYTES);

		// 每个区间有多少个链
		static int group_array[4] = { 16, 56, 56, 56 };
		if (bytes <= 128){
			return _Index(bytes, 3);
		}
		else if (bytes <= 1024){
			return _Index(bytes - 128, 4) + group_array[0];
		}
		else if (bytes <= 8 * 1024){
			return _Index(bytes - 1024, 7) + group_array[1] + group_array[0];
		}
		else if (bytes <= 64 * 1024){
			return _Index(bytes - 8 * 1024, 10) + group_array[2] + group_array[1] + group_array[0];
		}
		else if (bytes <= 256 * 1024){
			return _Index(bytes - 64 * 1024, 13) + group_array[3] + group_array[2] + group_array[1] + group_array[0];
		}
		else{
			assert(false);
		}

		return -1;
	}


};

ThreadCache.h

#pragma once

#include "Common.h"

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

	// 从中心缓存获取对象
	void* FetchFromCentralCache(size_t index, size_t size);
private:
	FreeList _freeLists[NFREELIST];
};

// TLS thread local storage
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

ThreadCache.cpp

#include "ThreadCache.h"

void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{
	// ...
	return nullptr;
}

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())
	{
		return _freeLists[index].Pop();
	}
	else
	{
		return FetchFromCentralCache(index, alignSize);
	}
}

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

	// 找对映射的自由链表桶,对象插入进入
	size_t index = SizeClass::Index(size);
	_freeLists[index].Push(ptr);
}

ConcurrentAlloc.h

#pragma once

#include "Common.h"
#include "ThreadCache.h"

static void* ConcurrentAlloc(size_t size)
{
	// 通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象
	if (pTLSThreadCache == nullptr)
	{
		pTLSThreadCache = new ThreadCache;
	}

	return pTLSThreadCache->Allocate(size);
}

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

	pTLSThreadCache->Deallocate(ptr, size);
}

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

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

相关文章

02、全文检索 ------ Solr(企业级的开源的搜索引擎) 的下载、安装、Solr的Web图形界面介绍

目录 Solr 的下载和安装Solr的优势&#xff1a;Lucene与Solr 安装 Solr1、下载解压2、添加环境变量3、启动 Solr Solr 所支持的子命令&#xff1a;Solr 的 Core 和 Collection 介绍Solr 的Web控制台DashBoard&#xff08;仪表盘&#xff09;Logging&#xff08;日志&#xff09…

理想架构的高回退Doherty功率放大器理论与ADS仿真-Multistage

理想架构的高回退Doherty功率放大器理论与仿真-Multistage 参考&#xff1a; 三路Doherty设计 01 射频基础知识–基础概念 Switchmode RF and Microwave Power Amplifiers、 理想架构的Doherty功率放大器&#xff08;等分经典款&#xff09;的理论与ADS电流源仿真参考&#x…

《云原生安全攻防》-- 云原生安全概述

从本节课程开始&#xff0c;我们将正式踏上云原生安全的学习之旅。在深入探讨云原生安全的相关概念之前&#xff0c;让我们先对云原生有一个全面的认识。 什么是云原生呢? 云原生&#xff08;Cloud Native&#xff09;是一个组合词&#xff0c;我们把它拆分为云和原生两个词来…

TS项目实战一:流淌的字符动画界面

使用ts实现虚拟世界&#xff0c;创建ts项目&#xff0c;并编写ts代码&#xff0c;使用tsc编译后直接加载到html界面&#xff0c;实现类似黑客帝国中的流淌的代码界面的效果。 源码下载地址&#xff1a;点击下载 讲解视频 TS实战项目一&#xff1a;数字流界面项目创建 TS实战项…

LRU缓存(Leetcode146)

例题&#xff1a; 分析&#xff1a; 题目要求函数get和put要达到O(1)的时间复杂度&#xff0c;可以用 hashMap 来实现&#xff0c;因为要满足逐出最久未使用的元素的一个效果&#xff0c;还需要配合一个双向链表来共同实现。链表中的节点为一组key-value。 我们可以用双向链表来…

前端工程化之:webpack1-9(plugin)

一、plugin loader 的功能定位是转换代码&#xff0c;而一些其他的操作难以使用 loader 完成&#xff0c;比如&#xff1a; 当 webpack 生成文件时&#xff0c;顺便多生成一个说明描述文件&#xff1b;当 webpack 编译启动时&#xff0c;控制台输出一句话表示 webpack 启动了&…

Gas Hero Common Heroes NFT 概览与数据分析

作者&#xff1a;stellafootprint.network 编译&#xff1a;mingfootprint.network 数据源&#xff1a;Gas Hero Common Heroes NFT Collection Dashboard Gas Hero “盖世英雄” 是一个交互式的 Web3 策略游戏&#xff0c;强调社交互动&#xff0c;并与 FSL 生态系统集成…

THREE.JS动态场景开发实战【赛博朋克】

在本教程中&#xff0c;我们将探索如何创建类似 Three.js 的赛博朋克场景&#xff0c;灵感来自 Pipe 网站上的背景动画。 我们将指导你完成使用 Three.js 编码动态场景的过程&#xff0c;包括后处理效果和动态光照&#xff0c;所有这些都不需要任何着色器专业知识。 我用这个场…

自动保存知乎上点赞的内容至本地

背景&#xff1a;知乎上常有非常精彩的回答/文章&#xff0c;必须要点赞收藏&#xff0c;日后回想起该回答/文章时翻看自己的动态和收藏夹却怎么也找不到&#xff0c;即使之前保存了链接网络不好也打不开了&#xff08;。所以我一般碰到好的回答/文章都会想办法保存它的离线版本…

文件上传的另类应用

1.Imagemagick CVE-2016-3714 CVE-2022-44268 CVE-2020-29599可在vulhub靶场进行复现1.1.Imagemagick简介 ImageMagic是一款图片处理工具&#xff0c;当传入一个恶意图片时&#xff0c;就有可能存在命令注入漏洞。 ImageMagick默认支持一种图片格式mvg&#xff0c;而mvg与svg…

USTC ICS(2023Fall) Lab2 The PingPong Sequence

LC-3汇编语言 .ORIG x3000LDI R0,n ;f(n)NOT R0,R0ADD R0,R0,#1 ;取R0补码用于减法AND R1,R1,#0 ;R1记录循环次数,先初始化为0ADD R2,R1,#0 ;R2记录符号&#xff0c;加号为0,减号为-1,f(1)对应加号ADD R3,R1,#3 ;记录f(n),f(1)3AND R5,R5,#0 ;R5存0000 1111 1111 1111…

ubuntu20配置mysql8

首先更新软件包索引运行 sudo apt update命令。然后运行 sudo apt install mysql-server安装MySQL服务器。 安装完成后&#xff0c;MySQL服务将作为systemd服务自动启动。你可以运行 sudo systemctl status mysql命令验证MySQL服务器是否正在运行。 连接MySQL 当MySQL安装…

华为mate60 pro与小米14 pro 的巅峰对决

今天我们换下思路&#xff0c;不讲技术了&#xff01;我们一起讲讲手机&#xff01;小编暂时充当一下业余的数码咖。 今天我们就讲讲华为mate60 pro和小米14pro 这两款手机。这两款手机都是近期新出的发行版本&#xff0c;热度那是一直未减啊。 华为mate60 Pro 我们先说说这个…

gitlab-runner注册到gitlab时报错:ERROR: Registering runner... failed xxxxxxxx

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

CRG设计之时钟

1. 前言 CRG(Clock and Reset Generation&#xff0c;时钟复位生成模块) 模块扮演着关键角色。这个模块负责为整个系统提供稳定可靠的时钟信号&#xff0c;同时在系统上电或出现故障时生成复位信号&#xff0c;确保各个模块按预期运行。简而言之&#xff0c;CRG模块就像是SoC系…

Advanced CNN

文章目录 回顾Google NetInception1*1卷积Inception模块的实现网络构建完整代码 ResNet残差模块 Resedual Block残差网络的简单应用残差实现的代码 练习 回顾 这是一个简单的线性的卷积神经网络 然而有很多更为复杂的卷积神经网络。 Google Net Google Net 也叫Inception V…

第一集《修道宗范》

当家师父慈悲&#xff0c;诸位法师、诸位新戒、诸位在家菩萨&#xff0c;阿弥陀佛 今天学人跟大家研究的主题是《修道宗范》。很多人都会认为&#xff1a;所有的宗教都是劝人为善&#xff0c;所以佛教的修学跟一般的宗教&#xff0c;完全是一样的。其实&#xff0c;这个观念只…

Centos慢慢长大(一)

1、写在前面 这将是一个系列性的文章。可能更多的是记录我在学习的过程中的一些感悟吧。我想强调的是在这一系列文章里我会从最小化的安装开始&#xff0c;然后逐渐的增加需要安装的软件。就象一个婴儿的诞生&#xff0c;慢慢的学走路、学说话、学使用筷子。。。。。。 这将是一…

离谱题 3236:练39.1 书香阁座位

3236正常写法 #include<bits/stdc.h> using namespace std; int main() {int sum,a,b;a1;b10;sumb;cout<<a<<" "<<b;cout<<" "<<sum<<endl;do{a;b2;sumx;cout<<a<<" "<<b<<&…

升级企业战略,思腾合力布局智能生产基地

一直专注于人工智能领域&#xff0c;提供云计算、AI服务器、AI工作站、系统集成、产品定制、软件开发、边缘计算等产品和整体解决方案&#xff0c;致力于成为行业领先的人工智能基础架构解决方案商。 升级企业战略 布局智能生产基地 “十四五”时期&#xff0c;是乘势而上打造…