【多线程】补充内容 {线程池;线程安全的单例模式;STL容器、智能指针的线程安全;其他常见的各种锁:自旋锁、读写锁}

news2024/9/17 9:05:44

一、线程池

1.1 概念

线程池一种线程使用模式:

线程过多会带来调度开销,进而影响缓存局部性和整体性能。

而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务:(线程池的优点)

  • 这避免了在处理短时间任务时创建与销毁线程的代价。
  • 线程池不仅能够保证内核的充分利用,还能防止过分调度。

注意:可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

线程池的应用场景:

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。比如WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。比如突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误

线程池使用:

  • 创建固定线程数量的线程池,循环从任务队列中获取任务对象
  • 获取到任务对象后,执行任务对象中的任务接口

1.2 实现

1.2.1 封装线程对象thread + RAII自动加锁解锁

#pragma once
#include <iostream>
#include <pthread.h>
#include <string>
#include <functional>
#include "logmessage.hpp"
 
namespace zty
{
    typedef void *(*func_t)(void *);
 	//1.封装线程对象
    class thread
    {
        pthread_t _tid;
        func_t _callback = nullptr;
        void *_args = nullptr;
 
    public:
        thread() {}
 
        thread(func_t callback, void *args)
            : _callback(callback),
              _args(args)
        {
            pthread_create(&_tid, nullptr, _callback, _args);
        }
 
        thread(const thread &other) = delete;
        thread &operator=(const thread &other) = delete;
 
        void run(func_t callback, void *args)
        {
            _callback = callback;
            _args = args;
            pthread_create(&_tid, nullptr, _callback, _args);
            // printf("[%d] run\n", _tid%10000);
            LogMessage(DEBUG, "[%d] run", _tid%10000);
        }
 
        void join()
        {
            // printf("[%d] join\n", _tid%10000);
            LogMessage(DEBUG, "[%d] join", _tid%10000);
            pthread_join(_tid, nullptr);
        }
 
        pthread_t get_id()
        {
            return _tid;
        }
    };
    
    //2. RAII自动加锁解锁
    class lock_guard
    {
        pthread_mutex_t &_pmtx;
 
    public:
        lock_guard(pthread_mutex_t &pmtx)
            : _pmtx(pmtx)
        {
            pthread_mutex_lock(&_pmtx);
        }
        ~lock_guard()
        {
            pthread_mutex_unlock(&_pmtx);
        }
    };
}

1.2.2 线程池的实现

下面进行实现一个简单的线程池,线程池中提供了一个任务队列,以及若干个线程(多线程)
img

  • 线程池中的多个线程负责从任务队列当中拿任务,并将拿到的任务进行处理,没有任务就阻塞等待。
  • 线程池对外提供一个push接口,用于让外部线程能够将任务push到任务队列当中

实现代码如下:(在堆区创建的单例懒汉模式)

#pragma once
#include "lockguard.hpp"
#include "thread.hpp"
#include "task.hpp"
#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
 
namespace zty
{
    const int THREAD_NUM = 3;
    template <class T>
    class thread_pool
    {
        static thread_pool *s_ins;
        int _thread_num;
        std::vector<zty::thread> _threads;
        std::queue<T> _tasks;
        static pthread_mutex_t _mtx;
        static pthread_cond_t _cond;
        bool _terminate = false; // 结束标志
 
        thread_pool(int thread_num = THREAD_NUM)
            : _thread_num(thread_num),
              _threads(_thread_num)
        {}
 
        ~thread_pool()
        {
            _terminate = true;              // 设置结束标志
            pthread_cond_broadcast(&_cond); // 唤醒所有等待条件变量的线程
            for (auto &e : _threads)        // 回收所有子线程
            {
                e.join();
            }
        }
 
        thread_pool(const thread_pool &other) = delete;
        thread_pool &operator= (const thread_pool &other) = delete;
 
        struct GC
        {
            ~GC()
            {
                if (s_ins != nullptr)
                {
                    delete s_ins;
                    s_ins = nullptr;
                }
            }
        };
 
    public:
        static thread_pool &GetInstance(int num = THREAD_NUM)
        {
            if (s_ins == nullptr)
            {
                zty::lock_guard lock(_mtx);
                if (s_ins == nullptr)
                {
                    s_ins = new thread_pool(num);
                }
            }
            return *s_ins;
        }
 
        void push(const T &task)
        {
            zty::lock_guard lock(_mtx);
            _tasks.push(task);
            pthread_cond_signal(&_cond);
        }
 
