SpringBoot + MyBatisPlus 实现多租户分库

一、引言

在如今的软件开发中,多租户(Multi-Tenancy)应用已经变得越来越常见。多租户是一种软件架构技术,它允许一个应用程序实例为多个租户提供服务。每个租户都有自己的数据和配置,但应用程序实例是共享的。而在我们的Spring Boot + MyBatis Plus环境中,我们可以利用动态数据源来实现多租户分库。

二、实现原理

SpringBoot + MyBatisPlus 动态数据源实现多租户分库的原理主要是通过切换不同的数据库连接来实现。对于每个租户,应用程序会使用一个独立的数据库连接,这样每个租户就拥有了自己的数据隔离空间。具体来说,当我们创建一个新的租户时,我们同时也为这个租户创建一个新的数据库连接。这些数据库连接被存储在一个数据源工厂中,我们可以根据租户的ID或者其他唯一标识符来获取对应的数据库连接。当一个租户需要访问其数据时,我们从数据源工厂中获取该租户对应的数据库连接,然后使用这个连接来执行数据库操作。

三、引入依赖

在pom.xml文件中引入下述相关的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.bc</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>6.0.1.Final</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.0</version><scope>provided</scope></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.25</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

四、配置yml 

在application.yml文件中添加下述配置:

server:port: 10086spring:application:name: demodatasource:url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=UTCusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSource
mybatis:mapper-locations: classpath:mapper/*.xmlmybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

五、数据准备 

在demo库中新建一张名为tenant_datasource的表,用于存储多租户的数据源配置信息: 

CREATE TABLE `tenant_datasource` (`tenant_id` varchar(50) NOT NULL,`url` varchar(255) DEFAULT NULL,`username` varchar(50) DEFAULT NULL,`password` varchar(50) DEFAULT NULL,PRIMARY KEY (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后执行下述SQL往tenant_datasource表中插入一些测试数据: 

insert into `tenant_datasource` (`tenant_id`, `url`, `username`, `password`) values('tenant1','jdbc:mysql://localhost:3306/tenant1_db','root','123456');
insert into `tenant_datasource` (`tenant_id`, `url`, `username`, `password`) values('tenant2','jdbc:mysql://localhost:3306/tenant2_db','root','123456');

在tenant1_db库中新建一张名为user的表,用于存储多租户1的用户信息:

CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_name` varchar(10) NOT NULL,`sex` tinyint(4) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后执行下述SQL往tenant1_db库在的user表中插入一些测试数据: 

insert into `user` (`id`, `user_name`, `sex`) values('1','范闲','1');

在tenant2_db库中新建一张名为user的表,用于存储多租户2的用户信息: 

CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_name` varchar(10) NOT NULL,`sex` tinyint(4) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后执行下述SQL往tenant2_db库在的user表中插入一些测试数据: 

insert into `user` (`id`, `user_name`, `sex`) values('1','海棠朵朵','0');

六、编写实体类

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@TableName("user")
@Data
public class User {@TableId(type = IdType.AUTO)private Integer id;@TableField(value = "user_name")private String userName;@TableField(value = "sex")private Integer sex;
}
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.boot.jdbc.DataSourceBuilder;import javax.sql.DataSource;@Data
@TableName("tenant_datasource")
public class TenantDataSource {@TableId(type = IdType.INPUT)private String tenantId;@TableField(value = "url")private String url;@TableField(value = "username")private String username;@TableField(value = "password")private String password;public DataSource createDataSource() {return DataSourceBuilder.create().url(this.url).username(this.username).password(this.password).build();}
}

七、编写默认数据源配置类 

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DynamicDataSourceProperties {private String url;private String username;private String password;
}

八、构建Mapper接口和xml文件 

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;
import org.springframework.stereotype.Repository;@Repository
public interface UserMapper extends BaseMapper<User> {}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.TenantDataSource;
import org.springframework.stereotype.Repository;@Repository
public interface TenantDataSourceMapper extends BaseMapper<TenantDataSource> {}

在启动类配置扫描路径@MapperScan("com.example.demo.mapper"): 

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@MapperScan("com.example.demo.mapper")
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

九、编写业务实现类 

import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public User getUserById(Long id) {User user = userMapper.selectById(id);return user;}
}

 十、创建数据源管理器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** 创建数据源管理器*/
