线程池-大丙

  1. 线程池原理
    我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务呢?

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件), 则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

在各个编程语言的语种中都有线程池的概念,并且很多语言中直接提供了线程池,作为程序猿直接使用就可以了,下面给大家介绍一下线程池的实现原理:

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

任务队列,存储需要处理的任务,由工作的线程来处理这些任务
通过线程池提供的 API 函数,将一个待处理的任务添加到任务队列,或者从任务队列中删除
已处理的任务会被从任务队列中删除
线程池的使用者,也就是调用线程池函数往任务队列中添加任务的线程就是生产者线程
工作的线程(任务队列任务的消费者) ,N个
线程池中维护了一定数量的工作线程,他们的作用是是不停的读任务队列,从里边取出任务并处理
工作的线程相当于是任务队列的消费者角色,
如果任务队列为空,工作的线程将会被阻塞 (使用条件变量 / 信号量阻塞)
如果阻塞之后有了新的任务,由生产者将阻塞解除,工作线程开始工作
管理者线程(不处理任务队列中的任务),1个
它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测
当任务过多的时候,可以适当的创建一些新的工作线程
当任务过少的时候,可以适当的销毁一些工作的线程

任务队列

// 任务结构体
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
};
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;
}

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;
}


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);
}

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

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

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;
}

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;
}

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);
}


作者: 苏丙榅
链接: https://subingwen.cn/linux/threadpool/#4-%E5%A4%B4%E6%96%87%E4%BB%B6%E5%A3%B0%E6%98%8E
来源: 爱编程的大丙
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
你这段描述非常清晰地表达了你期望的 **升级包处理流程**: ``` [包1] → [拷贝] ──→ [线程池-任务1: 解密验签 & 解压](并发) [包2] → [拷贝] ──→ [线程池-任务2: 解密验签 & 解压](并发) [包3] → [拷贝] ──→ [线程池-任务3: 解密验签 & 解压](并发) [包4] → [拷贝] ──→ [线程池-排队等待线程释放] [包5] → [拷贝] ──→ [线程池-排队等待线程释放] ``` --- ## ✅ 你当前代码是否满足这个期望? ### 1. `for (File packageFile : packages)` 是什么? 这是一个 **增强型 for 循环**,用于遍历 `packages` 数组中的每个升级包文件。 ```java for (File packageFile : packages) { new SerialCopyTask(packageFile, parallelExecutor).run(); } ``` 这行代码的作用是: - 依次取出每个包。 - 创建一个 `SerialCopyTask` 并调用 `.run()`。 > ❗️注意:`.run()` 并 **不会启动新线程**,它只是在当前线程中执行 `run()` 方法。 --- ## 🚫 当前行为分析 ### 你当前的代码: ```java for (File packageFile : packages) { new SerialCopyTask(packageFile, parallelExecutor).run(); } ``` 这意味着: - **每个包的拷贝阶段是串行执行的**(因为 `.run()` 没有交给线程池执行)。 - **拷贝完成后,`SerialCopyTask` 提交 `PostCopyProcessingTask` 到线程池**,所以 **解密验签和解压是并行的**。 --- ## ✅ 你期望的行为(拷贝串行,后续并行) ``` [包1] → [拷贝] ──→ [线程池-任务1: 解密验签 & 解压](并发) [包2] → [拷贝] ──→ [线程池-任务2: 解密验签 & 解压](并发) [包3] → [拷贝] ──→ [线程池-任务3: 解密验签 & 解压](并发) ``` --- ## ✅ 实现你期望的方式 ### ✅ 1. 修改拷贝阶段为 **串行提交到线程池** 你当前的问题是: > **拷贝阶段是串行执行的,但不是通过线程池控制的,而是直接在主线程中调用 `.run()`** 要实现拷贝串行,你可以: - 使用一个 **单线程的线程池** 来串行执行拷贝任务。 ```java ExecutorService serialCopyExecutor = Executors.newSingleThreadExecutor(); ``` 然后: ```java for (File packageFile : packages) { serialCopyExecutor.submit(new SerialCopyTask(packageFile, parallelExecutor)); } ``` 这样,**拷贝任务会串行提交给线程池执行**,而 **不会阻塞主线程**。 --- ### ✅ 2. 完整修改如下: ```java public void processUpdatePackagesInParallel(File[] packages) { if (packages == null || packages.length == 0) { notifyListener(SUCCESS, "无升级包"); return; } // 初始化每个包的总小和进度 for (File packageFile : packages) { packageTotalSizeMap.put(packageFile.getName(), packageFile.length()); packageProgressMap.put(packageFile.getName(), 0.0); packageStageMap.put(packageFile.getName(), STAGE_COPY); } allPackagesLatch = new CountDownLatch(packages.length); int parallelThreads = Math.min(packages.length, 3); // 限制最并行线程数 ExecutorService parallelExecutor = Executors.newFixedThreadPool(parallelThreads); ExecutorService serialCopyExecutor = Executors.newSingleThreadExecutor(); // 串行拷贝线程池 // 串行拷贝阶段(提交到单线程池) for (File packageFile : packages) { serialCopyExecutor.submit(new SerialCopyTask(packageFile, parallelExecutor)); } // 关闭串行拷贝线程池的提交通道 serialCopyExecutor.shutdown(); // 在后台等待所有任务完成 new Thread(() -> { try { allPackagesLatch.await(); if (isCancelled) { notifyListener(ERROR_USER_CANCELED, "用户取消"); } else if (updateSuccess && mapDataController.checkInstallationStatus()) { notifyProgress("校验成功"); Log.d(TAG, "最终进度:" + packageProgressMap); notifyListener(SUCCESS, "更新成功,请重启车机"); } else { notifyProgress("校验失败"); notifyListener(ERROR_FILE_VERIFY_FAILED, "文件校验失败"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); notifyListener(ERROR_UNEXPECTED, "任务中断"); } }).start(); } ``` --- ## ✅ 修改后的流程图 ``` [包1] → [串行线程池-SerialCopyTask] → [拷贝] → [线程池-PostCopyProcessingTask: 解密验签 & 解压] [包2] → [串行线程池-SerialCopyTask] → [拷贝] → [线程池-PostCopyProcessingTask: 解密验签 & 解压] [包3] → [串行线程池-SerialCopyTask] → [拷贝] → [线程池-PostCopyProcessingTask: 解密验签 & 解压] ``` --- ## ✅ 小结 | 问题 | 答案 | |------|------| | `for (File packageFile : packages)` 是什么? | 是 Java 的增强型 for 循环,用于遍历所有升级包 | | 为什么拷贝是串行? | 因为你调用了 `.run()` 而不是 `.submit()`,导致在主线程中串行执行 | | 如何实现你期望的流程? | 使用 `newSingleThreadExecutor()` 串行提交拷贝任务,后续阶段交给线程池并行处理 | | `SerialCopyTask` 是串行执行的吗? | 默认是,但必须通过单线程池保证 | | `PostCopyProcessingTask` 是并行执行的吗? | 是的,只要你提交到线程池中执行 | --- ## 📌 推荐优化点 | 优化点 | 建议 | |--------|------| | 线程池复用 | 将 `parallelExecutor` 和 `serialCopyExecutor` 提升为成员变量 | | 包优先级 | 支持优先级队列,先处理核心包 | | 异常隔离 | 一个包失败不影响其他包 | | 日志记录 | 增加详细的日志输出,便于调试 | | 进度回调 | 使用 `LiveData` 或 `Flow` 替代 `Handler` 更新 UI | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值