Linux线程池实现

news2025/4/2 5:56:56

1.线程池实现

全部代码:whb-helloworld/113

1.唤醒线程

一个是唤醒全部线程,一个是唤醒一个线程。

void WakeUpAllThread()
        {
            LockGuard lockguard(_mutex);
            if (_sleepernum)
                _cond.Broadcast();
            LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
        }

        void WakeUpOne()
        {
            _cond.Signal();
            LOG(LogLevel::INFO) << "唤醒一个休眠线程";
        }

2.创建线程

全局变量gnum的值就是要创建的线程的个数,然后把lambda表达式插入进去,Thread里面有function可以接收这个表达式,然后执行routine函数时就会执行这个表达式。emplace_back一个表达式,本质就是把这个表达式复制给这个vector<Thread>的Thread类的元素,就是传参过去。

 ThreadPool(int num = gnum) : _num(num), _isrunning(false), _sleepernum(0)
        {
            for (int i = 0; i < num; i++)
            {
                _threads.emplace_back(
                    [this]()
                    {
                        HandlerTask();
                    });
            }
        }
 using func_t = std::function<void()>;
 static void *Routine(void *args) // 属于类内的成员函数,默认包含this指针!
        {
            Thread *self = static_cast<Thread *>(args);
            self->EnableRunning();
            if (self->_isdetach)
                self->Detach();
            pthread_setname_np(self->_tid, self->_name.c_str());
            self->_func(); // 回调处理

            return nullptr;
        }

 

在C++中,捕获 this 是一种常见的做法,尤其是在使用 Lambda 表达式时。捕获 this 的主要目的是为了让 Lambda 表达式能够访问类的成员变量和成员函数。

3.开始函数

判断是否运行,没有就把状态变为true,然后范围for把线程都启动,并打印日志信息

 void Start()
        {
            if (_isrunning)
                return;
            _isrunning = true;
            for (auto &thread : _threads)
            {
                thread.Start();
                LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();
            }
        }

 4.关掉拷贝和赋值

ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

5.单例模式

 先判断是否创建了实例,没有就加锁,第二个判断也是判断是否有实例,没有就创建并执行开始函数,最后返回inc这个指向实例的指针。第一个判断是为了下次不用一直加锁,只要保证第一次实例加锁就行,后面就可以不用加锁了,提高效率。

static ThreadPool<T> *GetInstance()
        {
            if (inc == nullptr)
            {
                LockGuard lockguard(_lock);

                LOG(LogLevel::DEBUG) << "获取单例....";
                if (inc == nullptr)
                {
                    LOG(LogLevel::DEBUG) << "首次使用单例, 创建之....";
                    inc = new ThreadPool<T>();
                    inc->Start();
                }
            }

            return inc;
        }

 6.停止函数和回收函数

根据_isrunning判断状态,如果是运行状态,就变为false,然后唤醒所有线程开始执行任务,

join就回收这些线程的信息。

 void Stop()
        {
            if (!_isrunning)
                return;
            _isrunning = false;

            // 唤醒所有的线层
            WakeUpAllThread();
        }
        void Join()
        {
            for (auto &thread : _threads)
            {
                thread.Join();
            }
        }

 

7.处理任务函数

开始是把线程名字放到name里面,进到循环里面,要加锁保证sleepernum值不会因为并发而改变,循环里面第一个循环只有任务队列为空且线程是运行才会进到等待队列进行休眠,sleep值是计算休眠的个数,判断是线程不运行且任务队列为空就退出了,没退出就说明有任务,就取出队列的任务给t,最后就可以t()执行任务。