@Component
public class DataSourceManager {@Autowiredprivate DynamicDataSourceProperties dynamicDataSourceProperties;private final Map<String, DataSource> dataSources = new HashMap<>();@PostConstructpublic void init() {// 根据配置创建数据源并加入管理器DataSource defaultDataSource = DataSourceBuilder.create().url(dynamicDataSourceProperties.getUrl()).username(dynamicDataSourceProperties.getUsername()).password(dynamicDataSourceProperties.getPassword()).build();dataSources.put("default", defaultDataSource);}public void addDataSource(String tenantId, DataSource dataSource) {dataSources.put(tenantId, dataSource);}public DataSource getDataSource(String tenantId) {return dataSources.get(tenantId);}public Map<String, DataSource> getAllDataSources() {return dataSources;}/*** 判断是否包含数据源*/public boolean containDataSourceKey(String key) {return dataSources.containsKey(key);}
}

十一、创建租户上下文 

import lombok.extern.slf4j.Slf4j;@Slf4j
public class TenantContext {// 使用ThreadLocal来存储当前线程的数据源名称(租户标识),保证多线程情况下,各自的数据源互不影响private static ThreadLocal<String> tenantId = ThreadLocal.withInitial(() -> "default");public static void setTenantId(String id) {tenantId.set(id);log.info("已切换到数据源:{}", id);}public static String getTenantId() {return tenantId.get();}public static void clear() {tenantId.remove();log.info("已切换回默认数据源");}
}

十二、创建动态数据源

创建一个动态数据源类,继承AbstractRoutingDataSource,用于动态切换数据源:

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;@Slf4j
@Data
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据源** @return*/@Overrideprotected Object determineCurrentLookupKey() {return TenantContext.getTenantId();}
}

十三、创建数据源配置

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;import java.util.HashMap;
import java.util.Map;/*** 创建数据源配置类,用于配置动态数据源*/@Configuration
public class DynamicDataSourceConfig {@Autowiredprivate DataSourceManager dataSourceManager;@Beanpublic DynamicDataSource dynamicDataSource() {// 1、将数据源default设置为默认数据源DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setDefaultTargetDataSource(dataSourceManager.getDataSource("default"));// 2、获取初始化时所有的数据源,并设置目标数据源,必须为targetDataSources设置初始值,否则会报错Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.putAll(dataSourceManager.getAllDataSources());dynamicDataSource.setTargetDataSources(targetDataSources);return dynamicDataSource;}@Beanpublic DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {return new DataSourceTransactionManager(dynamicDataSource);}
}

十四、创建多租户数据源服务 

创建多租户数据源服务类,用于初始化多租户数据源:

import com.example.demo.entity.TenantDataSource;
import com.example.demo.mapper.TenantDataSourceMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@Service
public class MultiTenantDataSourceService {@Autowiredprivate DataSourceManager dataSourceManager;@Autowiredprivate DynamicDataSource dynamicDataSource;@Autowiredprivate TenantDataSourceMapper tenantDataSourceMapper;@PostConstructpublic void initialize() {// 1、从默认的数据源中查询出所有的租户信息,然后覆盖DynamicDataSource中的targetDataSources属性Map<Object, Object> targetDataSources = new HashMap<>();List<TenantDataSource> tenantDataSources = tenantDataSourceMapper.selectList(null);for (TenantDataSource tenantDataSource : tenantDataSources) {dataSourceManager.addDataSource(tenantDataSource.getTenantId(), tenantDataSource.createDataSource());}targetDataSources.putAll(dataSourceManager.getAllDataSources());dynamicDataSource.setTargetDataSources(targetDataSources);// 2、必须执行此操作,才会重新初始化AbstractRoutingDataSource中的resolvedDataSources,也只有这样,动态切换数据源才会起效dynamicDataSource.afterPropertiesSet();}
}

十五、构建拦截器,并将其注册到InterceptorRegistry中

import cn.hutool.core.util.StrUtil;
import com.example.demo.config.DataSourceManager;
import com.example.demo.config.TenantContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Configuration
public class AuthInterceptor implements HandlerInterceptor {@Autowiredprivate DataSourceManager dataSourceManager;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String tenantId = request.getHeader("tenantId");if (StrUtil.isNotBlank(tenantId) && dataSourceManager.containDataSourceKey(tenantId) && (!"default".equals(tenantId))) {TenantContext.setTenantId(tenantId);return true;}else{return false;}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {TenantContext.clear();}
}
import com.example.demo.Interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {@Autowiredprivate AuthInterceptor authInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authInterceptor);}
}

十六、创建Controller 

import com.example.demo.entity.User;
import com.example.demo.serivice.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/user/{userId}")public User getUser(@PathVariable Long userId) {return userService.getUserById(userId);}
}

十七、测试

启动应用程序,通过访问localhost:10086/user/{userId} 来测试多租户分库功能:

