【线程池】的原理分析及源码(C语言版)

线程池的原理分析及源码(C语言版)

centos8 连接失败 线程已满_张三和你一聊聊线程池
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

线程池

线程池是一种用于管理和复用线程的机制,通过线程池可以减少线程的创建和销毁次数,提高程序的性能和效率。线程池通常包含一个线程队列和一个任务队列,线程队列用于存储可用的线程,而任务队列用于存储待执行的任务。

下面是一个简单的线程池的原理分析和简单的C语言实现:

线程池的基本原理:

  1. 初始化: 创建一定数量的线程,并将它们放入线程队列中,初始化任务队列。

  2. 任务提交: 当有任务需要执行时,将任务添加到任务队列中。

  3. 线程执行: 线程池中的线程从任务队列中取出任务并执行。

  4. 线程复用: 执行完任务后,线程不销毁,而是继续等待新的任务。这样可以避免频繁地创建和销毁线程,提高效率。

  5. 线程阻塞: 如果任务队列为空,线程将被阻塞等待新的任务。

  6. 线程池销毁: 在程序结束时或者不再需要线程池时,销毁线程池,释放资源。

C语言实现:

下面是一个简单的C语言线程池实现,使用了pthread库:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define MAX_THREADS 5
#define MAX_TASKS 10

typedef struct {
    void (*function)(void*);  // 任务函数指针
    void *arg;  // 任务参数
} Task;

typedef struct {
    Task tasks[MAX_TASKS];  // 任务队列
    int front, rear;  // 队头和队尾
    pthread_mutex_t mutex;  // 互斥锁
    pthread_cond_t cond;  // 条件变量
} TaskQueue;

typedef struct {
    pthread_t threads[MAX_THREADS];  // 线程队列
    TaskQueue taskQueue;  // 任务队列
    int threadCount;  // 线程数量
} ThreadPool;

void initializeThreadPool(ThreadPool *pool, int threadCount);
void submitTask(ThreadPool *pool, void (*function)(void*), void *arg);
void executeTask(ThreadPool *pool);
void destroyThreadPool(ThreadPool *pool);

void* workerThread(void *arg) {
    ThreadPool *pool = (ThreadPool*)arg;
    while (1) {
        executeTask(pool);
    }
    return NULL;
}

void initializeThreadPool(ThreadPool *pool, int threadCount) {
    pool->threadCount = threadCount;
    pool->taskQueue.front = pool->taskQueue.rear = 0;
    pthread_mutex_init(&pool->taskQueue.mutex, NULL);
    pthread_cond_init(&pool->taskQueue.cond, NULL);

    for (int i = 0; i < threadCount; ++i) {
        pthread_create(&pool->threads[i], NULL, workerThread, pool);
    }
}

void submitTask(ThreadPool *pool, void (*function)(void*), void *arg) {
    pthread_mutex_lock(&pool->taskQueue.mutex);

    while ((pool->taskQueue.rear + 1) % MAX_TASKS == pool->taskQueue.front) {
        // 等待任务队列非满
        pthread_cond_wait(&pool->taskQueue.cond, &pool->taskQueue.mutex);
    }

    // 添加任务到任务队列
    pool->taskQueue.tasks[pool->taskQueue.rear].function = function;
    pool->taskQueue.tasks[pool->taskQueue.rear].arg = arg;
    pool->taskQueue.rear = (pool->taskQueue.rear + 1) % MAX_TASKS;

    // 通知线程有新任务
    pthread_cond_signal(&pool->taskQueue.cond);
    pthread_mutex_unlock(&pool->taskQueue.mutex);
}

void executeTask(ThreadPool *pool) {
    pthread_mutex_lock(&pool->taskQueue.mutex);

    while (pool->taskQueue.front == pool->taskQueue.rear) {
        // 等待任务队列非空
        pthread_cond_wait(&pool->taskQueue.cond, &pool->taskQueue.mutex);
    }

    // 取出任务并执行
    Task task = pool->taskQueue.tasks[pool->taskQueue.front];
    pool->taskQueue.front = (pool->taskQueue.front + 1) % MAX_TASKS;

    // 通知线程有新空位
    pthread_cond_signal(&pool->taskQueue.cond);
    pthread_mutex_unlock(&pool->taskQueue.mutex);

    // 执行任务
    task.function(task.arg);
}