void HandlerTask()
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name));
            while (true)
            {
                T t;
                {
                    LockGuard lockguard(_mutex);
                    // 1. a.队列为空 b. 线程池没有退出
                    while (_taskq.empty() && _isrunning)
                    {
                        _sleepernum++;
                        _cond.Wait(_mutex);
                        _sleepernum--;
                    }
                    // 2. 内部的线程被唤醒
                    if (!_isrunning && _taskq.empty())
                    {
                        LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
                        break;
                    }

                    // 一定有任务
                    t = _taskq.front(); // 从q中获取任务,任务已经是线程私有的了!!!
                    _taskq.pop();
                }
                t(); // 处理任务,需/要在临界区内部处理吗?1 0
            }
        }
  1. pthread_getname_np(pthread_self(), name, sizeof(name));

    • pthread_getname_np 是一个 POSIX 线程库(pthread)提供的函数,用于获取线程的名称。

    • pthread_self() 是一个函数,返回当前线程的线程ID(pthread_t 类型)。

    • name 是目标数组,用于存储线程名称。

    • sizeof(name) 是目标数组的大小,确保不会超出数组的存储范围。

8.入任务函数

判断状态,运行就进去加锁,插入数据加锁避免并发插入数据,在判断线程大小和休眠个数是否相等,一样就说明都休眠了,要唤醒线程进行处理任务。

bool Enqueue(const T &in)
        {
            if (_isrunning)
            {
                LockGuard lockguard(_mutex);
                _taskq.push(in);
                if (_threads.size() == _sleepernum)
                    WakeUpOne();
                return true;
            }
            return false;
        }

9.完整代码

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"

// .hpp header only

namespace ThreadPoolModule
{
    using namespace ThreadModlue;
    using namespace LogModule;
    using namespace CondModule;
    using namespace MutexModule;

    static const int gnum = 5;
    template <typename T>
    class ThreadPool
    {
    private:
        void WakeUpAllThread()
        {
            LockGuard lockguard(_mutex);
            if (_sleepernum)
                _cond.Broadcast();
            LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
        }

        void WakeUpOne()
        {
            _cond.Signal();
            LOG(LogLevel::INFO) << "唤醒一个休眠线程";
        }

        ThreadPool(int num = gnum) : _num(num), _isrunning(false), _sleepernum(0)
        {
            for (int i = 0; i < num; i++)
            {
                _threads.emplace_back(
                    [this]()
                    {
                        HandlerTask();
                    });
            }
        }
        void Start()
        {
            if (_isrunning)
                return;
            _isrunning = true;
            for (auto &thread : _threads)
            {
                thread.Start();
                LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();
            }
        }

        ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

    public:
        static ThreadPool<T> *GetInstance()
        {
            if (inc == nullptr)
            {
                LockGuard lockguard(_lock);

                LOG(LogLevel::DEBUG) << "获取单例....";
                if (inc == nullptr)
                {
                    LOG(LogLevel::DEBUG) << "首次使用单例, 创建之....";
                    inc = new ThreadPool<T>();
                    inc->Start();
                }
            }

            return inc;
        }
        void Stop()
        {
            if (!_isrunning)
                return;
            _isrunning = false;

            // 唤醒所有的线层
            WakeUpAllThread();
        }
        void Join()
        {
            for (auto &thread : _threads)
            {
                thread.Join();
            }
        }
        void HandlerTask()
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name));
            while (true)
            {
                T t;
                {
                    LockGuard lockguard(_mutex);
                    // 1. a.队列为空 b. 线程池没有退出
                    while (_taskq.empty() && _isrunning)
                    {
                        _sleepernum++;
                        _cond.Wait(_mutex);
                        _sleepernum--;
                    }
                    // 2. 内部的线程被唤醒
                    if (!_isrunning && _taskq.empty())
                    {
                        LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
                        break;
                    }

                    // 一定有任务
                    t = _taskq.front(); // 从q中获取任务,任务已经是线程私有的了!!!
                    _taskq.pop();
                }
                t(); // 处理任务,需/要在临界区内部处理吗?1 0
            }
        }
        bool Enqueue(const T &in)
        {
            if (_isrunning)
            {
                LockGuard lockguard(_mutex);
                _taskq.push(in);
                if (_threads.size() == _sleepernum)
                    WakeUpOne();
                return true;
            }
            return false;
        }
        ~ThreadPool()
        {
        }

    private:
        std::vector<Thread> _threads;
        int _num; // 线程池中,线程的个数
        std::queue<T> _taskq;
        Cond _cond;
        Mutex _mutex;

        bool _isrunning;
        int _sleepernum;

        // bug??
        static ThreadPool<T> *inc; // 单例指针
        static Mutex _lock;
    };

    template <typename T>
    ThreadPool<T> *ThreadPool<T>::inc = nullptr;

    template <typename T>
    Mutex ThreadPool<T>::_lock;

}