可以看到上述测试示例中,已经实现了不同的租户查询独立的数据库信息。

十八、适用场景

  • 多租户系统开发:适用于多租户系统,每个租户有独立的数据库,通过动态数据源切换实现多租户数据隔离。
  • 租户级数据隔离:当多个租户共享同一应用但需要数据隔离时,可以通过此模式实现。 
  • 灵活扩展:适用于系统需求可能动态扩展租户,每个租户有独立数据库的场景,不需修改系统架构。

十九、优点

  • 数据隔离性强:每个租户有独立的数据库,数据隔离,保护租户数据安全。
  • 性能优化:每个租户有独立的数据库,避免多租户共享同一数据库的性能瓶颈。
  • 方便扩展:可以轻松实现动态增加新租户,每个租户有独立的数据库。
  • 可维护性高:MyBatisPlus提供了便捷的操作数据库的功能,减少开发人员的工作量。
  • 易用性强:Spring Boot集成MyBatisPlus,简化了配置和集成流程,提高开发效率。

二十、总结 

Spring Boot与MyBatis Plus结合,通过动态数据源实现多租户分库,是一种高效、灵活、易维护的解决方案,适用于多租户系统的开发。可以有效地保护租户数据安全,提高系统性能,同时具有良好的可扩展性和可维护性。 

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

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

相关文章

Celery,一个实时处理的 Python 分布式系统

大家好&#xff01;我是爱摸鱼的小鸿&#xff0c;关注我&#xff0c;收看每期的编程干货。 一个简单的库&#xff0c;也许能够开启我们的智慧之门&#xff0c; 一个普通的方法&#xff0c;也许能在危急时刻挽救我们于水深火热&#xff0c; 一个新颖的思维方式&#xff0c;也许能…

【高校科研前沿】中国农业大学姚晓闯老师等人在农林科学Top期刊发表长篇综述:深度学习在农田识别中的应用

文章简介 论文名称&#xff1a;Deep learning in cropland field identification: A review&#xff08;深度学习在农田识别中的应用&#xff1a;综述&#xff09; 第一作者及单位&#xff1a;Fan Xu&#xff08;中国农业大学土地科学与技术学院&#xff09; 通讯作者及单位&…

39 线程库

目录 thread类的简单介绍线程函数参数锁线程交替打印原子性操作库无锁CAS智能指针的线程安全单例模式的线程安全 1. thread类的简单介绍 在c11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;如windows和linux下各有自己的接口&#xff0c;这使得…

PTA - sdut-使用函数求a+aa+aaa++⋯+aa.....aaa(n个a)之和

题目描述&#xff1a; 给定两个均不超过9的正整数a和n&#xff0c;要求&#xff1a;编写函数fn(a,n)&#xff0c; 求aaaaaa⋯aa⋯aa(n个a&#xff09;之和&#xff0c;fn须返回的是数列之和。 函数接口定义&#xff1a; def fn(a,n):其中&#xff0c; a 和 n 都是传入的参数…

《RWKV》论文笔记

原文出处 [2305.13048] RWKV: Reinventing RNNs for the Transformer Era (arxiv.org) 原文笔记 What RWKV(RawKuv):Reinventing RNNs for the Transformer Era 本文贡献如下&#xff1a; 提出了 RWKV 网络架构&#xff0c;结合了RNNS 和Transformer 的优点&#xff0c;同…

Java文件操作和IO的小案例

文章目录 案例1案例2案例3 案例1 要求&#xff1a; 扫描指定目录&#xff0c;并找到名称中包含指定字符的所有普通文件&#xff08;不包含目录&#xff09;&#xff0c;并且后续询问用户是否要删除该文件。 代码实现&#xff1a; package shixun;import java.io.File; import…

动手学深度学习54 循环神经网络

动手学深度学习54 循环神经网络 1. 循环神经网络RNN2. QA 1. 循环神经网络RNN h t h_t ht​ 与 h t − 1 h_{t-1} ht−1​ x t − 1 x_{t-1} xt−1​有关 x t x_t xt​ 与 h t h_t ht​ x t − 1 x_{t-1} xt−1​ 有关 怎么把潜变量变成RNN–假设更简单 潜变量和隐变量的区…

【动态规划Ⅴ】二维数组的动态规划——0/1矩阵、最大正方形

二维数组的动态规划——0/1矩阵、最大正方形 最大正方形1277. 统计全为 1 的正方形子矩阵221. 最大正方形 01矩阵542. 01 矩阵 最大正方形 下面两个题目是非常相似的&#xff0c;只是一个统计正方形数目&#xff0c;一个统计最大正方形的面积。 1277. 统计全为 1 的正方形子矩…