void destroyThreadPool(ThreadPool *pool) {
    for (int i = 0; i < pool->threadCount; ++i) {
        pthread_cancel(pool->threads[i]);
    }

    pthread_mutex_destroy(&pool->taskQueue.mutex);
    pthread_cond_destroy(&pool->taskQueue.cond);
}

// 示例任务函数
void printHello(void *arg) {
    int *num = (int*)arg;
    printf("Hello from task %d\n", *num);
}

int main() {
    ThreadPool pool;
    initializeThreadPool(&pool, MAX_THREADS);

    int taskArgs[MAX_TASKS];
    for (int i = 0; i < MAX_TASKS; ++i) {
        taskArgs[i] = i;
        submitTask(&pool, printHello, &taskArgs[i]);
    }

    // 等待任务执行完成
    sleep(2);

    destroyThreadPool(&pool);

    return 0;
}

请注意,这只是一个简单的示例,实际的线程池实现可能需要更多的功能和错误处理。此外,在生产环境中,建议使用现有的线程池库,而不是自己编写线程池。(谨慎使用)

线程池库

有许多现有的库提供了线程池的实现,这些库在不同的编程语言中都有。以下是一些常用的线程池库:

  1. C语言:

    • POSIX Threads (pthread): 这是C语言中用于多线程编程的标准库,提供了线程创建、同步等功能。虽然不是专门的线程池库,但可以通过合理的设计使用pthread来实现线程池。
  2. C++语言:

    • C++ Standard Library (std::thread): C++11及以后的标准库提供了std::thread,也可以通过其他标准库组件来实现线程池。
    • Boost.Thread: Boost库提供了boost::thread,同样可以通过它来实现线程池。
  3. Java语言:

    • java.util.concurrent: Java标准库提供了Executor框架,包括ThreadPoolExecutor等实现类,用于管理线程池。
    • Guava Library: Google的Guava库提供了ListeningExecutorService等线程池相关的接口和实现。
  4. Python语言:

    • concurrent.futures: Python标准库中的concurrent.futures模块提供了线程池和进程池的实现,如ThreadPoolExecutorProcessPoolExecutor
    • ThreadPoolExecutor: 这是一个基于concurrent.futures的第三方库,提供了更多的功能。
  5. C#语言:

    • System.Threading.Tasks: .NET框架中的TaskThreadPool类提供了线程池的实现。
    • TPL (Task Parallel Library): TPL是.NET框架的一部分,提供了更高级的任务并行编程模型,包括线程池。
  6. JavaScript语言:

    • Web Workers: 在Web开发中,浏览器提供了Web Workers,可以通过它们实现类似线程池的功能。
  7. 其他语言:

    • 对于其他编程语言,通常都有类似的库或框架,提供了线程池或并发编程的支持。

这些库提供了高级抽象,简化了线程池的使用和管理,同时考虑了线程的复用、任务调度等问题。在选择库时,可以根据自己的项目需求和编程语言选择合适的库。

1.线程池工作原理

线程池的组成主要分为3个部分,这三部分配合工作就可以得到一个完整的线程池:

  1. 任务队列,存储需要处理的任务,由工作的线程来处理这些任务
  2. 工作的线程(任务队列任务的消费者),N个
  3. 管理者线程(不处理任务队列中的任务)
    • 它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测
    • 当任务过多的时候,可以适当地创建一些新的工作线程
    • 当任务过少的时候,可以适当地销毁一些工作的线程

2.定义线程池的结构

/**任务结构体 */
typedef struct Task
{
    void (*function)(void *arg); /// 泛型,兼容各种各样的数据类型
    void *arg;
} Task;

/** 线程池的结构体*/  
struct ThreadPool
{
    /** 任务队列*/
    Task *taskQ;
    int queueCapacity; // 容量
    int queueSize;     // 当前任务个数
    int queueFront;    // 队头
    int queueRear;     // 对尾
    pthread_t managerID;  // 管理者线程ID
    pthread_t *threadIDs; // 工作的线程ID
    int minNum;  // 最小的线程数
    int maxNum;  // 最大的线程数
    int busyNum; // 正在工作的线程
    int liveNum; // 存活的线程个数
    int exitNum; // 要销毁的线程个数
    pthread_mutex_t mutexPool; ///< 锁整个线程池
    pthread_mutex_t mutexBusy; ///< 锁busyNum变量
    pthread_cond_t notFull;    ///< 任务队列是不是满了
    pthread_cond_t notEmpty;   ///< 任务队列是不是空了
    int shutdown; // 是不是要销毁线程池,销毁为1,不销毁为0;
};