        bool pop(T &out)
        {
            zty::lock_guard lock(_mtx);
            while (_tasks.empty())
            {
                pthread_cond_wait(&_cond, &_mtx);
                if (_terminate)
                    return false;
            }
 
            out = _tasks.front();
            _tasks.pop();
            return true;
        }
 
        void run()
        {
            for (auto &e : _threads)
            {
                e.run(routine, this);
            }
        }
 		//pthread_creat函数要求的线程入口点函数的参数只有一个void*,不能有this指针。
        static void *routine(void *args) 
        {
            thread_pool *self = (thread_pool *)args; // self实际就是this指针
            T task;
            while (!self->_terminate)
            {
                bool ret = self->pop(task);
                if (ret) // 判断self->pop是否获取到任务了
                {
                    LogMessage(NORMAL, "[%d] %d%c%d=%d", pthread_self() % 10000, task._l, task._op, task._r, task());
                    sleep(1);
                }
            }
            return (void *)0;
        }
    };
 
    template <class T>
    thread_pool<T> *thread_pool<T>::s_ins = nullptr;
    template <class T>
    pthread_mutex_t thread_pool<T>::_mtx = PTHREAD_MUTEX_INITIALIZER;
    template <class T>
    pthread_cond_t thread_pool<T>::_mtx = PTHREAD_CONDITION_INITIALIZER;
    template <class T>
    typename thread_pool<T>::GC thread_pool<T>::s_gc;
}

test.cc

#include "thread.hpp"
#include "lockguard.hpp"
#include "task.hpp"
#include "threadpool.hpp"
#include "logmessage.hpp"
#include <thread>
#include <unistd.h>
#include <ctime>
#include <cstdlib>
using namespace std;
 
int main()
{
    srand((unsigned int)time(nullptr));
    // zty::thread_pool<zty::Task> tp(5);
    zty::thread_pool<zty::Task> &tp = zty::thread_pool<zty::Task>::GetInstance();
    tp.run();
    char ops[] = {'+', '-', '*', '/' ,'%'};
    int cnt = 5;
    while(cnt--)
    {
        int l = rand()%100;
        int r = rand()%100;
        char op = ops[rand()%5];
        zty::Task task(l, r, op);
        // printf("main_thread: %d%c%d=?\n", task._l, task._op, task._r);
        LogMessage(NORMAL, "main_thread: %d%c%d=?", task._l, task._op, task._r);
        tp.push(task);
        sleep(1);
    }
    return 0;
}

logmessage.hpp

#pragma once
#include <cstdarg>
#include <cstdio>
#include <ctime>
#include <cstring>
 
enum LEVEL
{
    DEBUG,
    NORMAL,
    WARNING,
    ERROR,
    FATAL
};
 
const char *lvtable[] = {"DEBUG", "NORMAL", "WARNING", "ERROR", "FATAL"};
const char *filepath = "./log.txt";
 
void LogMessage(LEVEL level, const char *format, ...)
{
#ifndef DEBUGSHOW
    if (level == DEBUG)
        return;
#endif 
    char buffer[1024];
    time_t timestamp;
    time(&timestamp);
    tm *timeinfo = localtime(&timestamp);
    snprintf(buffer, sizeof(buffer), "[%d/%d/%d]%s: ", timeinfo->tm_year + 1900, timeinfo->tm_mon, timeinfo->tm_mday, lvtable[level]);
    va_list ap;
    va_start(ap, format);
    vsnprintf(buffer + strlen(buffer), sizeof(buffer) - strlen(buffer), format, ap);
    va_end(ap);
    printf("%s\n", buffer);
    FILE *fp = fopen(filepath, "a");
    fprintf(fp, "%s\n", buffer);
}

二、线程安全的单例模式

这里在C++篇章已经谈过,这里不再赘述,链接::【C++】特殊类设计 {不能被拷贝的类;只能在堆上创建的类;只能在栈上创建的类;不能被继承的类;单例模式:懒汉模式,饿汉模式}-CSDN博客

注意事项:

  1. 单例模式有两种实现模式:饿汉模式(启动时实例化对象)和懒汉模式(在任意程序模块第一次访问单例时实例化对象)。
  2. 单例对象可以在堆区创建,也可以在静态区创建
  3. 在堆区创建的懒汉单例,获取单例指针时需要双检查加锁:双重 if 判定, 避免不必要的锁竞争
  4. 在堆区创建单例时,包含一个静态的内部类对象,该对象析构时会顺便析构单例,自动释放。

三、STL容器、智能指针的线程安全

STL中的容器是否是线程安全的?不是

  • STL 的设计初衷是将性能挖掘到极致,而一旦涉及到加锁保证线程安全,会对性能造成巨大的影响。

  • 而且对于不同的容器,加锁方式的不同,性能可能也不同(例如hash表的锁表和锁桶)

  • 因此 STL 默认不是线程安全。如果需要在多线程环境下使用。往往需要调用者自行保证线程安全