2.单例模式与设计模式

类里面static修饰变量,则变量是属于类的,而不是属于某个特定对象,无论创建多少个对象,static变量都只有一份,所有对象共享一份static变量。

static修饰方法,则方法也是属于类的,不能访问非静态成员(变量和方法),因为非静态成员需要对象才能访问,所有对象共享一个static方法

而锁加static,是为了保证唯一锁,不然创建多个锁没有static,则每个锁都是不一样的,就等于一个门上有很多钥匙孔,static就可以保证只有一个钥匙孔和一把钥匙。

单例模式

单例模式是属于一种创建型设计模式,确保一个类在整个应用程序中只有一个实例,提供一个全局访问点来访问这个实例这个实例。

饿汉模式

饿汉模式在类被加载时就立即创建实例,而不是在第一次需要实例时才创建。这种方式特定是实例创建早,通长用于资源不多且创建实例开销较小的情况,因为每次执行都要创建一个实例,如果实例体积大就会占用很多内存,所以适用于资源不多的。

饿汉模式特点

立即实例化:类加载时就创建了单例实例,不管是否有其它地方需要这个实例

线程安全:由于实例在加载时就创建好了,线程安全问题得到天然的解决

浪费资源:单例对象的创建开销大,且开辟的空间不一定用的上,会有低效率

当两个线程同时访问一个类,并且这个类采用立即实例化的方式创建单例时,由于实例化是在类加载时完成的,所以实际上不存在两个线程同时创建实例的情况。下面是具体的原因和过程:

1. **类加载机制**:在Java中,类加载是由类加载器完成的。类加载器会保证一个类只被加载一次。当第一个线程触发类的加载时,类加载器会同步这个加载过程,确保其他线程在当前线程完成加载之前不会进入类的加载过程。

2. **静态初始化**:在类加载的过程中,如果类中包含静态初始化块或者静态变量初始化,这些操作会在类加载时执行。如果单例实例是在静态初始化块中创建的,那么这个实例的创建过程是同步的,即在任何线程看到类的加载完成之前,实例已经被创建好了。

3. **线程安全保证**:由于类加载和静态初始化是同步的,这意味着当第一个线程触发类的加载并创建实例时,其他线程会被阻塞,直到实例创建完成。因此,不会有多个线程同时创建实例的情况发生。

4. **可见性**:在Java中,静态变量的初始化具有可见性保证。一旦静态变量被初始化,它对所有线程都是可见的。这意味着一旦单例实例被创建,所有线程都可以看到它。

总结来说,即使在多线程环境中,由于类加载和静态初始化的同步机制,以及静态变量的可见性保证,立即实例化的单例模式可以确保在任何时候都只有一个实例被创建,从而保证了线程安全。这就是为什么即使有两个线程同时访问一个采用立即实例化的类,也不会导致线程安全问题的原因。

饿汉模式单例实现

template<typename T>
class Singleton
{
	static T data;
public:
	static T* GetInstance()
	{
		return &data;
	}
};

懒汉模式

是一种单例模式的实现方式,就需要在第一次实例时创建对象。特点是推迟实例化,从而避免对不必要的资源消耗。

懒汉单例模式特点

延迟实例化:对象仅在第一次被请求时才进行创建,从而提高程序的效率

资源节省:在不需要实例的情况下,避免了资源的浪费,优化了资源的使用

线程安全问题:在多线程环境下,可能需要额外的同步机制来确保线程安全问题,确保实例的唯一性。

 懒汉单例模式的实现

template<typename T>
class Singleton
{
	static T* inst;
public:
	static T* GetInstance()
	{
		if (inst == nullptr)
		{
			inst = new T();
		}
	return inst;
	}
};