3.创建线程池实例

ThreadPool *threadpoolCreate(int min, int max, int queueSize)
{
    /// 初始化线程池
    ThreadPool *pool = (ThreadPool *)malloc(sizeof(ThreadPool));

    do
    {
        if (pool == NULL) {
            printf("malloc threadpool fail ...\n");
            break;
        }

        /// 初始化工作的线程
        pool->threadIDs = (pthread_t *)malloc(sizeof(pthread_t) * max);
        if (pool->threadIDs == NULL) {
            printf("malloc threadIDs fail ... \n");
            break;
        }

        // 初始化线程池的相关参数
        memset(pool->threadIDs, 0, sizeof(pthread_t) * max);
        pool->minNum = min;
        pool->maxNum = max;
        pool->busyNum = 0;
        pool->liveNum = min;
        pool->exitNum = 0;

        // 初始化相关锁和条件变量
        if (pthread_mutex_init(&pool->mutexPool, NULL) != 0 ||
            pthread_mutex_init(&pool->mutexBusy, NULL) != 0 ||
            pthread_cond_init(&pool->notEmpty, NULL) != 0 ||
            pthread_cond_init(&pool->notFull, NULL) != 0)
        {
            printf("mutex or condition init fail...\n");
            break;
        }

        // 初始化任务队列
        pool->taskQ = (Task *)malloc(sizeof(Task) * queueSize);
        pool->queueCapacity = queueSize;
        pool->queueSize = 0;
        pool->queueFront = 0;
        pool->queueRear = 0;
        
        // 线程池不销毁
        pool->shutdown = 0;
        
        // 创建线程
        pthread_create(&pool->managerID, NULL, manager, pool);
        for (int i = 0; i < min; ++i) {
            pthread_create(&pool->threadIDs[i], NULL, worker, pool);
        }
        return pool;
    } while (0);
    
    /// 释放资源
    if (pool && pool->threadIDs) {
        free(pool->threadIDs);
    }
    if (pool && pool->TaskQ) {
        free(pool->TaskQ);
    }
    if (pool) {
        free(pool);
    }
    return NULL;
}

4.工作线程的函数

void *worker(void *arg)
{
    ThreadPool *pool = (ThreadPool *)arg;

    while (1) {
        pthread_mutex_lock(&pool->mutexPool);
        // 当前任务队列是否为空
        while (pool->queueSize == 0 && !pool->shutdown) {
            // 阻塞工作线程
            pthread_cond_wait(&pool->notEmpty, &pool->mutexPool);

            // 判断是不是要销毁线程
            if (pool->exitNum > 0) {
                pool->exitNum--;
				if (pool->liveNum > pool->minNum) {
                    pool->liveNum--;
                    pthread_mutex_unlock(&pool->mutexPool);
                    threadExit(pool);
                }
            }
        }

        // 判断线程池是否被关闭了
        if (pool->shutdown) {
            pthread_mutex_unlock(&pool->mutexPool);
            threadExit(pool);
        }

        // 从任务队列中取出一个任务
        Task task;
        task.function = pool->taskQ[pool->queueFront].function;
        task.arg = pool->taskQ[pool->queueFront].arg;

        // 移动头结点
        pool->queueFront = (pool->queueFront + 1) % pool->queueCapacity;
        pool->queueSize--;

        // 解锁
        pthread_cond_signal(&pool->notFull);
        pthread_mutex_unlock(&pool->mutexPool);

        printf("thread %ld start working...\n", pthread_self());
        
        pthread_mutex_lock(&pool->mutexBusy);
        pool->busyNum++;
        pthread_mutex_unlock(&pool->mutexBusy);
        
        task.function(task.arg);
        free(task.arg);
        task.arg = NULL;
        printf("thread %ld end working...\n", pthread_self());
        
        pthread_mutex_lock(&pool->mutexBusy);
        pool->busyNum--;
        pthread_mutex_unlock(&pool->mutexBusy);
    }
    return NULL;
}

5.管理者线程的函数