智能指针是否是线程安全的? 是

  • 对于unique_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题。

  • 对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题。但是标准库实现的时候考虑到了这个问题,基于原子操作(CAS)的方式保证了 shared_ptr 能够高效、原子地进行引用计数。


四、其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。(乐观锁需要被设计)
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。(与互斥锁原理一样)

4.1 pthread自旋锁

特性

  • 自旋锁是一种基于忙等待的锁,用于保护共享资源的访问。当一个线程尝试获取自旋锁时,如果锁已经被其他线程占用,则该线程会一直循环等待,直到锁被释放。这种等待的过程称为自旋
  • 不同于互斥锁,线程竞争自旋锁失败后不会被阻塞挂起,而是会进行自旋检测,直到获取锁
  • 无论是互斥锁的挂起等待还是自旋锁的轮询检测都是由pthread库完成的,我们不需要自行操作。因此自旋锁的使用方法和互斥锁相同。

使用场景

  • 自旋锁不会引起线程的上下文切换,而互斥锁(如Mutex)可能会引起线程的上下文切换,因此在一些锁的持有时间很短的场景下,使用自旋锁可以减少上下文切换的开销。
  • 自旋锁适用于等待时间短的情况,在等待时间短的情况下,自旋锁的效率较高。如果等待时间较长,自旋锁可能会消耗大量的CPU资源。

相关接口

#include <pthread.h>

pthread_spinlock_t spinlock; // 定义一个自旋锁
pthread_spin_init(&spinlock, 0); // 初始化自旋锁
pthread_spin_lock(&spinlock); // 获取自旋锁 
pthread_spin_unlock(&spinlock); // 释放自旋锁
pthread_spin_destroy(&spinlock); // 销毁自旋锁

应用层实现

//可以使用while循环+pthread_mutex_trylock在应用层实现一个自旋锁
while(pthread_mutex_trylock(&lock) == EBUSY);
//访问临界资源
//......
pthread_mutex_unlock(&lock);

4.2 pthread读写锁

特性

读写锁(Read-Write Lock)是一种特殊的锁机制,它允许多个线程同时获得读访问权限,但同一时间只有一个线程可以获得写访问权限。读写锁适用于读操作远远超过写操作的场景,可以提高并发性能。

使用场景

读写锁适用于读操作频繁而写操作较少的场景。如果读操作和写操作的数量相差不大,或者写操作频繁,可能会导致读写锁的效率不如互斥锁。

读者写者模型的特点

321原则(便于记忆)

  • 三种关系: 写者和写者(互斥关系)、读者和读者(没有关系)、写者和读者(互斥关系、同步关系)。
  • 两种角色: 写者和读者(通常由线程承担)
  • 一个交易场所: 通常指的是内存中的一段共享缓冲区(共享资源)

读者写者模型 VS 生产消费模型的本质区别:消费者会拿走数据,读者不会(读者只会对数据进行拷贝、访问…)

相关接口

#include <pthread.h>

//定义一个读写锁
pthread_rwlock_t xxx
//初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
    const pthread_rwlockattr_t*restrict attr);
//销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//读锁加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//写锁加锁
 
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解锁共用

示例代码

下面是一个使用pthread库中读写锁的简单示例:

#include <pthread.h>
#include <stdio.h>
 
// 定义一个全局读写锁
pthread_rwlock_t rwlock;
 
int shared_data = 0;
 
void* reader(void* arg) {
    int tid = *((int*)arg);
    while (1) {
        pthread_rwlock_rdlock(&rwlock); // 获取读锁
 
        printf("Reader %d: Shared data = %d\n", tid, shared_data);
 
        pthread_rwlock_unlock(&rwlock); // 释放读锁
 
        // 模拟读操作完成后的延迟
        usleep(1000000);
    }
    pthread_exit(NULL);
}
 
void* writer(void* arg) {
    int tid = *((int*)arg);
    while (1) {
        pthread_rwlock_wrlock(&rwlock); // 获取写锁
 
        shared_data++; // 修改共享数据
        printf("Writer %d: Modified shared data to %d\n", tid, shared_data);
 
        pthread_rwlock_unlock(&rwlock); // 释放写锁
 
        // 模拟写操作完成后的延迟
        usleep(2000000);
    }
    pthread_exit(NULL);
}
 