上面线程池以懒汉模式实现

3.线程安全和重入问题

概念
线程安全:就是多个线程在访问共享资源时,能够正确地执⾏,不会相互⼲扰或破坏彼此的执⾏结
果。⼀般⽽⾔,多个线程并发同⼀段只有局部变量的代码时,不会出现不同的结果。但是对全局变量 或者静态变量进⾏操作,并且没有锁保护的情况下,容易出现该问题。
重⼊:同⼀个函数被不同的执⾏流调⽤,当前⼀个流程还没有执⾏完,就有其他的执⾏流再次进⼊, 我们称之为重⼊。⼀个函数在重⼊的情况下,运⾏结果不会出现任何不同或者任何问题,则该函数被 称为可重⼊函数,否则,是不可重⼊函数。
常⻅的线程不安全的情况
不保护共享变量的函数
函数状态随着被调⽤,状态发⽣变化的函数
返回指向静态变量指针的函数
调⽤线程不安全函数的函数
常⻅不可重⼊的情况
调⽤了malloc/free函数,因为malloc函数
是⽤全局链表来管理堆的
调⽤了标准I/O库函数,标准I/O库的很多实
现都以不可重⼊的⽅式使⽤全局数据结构
可重⼊函数体内使⽤了静态的数据结构
常⻅的线程安全的情况
每个线程对全局变量或者静态变量只有读取
的权限,⽽没有写⼊的权限,⼀般来说这些
线程是安全的
类或者接⼝对于线程来说都是原⼦操作
多个线程之间的切换不会导致该接⼝的执⾏
结果存在⼆义性
常⻅可重⼊的情况
不使⽤全局变量或静态变量
不使⽤ malloc或者new开辟出的空间
不调⽤不可重⼊函数
不返回静态或全局数据,所有数据都有函数
的调⽤者提供
使⽤本地数据,或者通过制作全局数据的本
地拷⻉来保护全局数据
可重⼊与线程安全联系
函数是可重⼊的,那就是线程安全的(其实知道这⼀句话就够了)
函数是不可重⼊的,那就不能由多个线程使⽤,有可能引发线程安全问题
如果⼀个函数中有全局变量,那么这个函数既不是线程安全也不是可重⼊的。
可重⼊与线程安全区别
可重⼊函数是线程安全函数的⼀种
线程安全不⼀定是可重⼊的,⽽可重⼊函数则⼀定是线程安全的。
如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重⼊函数若锁还
未释放则会产⽣死锁,因此是不可重⼊的。
注意:
如果不考虑信号导致一个执行流重复进入函数这种情况,线程安全和重入在安全角度不做区分,但是线程安全侧重说明线程访问公共资源的安全问题情况,表项的是并发线程的特点,可重入描述的是一个函数是否能被重复进入,表示的是函数的特点。

4.常见锁的概念

死锁是指在⼀组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站⽤不会
释放的资源⽽处于的⼀种永久等待状态。
为了⽅便表述,假设现在线程A,线程B必须同时持有锁1和锁2,才能进⾏后续资源的访问

 

 死锁的四个必要条件

互斥条件:一个资源每次只能被一个执行流使用

请求与保持条件:一个执行流因请求资源而阻塞,对已获得的资源保持不放

 

5.STL,智能指针和线程安全

 STL的容器不是线程安全的,STL的设计初衷是将性能挖掘到极致,而一旦设计到加锁保证线程安全,会对性能造成巨大影响,而且对于不同的容器,加锁的方式不同。这样STL默认是线程不安全的,需要自己保证线程安全。

智能指针unique_ptr,由于只是在当前代码块内生效,不涉及线程安全问题。

对于shared_ptr,多个对象需要共用一个引用计数变量,就会存在线程安全问题。标准库解决了这个问题,基于原子操作的(CAS)的方式保证了shared_ptr能够高效,原子的操作引用计数。

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

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

相关文章

Linux《进程概念(上)》