void *manager(void *arg)
{
    ThreadPool *pool = (ThreadPool *)arg;
    while (!pool->shutdown) {
        // 每隔3s检测一次
        sleep(3);

        // 取出线程池中任务的数量和当前线程的数量
        pthread_mutex_lock(&pool->mutexPool);
        int queueSize = pool->queueSize;
        int liveNum = pool->liveNum;
        pthread_mutex_unlock(&pool->mutexPool);

        // 取出忙的线程的数量
        pthread_mutex_lock(&pool->mutexBusy);
        int busyNum = pool->busyNum;
        pthread_mutex_unlock(&pool->mutexBusy);

        // 添加线程
        // 任务的个数>存活的线程个数 && 存活的线程数<最大线程数
        if (queueSize > liveNum && liveNum < pool->maxNum) {
            pthread_mutex_lock(&pool->mutexPool);
            int counter = 0;
            
            /**                  最大线程数                 ?              存活的线程数    最大线程数  */
            for (int i = 0; i < pool->maxNum && counter < NUMBER && pool->liveNum < pool->maxNum; ++i) {
                if (pool->threadIDs[i] == 0) {
                    pthread_create(&pool->threadIDs[i], NULL, worker, pool);
                    counter++;
                    pool->liveNum++;
                }
            }
            pthread_mutex_unlock(&pool->mutexPool);
        }

        // 销毁线程
        // 忙的线程*2 < 存活的线程数 && 存活的线程>最小线程数
        if (busyNum * 2 < liveNum && liveNum > pool->minNum) {
            pthread_mutex_lock(&pool->mutexPool);
            pool->exitNum = NUMBER;
            pthread_mutex_unlock(&pool->mutexPool);
            
            // 让工作的线程自杀
            for (int i = 0; i < NUMBER; ++i) {
                pthread_cond_signal(&pool->notEmpty);
            }
        }
    }
    return NULL;
}

6.线程退出函数

void threadExit(ThreadPool *pool)
{
    pthread_t tid = pthread_self();
    
    for (int i = 0; i < pool->maxNum; ++i) {
        if (pool->threadIDs[i] == tid) {
            pool->threadIDs[i] = 0;
            printf("threadExit() called, %ld exiting...\n", tid);
            break;
        }
    }
    pthread_exit(NULL);
}

7.往线程池中添加任务

void threadPoolAdd(ThreadPool *pool, void (*func)(void *), void *arg)
{
    pthread_mutex_lock(&pool->mutexPool);
    
    while (pool->queueSize == pool->queueCapacity && !pool->shutdown) {
        // 阻塞生产者线程
        pthread_cond_wait(&pool->notFull, &pool->mutexPool);
    }
    if (pool->shutdown) {
        pthread_mutex_unlock(&pool->mutexPool);
        return;
    }

    // 添加任务
    pool->taskQ[pool->queueRear].function = func;
    pool->taskQ[pool->queueRear].arg = arg;
    pool->queueRear = (pool->queueRear + 1) % pool->queueCapacity;
    pool->queueSize++;

    pthread_cond_signal(&pool->notEmpty);
    pthread_mutex_unlock(&pool->mutexPool);
}

8.获取线程池中工作的线程和活着的线程数量

获取线程池中活着的线程数

int threadPoolAliveNum(ThreadPool *pool)
{
    pthread_mutex_lock(&pool->mutexPool);
    int aliveNum = pool->liveNum;
    pthread_mutex_unlock(&pool->mutexPool);
    
    return aliveNum;
}

获取线程池中工作的线程数

int threadPoolBusyNum(ThreadPool *pool)
{
    pthread_mutex_lock(&pool->mutexBusy);
    int busyNum = pool->busyNum;
    pthread_mutex_unlock(&pool->mutexBusy);
    
    return busyNum;
}

9.线程池的销毁

int threadPoolDestroy(ThreadPool *pool)
{
    if (pool == NULL) {
        return -1;
    }

    // 关闭线程池
    pool->shutdown = 1;

    // 阻塞回收管理者线程
    pthread_join(pool->managerID, NULL);

    // 唤醒阻塞的消费者线程
    for (int i = 0; i < pool->liveNum; ++i) {
        pthread_cond_signal(&pool->notEmpty);
    }

    // 释放堆内存
    if (pool->taskQ) {
        free(pool->taskQ);
    }
    if (pool->threadIDs) {
        free(pool->threadIDs);
    }

    pthread_mutex_destroy(&pool->mutexPool);
    pthread_mutex_destroy(&pool->mutexBusy);
    pthread_cond_destroy(&pool->notEmpty);
    pthread_cond_destroy(&pool->notFull);
    
    free(pool);
    pool = NULL;
    
    return 0;
}