打卡第7天-----哈希表

继续坚持✊,我现在看到leetcode上的题不再没有思路了,真的是思路决定出路,在做题之前一定要把思路梳理清楚。 一、四数相加 leetcode题目编号:第454题.四数相加II 题目描述: 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j…

RoPE旋转位置编码从复数到欧拉公式

第二部分 从复数到欧拉公式 先复习下复数的一些关键概念 我们一般用表示复数&#xff0c;实数a叫做复数的实部&#xff0c;实数b叫做复数的虚部 复数的辐角是指复数在复平面上对应的向量和正向实数轴所成的有向角 的共轭复数定义为&#xff1a;&#xff0c;也可记作&#xff0…

AI发展的新方向:从卷模型到卷应用

在2024年7月4日于上海世博中心举办的世界人工智能大会暨人工智能全球治理高级别会议全体会议上&#xff0c;百度创始人、董事长兼首席执行官李彦宏发表了一段引人深思的演讲。他在产业发展主论坛上提出&#xff1a;“大家不要卷模型&#xff0c;要卷应用&#xff01;”这句话道…

对象存储-MinIO-学习-01-安装部署

目录 一、介绍 二、环境信息 三、下载安装包 1、MinIO官网下载地址 2、选择版本 &#xff08;1&#xff09;MinIO Server &#xff08;2&#xff09;MinIO Client &#xff08;3&#xff09;MinIO SDK 四、MinIO SDK安装步骤 1、安装minio库 2、导入minio库报错&…

docker笔记1

docker笔记1 一、为什么要学docker?二、docker是什么三、docker安装 一、为什么要学docker? 在过去&#xff0c;开发人员编写的代码在不同的环境中运行时常常面临一些问题&#xff0c;例如“在我的机器上可以运行&#xff0c;但在你的机器上却不行”的情况。这种问题部分原因…

2024全网最全面及最新且最为详细的网络安全技巧五 之 SSRF 漏洞EXP技巧,典例分析以及 如何修复 (下册)———— 作者:LJS

五.SSRF 漏洞EXP技巧&#xff0c;典例分析以及 如何修复 (下册) 目录 五.SSRF 漏洞EXP技巧&#xff0c;典例分析以及 如何修复 (下册) 5.4gopher 协议初探 0x01 Gopher协议 0x02 协议访问学习 复现环境 centos7 kali 2018 发送http get请求 发送http post请求 5.5 SSRF…

isaac sim 与 WLS2 ros2实现通信

Omniverse以及isaac还是windows下使用顺手一点&#xff0c;但是做跟ros相关的开发时候&#xff0c;基本就得迁移到ubuntu下了&#xff0c;windows下ros安装还是过于复杂&#xff0c;那不想用双系统或者ubuntu或者虚拟机&#xff0c;有啥别的好方法呢&#xff1f;这里想到了wind…

安全求交集PSI

安全求交集定义 求交集的PSI&#xff1a;交集可以被两方看见或其中一方看见&#xff0c;非交集进行保护有两方的PSI半诚实的PSI&#xff1a;攻击者要严格遵守协议&#xff0c;在此基础上得到他人的秘密是做不到的 Two-Party Semi-Honest PSI 挑战一&#xff1a;隐藏非交集元素…

软件测试之冒烟测试

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1. 核心 冒烟测试就是完成一个新版本的开发后&#xff0c;对该版本最基本的功能进行测试&#x…

使用树莓派进行python开发,控制电机的参考资料

网站连接&#xff1a;https://www.cnblogs.com/kevenduan?page1 1、简洁的过程步骤&#xff0c; 2、有代码示例&#xff0c; 3、有注意事项&#xff0c;

Java PKI Programmer‘s Guide

一、PKI程序员指南概述 PKI Programmer’s Guide Overview Java认证路径API由一系列类和接口组成&#xff0c;用于创建、构建和验证认证路径。这些路径也被称作认证链。实现可以通过基于提供者的接口插入。 这个API基于密码服务提供者架构&#xff0c;这在《Java密码架构参考指…

硬件:CPU和GPU

一、CPU与GPU 二、提升CPU利用率&#xff1a;计组学过的 1、超线程一般是给不一样的任务的计算使用&#xff0c;而非在计算密集型工作中 2、Cpu一次可以计算一个线程&#xff0c;而gpu有多少个绿点一次就能计算多少个线程&#xff0c;Gpu比cpu快是因为gpu它的核多&#xff0c;…