在之前的Linux学习当中我们已经了解了基本的Linux指令以及基础的开发工具的使用&#xff0c;那么接下来我们就要开始Linux当中一个非常重要的部分的学习——进程&#xff0c;在此进程是我们之后Linux学习的基础&#xff0c;并且通过进程的学习会让我们了解更多的操作系统的相关…

【算法】并查集基础讲解

一、定义 一种树型的数据结构&#xff0c;用于处理一些不相交集合的合并及查询问题。思想是用一个数组表示了整片森林&#xff08;parent&#xff09;&#xff0c;树的根节点唯一标识了一个集合&#xff0c;只要找到了某个元素的的树根&#xff0c;就能确定它在哪个集合里。 …

C++ STL常用算法之常用集合算法

常用集合算法 学习目标: 掌握常用的集合算法 算法简介: set_intersection // 求两个容器的交集 set_union // 求两个容器的并集 set_difference // 求两个容器的差集 set_intersection 功能描述: 求两个容器的交集 函数原型: set_intersection(iterator beg1, iterat…

日程公布| 第八届地球空间大数据与云计算前沿大会与集中学习(3号通知)

日程公布| 第八届地球空间大数据与云计算前沿大会与集中学习&#xff08;3号通知&#xff09; 日程公布| 第八届地球空间大数据与云计算前沿大会与集中学习&#xff08;3号通知&#xff09;

Linux C语言调用第三方库,第三方库如何编译安装

在 Linux 环境下使用 C 语言调用第三方库时&#xff0c;通常需要先对第三方库进行编译和安装。以下为你详细介绍一般的编译安装步骤&#xff0c;并给出不同类型第三方库&#xff08;如使用 Makefile、CMake 构建系统&#xff09;的具体示例。 一般步骤 1. 获取第三方库源码 …

leetcode -编辑距离

为了求解将 word1 转换成 word2 所需的最少操作数&#xff0c;可以使用动态规划。以下是详细的解决方案&#xff1a; ### 方法思路 1. **定义状态** dp[i][j] 表示将 word1 的前 i 个字符转换成 word2 的前 j 个字符所需的最少操作数。 2. **状态转移方程** - 如果 word1[…

字节开源版Manus来袭

字节开源版Manus来袭 项目地址&#xff1a;https://github.com/langmanus/langmanus/blob/main/README_zh.md 在人工智能领域&#xff0c;Manus的出现无疑是一颗重磅炸弹&#xff0c;它凭借强大的通用Agent能力&#xff0c;迅速吸引了全球开发者和AI爱好者的目光。然而&#…

论文阅读笔记——PointVLA: Injecting the 3D World into Vision-Language-Action Models

PointVLA 论文 现有的 VLA 基于 2D 视觉-语言数据表现良好但缺乏 3D 几何先验导致空间推理缺陷。传统方案&#xff1a;1&#xff09;3D->2D 投影&#xff0c;造成几何信息损失&#xff1b;2&#xff09;3D 数据集少。PointVLA 保留原有 VLA&#xff0c;提取点云特征&#xf…

在win11 环境下 新安装 WSL ubuntu + 换国内镜像源 + ssh + 桌面环境 + Pyhton 环境 + vim 设置插件安装

在win11 环境下 新安装 WSL ubuntu ssh gnome 桌面环境 Pyhton 环境 vim 设置插件安装 简单介绍详细流程换国内镜像源安装 ssh 桌面环境python 环境vim 设置插件安装 简单介绍 内容有点长&#xff0c;这里就先简单描述内容了。主要是快速在 Win11 搭建一个 wsl 的 linux 环…

基于springboot课程学习与互动平台(源码+lw+部署文档+讲解),源码可白嫖!

摘要 随着我国经济的高速发展与人们生活水平的日益提高&#xff0c;人们对生活质量的追求也多种多样。尤其在人们生活节奏不断加快的当下&#xff0c;人们更趋向于足不出户解决生活上的问题&#xff0c;线上管理系统展现了其蓬勃生命力和广阔的前景。与此同时&#xff0c;在此…