int main() {
    pthread_t reader1, reader2, writer1;
    int tid1 = 1, tid2 = 2, tid3 = 3;
 
    pthread_rwlock_init(&rwlock, NULL); // 初始化读写锁
 
    // 创建两个读线程和一个写线程
    pthread_create(&reader1, NULL, reader, &tid1);
    pthread_create(&reader2, NULL, reader, &tid2);
    pthread_create(&writer1, NULL, writer, &tid3);
 
    // 主线程等待读写线程结束
    pthread_join(reader1, NULL);
    pthread_join(reader2, NULL);
    pthread_join(writer1, NULL);
 
    pthread_rwlock_destroy(&rwlock); // 销毁读写锁
    return 0;
}

需要注意的是,读写锁采用共享/排他的锁控制策略。当有读者线程持有读锁时,其他读者线程可以继续获取读锁;但当有写者线程持有写锁时,其他任何读者线程或写者线程都无法获取读或写锁。

读写锁的实现原理:

在这里插入图片描述

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

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

相关文章

将真实世界带入实验室—如何使用ALPS进行网络损伤仿真测试

不完美的真实世界网络 不同于稳定、可控的传统网络实验室的网络环境&#xff0c;真实世界的网络环境面临着许多挑战和风险&#xff0c;这些挑战在很大程度上增加了网络的脆弱性和复杂性&#xff1a; &#xff08;1&#xff09;物理损伤&#xff1a;真实世界的网络基础设施&am…

Java扫码点餐系统奶茶店类型堂食配送小程序源码

&#x1f964;【奶茶新风尚&#xff01;扫码点餐系统&#xff0c;堂食配送两不误】&#x1f964; &#x1f3e0;【堂食新体验&#xff1a;一键下单&#xff0c;即享美味】&#x1f3e0; 踏入心仪的奶茶店&#xff0c;不再需要排队等候点单&#xff0c;只需拿起手机&#xff0…

TongHttpServer 简介

1. 概述 随着网络技术的飞速发展,高并发大用户场景越来越普遍,单一应用服务节点已经不能满足并发需求,为了提高整个系统可靠性,扩展性,吞吐率,通常将多个应用服务器通过硬负载/软负载组成集群,负载均衡器根据不同负载算法将请求分发到各个应用服务器节点。 Tong…

飞书打卡 快捷指令

使用快捷指令定时飞书打卡 在网上找了一圈&#xff0c;只有钉钉打卡的快捷指令&#xff0c;但是公司换飞书&#xff0c;哪个打工人不怕忘记打卡呢&#xff0c;所以自己研究了一下&#xff0c;其实也很简单。 找url 问题的最关键是打开飞书的打卡界面 如果只是打开飞书APP 很…

手动上电电路(电路收藏)

SW1按下 V1栅极对地 V1通 Vout给Mcu工作 GPIO2 高电平 V2通 SW1松开 V1栅极依然通过V2对地 维持V1通 Vout。再次按下SW1 GPIO1 对地 使Mcu收到中断 将GPIO2 输出低电平 V2关 松开SW1 V1栅极悬空 V1断开 Vout被截断

大数据基础:Spark重要知识汇总

文章目录 Spark重要知识汇总 一、Spark 是什么 二、Spark 四大特点 三、Spark框架模块介绍 3.1、Spark Core的RDD详解 3.1.1、什么是RDD 3.1.2、RDD是怎么理解的 四、Spark 运行模式 4.1、Spark本地模式介绍 4.2、Spark集群模式 Standalone 4.3、Spark集群模式 Stan…

中国十大顶级哲学家,全球公认的伟大思想家颜廷利:人类为何拥有臀部

人类为何拥有臀部&#xff1f;若众生皆无此部位&#xff0c;又如何能寻得一处真正属于自己的“座位”&#xff1f;在博大精深的中国传统文化中&#xff0c;汉字“座”与“坐”均蕴含“土”字元素。在易经的智慧里&#xff0c;作为五行之一的“土”&#xff0c;象征着人类社会的…

将gitee 上的nvim 配置 从gitee 上下载下来,并配置虚拟机

首先是下载 gitee 上的配置。 然后是 配置 tmux 然后是配置nvim . 1 在init.lua 文件中注释掉所有的与第三方插件有关的内容。 2 在packer 的文件中 &#xff0c; 注释掉所有的与 第三方插件有关的代码。 3 首先要保证 packer 能够正确的安装。 4 然后开始 安装 所有的插件…

汇川技术|CANlink、CANopen、Profibus-DP网络编辑器的使用

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 本节学习CANlink、CANopen、Profibus-DP网络编辑器的使用。 以下为学习笔记。 01 CANlink编辑器 在AC810的【网络组态】中未看到CANlink主站的功能&#xff0c;所以先简单了解&#xff0c;等具体使用时再具体查看。 …