10.使用测试

void taskFunc(void *arg)
{
    int num = *(int *)arg;
    printf("thread %ld is working, number = %d\n",
           pthread_self(), num);

    sleep(1);
}

int main()
{
    /** 创建线程池*/
    ThreadPool *pool = threadPoolCreate(3, 10, 100);
    for (int i = 0; i < 100; ++i) {
        int *num = (int *)malloc(sizeof(int));
        *num = i + 100;
        threadPoolAdd(pool, taskFunc, num);
    }
    sleep(30);
    threadPoolDestroy(pool);
    
    return 0;
}

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

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

相关文章

【第三届】:“玄铁杯”RISC-V应用创新大赛(基于yolov5和OpenCv算法 — 智能警戒哨兵)

文章目录 前言 一、智能警戒哨兵是什么&#xff1f; 二、方案流程图 三、硬件方案 四、软件方案 五、演示视频链接 总结 前言 最近参加了第三届“玄铁杯”RISC-V应用创新大赛&#xff0c;我的创意题目是基于 yolov5和OpenCv算法 — 智能警戒哨兵 先介绍一下比赛&#xf…

Spring日志完结篇,MyBatis操作数据库(入门)

目录 Spring可以对日志进行分目录打印 日志持久化&#xff08;让日志进行长期的保存&#xff09; MyBatis操作数据库(优秀的持久层框架) MyBatis的写法 开发规范&#xff1a; 单元测试的写法 传递参数 Spring可以对日志进行分目录打印 他的意思是说spring相关只打印INFO…

Android画布Canvas绘图scale,Kotlin

Android画布Canvas绘图scale&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.…

vuepress-----22、其他评论方案

vuepress 支持评论 本文讲述 vuepress 站点如何集成评论系统&#xff0c;选型是 valineleancloud, 支持匿名评论&#xff0c;缺点是数据没有存储在自己手里。市面上也有其他的方案, 如 gitalk,vssue 等, 但需要用户登录 github 才能发表评论, 但 github 经常无法连接,导致体验…

java实现网络聊天

网络聊天实现步骤&#xff08;从功能谈论方法&#xff09;&#xff1a; 客户端&#xff1a; 1.登录面板&#xff1a;注册提醒用户注册格式&#xff0c;登录账号密码不为空&#xff0c;点击登录的时候需要连接服务器端&#xff0c;启动聊天面板。&#xff08;监听用户点击登录…

logback的使用

1 logback概述 SLF4J的日志实现组件关系图如下所示。 SLF4J&#xff0c;即Java中的简单日志门面&#xff08;Simple Logging Facade for Java&#xff09;&#xff0c;它为各种日志框架提供简单的抽象接口。 SLF4J最常用的日志实现框架是&#xff1a;log4j、logback。一般有s…

LVS 三种负载均衡模式

昨天看视频了解LVS 三种负载均衡模式 &#xff0c;分别是Network Address Translation、Direct Routing、Tunneling 注&#xff1a;boardmix boardmix博思白板&#xff0c;多人实时协作的流程图&#xff0c;思维导图工具 https://boardmix.cn/ 画流程图还是很方便的

SSL 数字证书的一些细节

参考&#xff1a;TLS/SSL 协议详解(6) SSL 数字证书的一些细节1 证书验证 地址&#xff1a;https://wonderful.blog.csdn.net/article/details/77867063 参考&#xff1a;TLS/SSL协议详解 (7) SSL 数字证书的一些细节2 地址&#xff1a;https://wonderful.blog.csdn.net/articl…

Mybatis之核心配置文件详解、默认类型别名、Mybatis获取参数值的两种方式

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

聚类算法的性能度量

聚类算法的性能度量 聚类算法就是根据数据中样本与样本之间的距离或相似度&#xff0c;将样本划分为若干组&#xff0f;类&#xff0f;簇&#xff0c;其划分的原则&#xff1a;簇内样本相似、簇间样本不相似&#xff0c;聚类的结果是产生一个簇的集合。 其划分方式主要分为两…