通俗易懂的大模型原理

十分钟揭秘DeepSeek原理&#xff0c;通俗易懂的大语言模型科普&#xff01;_哔哩哔哩_bilibili 最基础原理&#xff0c;x是输入&#xff0c;y是输出。上百万和上百亿的参数 将一句话转化为数字向量 一句话就是向量矩阵 输入矩阵和参数矩阵进行计算得出输出矩阵&#xff0c;因为…

热门索尼S-Log3电影感氛围旅拍LUTS调色预设 Christian Mate Grab - Sony S-Log3 Cinematic LUTs

热门索尼S-Log3电影感氛围旅拍LUTS调色预设 Christian Mate Grab – Sony S-Log3 Cinematic LUTs 我们最好的 Film Look S-Log3 LUT 的集合&#xff0c;适用于索尼无反光镜相机。无论您是在户外、室内、风景还是旅行电影中拍摄&#xff0c;这些 LUT 都经过优化&#xff0c;可为…

【jQuery】插件

目录 一、 jQuery插件 1. 瀑布流插件&#xff1a; jQuery 之家 http://www.htmleaf.com/ 2. 图片懒加载&#xff1a; jQuery 插件库 http://www.jq22.com/ 3. 全屏滚动 总结不易~ 本章节对我有很大收获&#xff0c;希望对你也是~~~ 一、 jQuery插件 jQuery 功能…

MATLAB导入Excel数据

假如Excel中存在三列数据需要导入Matlab中。 保证该Excel文件与Matlab程序在同一目录下。 function [time, voltage, current] test(filename)% 读取Excel文件并提取时间、电压、电流数据% 输入参数:% filename: Excel文件名&#xff08;需包含路径&#xff0c;如C:\data\…

孤码长征:破译PCL自定义点云注册机制源码迷局——踩坑实录与架构解构

在之前一个博客《一文搞懂PCL中自定义点云类型的构建与函数使用》中&#xff0c;清晰地介绍了在PCL中点云的定义与注册方法。我的一个读者很好奇其内部注册的原理以及机制&#xff0c;再加上最近工作中跟猛男开发自定义点云存储的工作&#xff0c;借着这些需求&#xff0c;我也…

Centos 7 搭建 jumpserver 堡垒机

jumpserver 的介绍 1、JumpServer 是完全开源的堡垒机, 使用 GNU GPL v2.0 开源协议, 是符合4A 的专业运维审计系统 1)身份验证 / Authentication 2)授权控制 / Authorization 3)账号管理 / Accounting 4)安全审计 / Auditing 2、JumpServer 使用 Python / Django 进行开…

封装了一个优雅的iOS全屏侧滑返回工具

思路 添加一个全屏返回手势&#xff0c;UIPangesturerecognizer, 1 手势开始 在手势开始响应的时候&#xff0c;将navigationController的delegate代理设置为工具类&#xff0c;在工具类中执行代理方法&#xff0c;- (nullable id )navigationController:(UINavigationControll…

HCIP-6 DHCP

HCIP-6 DHCP DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09; 手工配置网络参数存在的问题 灵活性差 容易出错 IP地址资源利用率低 工作量大 人员素质要求高 DHCP服务器按照如下次序为客户端选择IP地址: ①DHCP服务器的数…

opencv图像处理之指纹验证

一、简介 在当今数字化时代&#xff0c;生物识别技术作为一种安全、便捷的身份验证方式&#xff0c;正广泛应用于各个领域。指纹识别作为生物识别技术中的佼佼者&#xff0c;因其独特性和稳定性&#xff0c;成为了众多应用场景的首选。今天&#xff0c;我们就来深入探讨如何利…

记一道CTF题—PHP双MD5加密+”SALT“弱碰撞绕过

通过分析源代码并找到绕过限制的方法&#xff0c;从而获取到flag&#xff01; 部分源码&#xff1a; <?php $name_POST[username]; $passencode(_POST[password]); $admin_user "admin"; $admin_pw get_hash("0e260265122865008095838959784793");…