2024最全RabbitMQ集群方案汇总

之前在网上找rabbitmq集群方案有哪几种时&#xff0c;基本上看到的答案都不太一样&#xff0c;所以此文的主要目的是梳理一下rabbitmq集群方案&#xff0c;对rabbitmq集群方案的笔记并不是搭建的笔记。 总结了一些文章&#xff0c;rabbitmq集群大概有五种方案&#xff1a;普通…

一文搞懂网络IO和java中的IO模型

目录 1.绪论 2.IO分类 3.用户空间和内核空间 4.同步阻塞IO 5.同步非阻塞IO 6.IO多路复用 6.1 基本原理 6.2 linux对IO多路复用的实现方式 6.3.1 select 1.实现原理 2.缺点 6.3.2 poll 1.实现原理 6.3.3 epoll 1.epoll数据结构 2.epoll的函数 3.epoll的优点 4…

【实践出真知】使用Docusaurus将md文档组织起来就是一个网站(写API文档,写教程、写日记、写博客的有福了)

文章目录 前言一、Docusaurus 是什么&#xff1f;二、一键生成网站框架并预览1. 系统需求2. 脚手架项目网站&#xff08;一键生成网站框架&#xff09;3. 生成的目录内容4. 网站运行与展示 总结 前言 前段时间&#xff0c;学习Flet&#xff0c;访问到Flet中文网&#xff0c;被…

魔方财务新版QRuser用户中心主题

本主题支持魔方财务3.5.7版本&#xff01;可自由切换魔方财务3.5.7版本与其他版本。本主题基于官方default开发&#xff0c;主要面向企业&#xff0c;三端自适应&#xff0c;支持并完美适配多语言。界面精美&#xff0c;简洁清新&#xff0c;主题内新增多处bootstrap-select的调…

软考系统架构师-计算机网络基础

目录 3.1 网络的基本概念 3.2 通信技术 3.3 网络技术 3.4 组网技术 1.网络设备及其工作层级 2.网络协议 &#xff08;1&#xff09;应用层协议。 &#xff08;2&#xff09;传输层协议。 &#xff08;3&#xff09;网络层协议。 3&#xff0e;交换机 4&#xff0e…

Speckly:基于Speckle文档的RAG智能问答机器人

前言 Speckly 是一个基于 检索增强生成 (RAG) 技术的智能问答机器人&#xff0c;它能像一位经验丰富的工程师&#xff0c;理解你的问题&#xff0c;并从 Speckle 文档中精准地找到答案。更厉害的是&#xff0c;它甚至可以帮你生成代码片段&#xff01; &#x1f680; 本文将详…

linux:基本权限

1、权限与用户之间的关系 在Linux系统中&#xff0c;针对文件定义了三种身份&#xff0c;分别是属主(owner)、属组(group)、其他人(others)&#xff0c;每一种身份又对应三种权限&#xff0c;分别是可读(readable)、可写(writable)、可执行(excutable)。 2、如何修改一个文件的…

快团团等社区团购类小区物资团购怎么按商品批量退款?

疫情期间&#xff0c;小区物资团的配送需要达到一定的起送件数&#xff0c;对于一些没有达到起送件数的商品&#xff0c;如何快速地批量退款呢&#xff1f;按照下列操作&#xff0c;只需四步&#xff0c;就可以对某一商品批量退款。 第1步&#xff1a;进入团购页面&#xff0c…

JavaScript(二)变量

一、两种注释方式 // 这是当行注释/* 这是多行注释 这是多行注释 */二、变量是什么 变量就是一个可以存放“数值”的容器&#xff0c;这个“数值”可以是数字、字符串、函数等。 变量不是数值本身&#xff0c;它是一个用于存储数值的容器&#xff0c;你可以把变量想象成一个个…

解决断点问题导致项目没有完全启动bug

场景&#xff1a; 项目启动正常&#xff0c;启动日志也正常打印&#xff0c;但是无法判断是否启动完毕&#xff0c;访问接口也进不了服务 原因&#xff1a; 启动前调试项目打断点时 不晓得打到了某个层面的断点 具体是哪忘了&#xff0c;导致项目没有完全启动&#xff0c;启…

WIFI7:引领智能驾驶新未来

近年来&#xff0c;智能驾驶技术飞速发展&#xff0c;从最初的初级的辅助驾驶逐步迈向高度自动驾驶&#xff0c;这一变化历程深刻依赖的是高效、稳定且前沿的无线通信技术的支撑。WIFI7&#xff0c;作为无线通信领域的最新里程碑&#xff0c;凭借其前所未有的性能提升与功能拓展…