[GWCTF 2019]我有一个数据库1

提示 信息收集phpmyadmin的版本漏洞 这里看起来不像是加密应该是编码错误 这里访问robots.txt 直接把phpinfo.php放出来了 这里能看到它所有的信息 这里并没有能找到可控点 用dirsearch扫了一遍 ####注意扫描buuctf的题需要控制扫描速度&#xff0c;每一秒只能扫10个多一个都…

基于深度学习的超分辨率图像技术一览

超分辨率(Super-Resolution)即通过硬件或软件的方法提高原有图像的分辨率&#xff0c;图像超分辨率是计算机视觉和图像处理领域一个非常重要的研究问题&#xff0c;在医疗图像分析、生物特征识别、视频监控与安全等实际场景中有着广泛的应用。 SR取得了显著进步。一般可以将现有…

网络设备的健康检查方式

网络设备的健康检查方式 L3检查 通过ICMP来检查IP地址是否正常 L4检查 通过三次握手来检查端口号是否正常 L7检查 通过真实的应用通信来检查应用程序是否正常

【CCF BDCI 2023】多模态多方对话场景下的发言人识别 Baseline 0.71 CNN 部分

【CCF BDCI 2023】多模态多方对话场景下的发言人识别 Baseline 0.71 CNN 部分 概述CNN 简介数据预处理查看数据格式加载数据集 图像处理限定图像范围图像转换加载数据 CNN 模型Inception 网络ResNet 残差网络总结参数设置 训练 Train模型初始化数据加载训练超参数训练循环 验证…

实现Django Models的数据mock

目录 一、创建测试数据 二、使用随机数据 三、使用第三方库生成数据 四、编写测试用例 五、总结 在 Django 中&#xff0c;Model 是用于定义数据库表的结构的类。有时候&#xff0c;我们需要在测试或者开发过程中&#xff0c;模拟 Model 的数据&#xff0c;而不是直接从数…

springboot基础(80):redis geospatial的应用

文章目录 前言redis geospatial如何从地图上获取经纬度springboot 的相关方法调用准备redis服务器引用的依赖预设位置的keyGEOADD 添加位置GEORADIUS 获取指定经纬度附件的停车场&#xff08;deprecated&#xff09;GEORADIUS 获取指定成员附件的停车场&#xff08;deprecated&…

SLMi8230BDCG-DG兼容Si8230BD-BS-IS 通过CQC认证隔离驱动方案探索

SLMi8230BDCG-DG 双通道1A 5.0kVRMS隔离栅极驱动器是一种具有不同配置的隔离双通道栅极驱动器。配置为高侧/低侧驱动器&#xff0c;峰值 电源 输出电流为1.0A&#xff0c;具有可编程死区&#xff08;DT&#xff09;功能。将DIS引脚拉高会同时关闭两个输出&#xff0c;并允许在D…

多合一iPhone 解锁工具:iMyFone LockWiper iOS

多合一iPhone 解锁工具 无需密码解锁 iPhone/iPad/iPod touch 上所有类型的屏幕锁定 在几分钟内解锁 iPhone Apple ID、Touch ID 和 Face ID 立即绕过 MDM 并删除 iPhone/iPad/iPod touch 上的 MDM 配置文件 支持所有 iOS 版本和设备&#xff0c;包括最新的 iOS 17 和 iPhone 1…

XML学习及应用

介绍XML语法及应用 1.XML基础知识1.1什么是XML语言1.2 XML 和 HTML 之间的差异1.3 XML 用途 2.XML语法2.1基础语法2.2XML元素2.3 XML属性2.4XML命名空间 3.XML验证3.1xml语法验证3.2自定义验证3.2.1 XML DTD3.2.2 XML Schema3.2.3PCDATA和CDATA区别3.2.4 参考 1.XML基础知识 1…

python:五种算法(DBO、WOA、GWO、PSO、GA)求解23个测试函数(python代码)

一、五种算法简介 1、蜣螂优化算法DBO 2、鲸鱼优化算法WOA 3、灰狼优化算法GWO 4、粒子群优化算法PSO 5、遗传算法GA 二、5种算法求解23个函数 &#xff08;1&#xff09;23个函数简介 参考文献&#xff1a; [1] Yao X, Liu Y, Lin G M. Evolutionary programming made…