基于任务组的线程池(SequentialThreadPool)

一、背景

考虑有多个任务,比如有5个数据采集设备,需要对每个设备采集的数据进行处理,单个设备的数据需要按照数据产生的顺序进行处理,每个设备产生的频率不一致。应该如何处理上述需求?

  • 方案一:
    多线程处理:对每个设备采用一个线程进行处理,确保了设备的数据顺序。
    弊端:当设备增加时,浪费了大量线程;对于频率低的设备线程利用率低,对于频率高的设备处理能力可能不足。
  • 方案二:
    线程池处理:虽然能很好的利用资源,但无法保证单个设备的数据有序性。
  • 方案三:
    协程:协程能较好的处理此问题,但需要C++20支持,且无栈协程具有传染性。

基于上述情况,提出了一种按照任务组进行顺序执行的线程池。

二、简介

基于C++11实现的一个head-only的线程池。其区别于普通线程池在于:可以对任务进行分组,同一个组内的任务按照入队顺序进行执行,不同组之间的任务抢占式执行。线程资源平衡利用,避免出现某个线程利用率高,某个线程利用率低的问题。

三、实现原理

将任务分组放入对应任务组队列,每次从其中一个任务队列中取走一个任务,此时任务当前任务组处于锁定,当此任务完成后此任务组解锁,可再次取走任务进行执行。详细步骤图解如下:

  1. 添加任务时,将任务按照指定的组放入相应队列。如下图所示,将任务放入ABC三个任务组,同时将ABC三个组加入可运行任务组队列。(可运行任务组队列表示当前处于可运行状态的任务分组)。
    在这里插入图片描述
  2. 从可运行任务组队列中取出可执行的任务组标识,并从对应任务组取走任务进行执行。下图中取走A1和B1任务进行执行,此时任务组A和任务组B处于不可继续取任务状态。(两个线程同时运行任务,故取走了两个任务)。
    在这里插入图片描述
  3. 任务B1完成执行后,此时任务组B又处于可运行状态,将任务组B加入可运行任务组队列。
    在这里插入图片描述
  4. 此时只有一个任务处于运行状态,故根据可运行任务组中的指示取走任务组C中的一个任务C1进行执行。
    在这里插入图片描述
  5. 循环执行以上步骤,即可实现任务分组有序执行。

四、源码

核心源码

  1. 添加任务
void enqueue(int group, std::function<void()> task) {
    bool waiting{};
    {
        std::lock_guard<std::mutex> lock(mutex_);
        waiting = group_tasks_[group].empty(); // 任务组为空,说明此任务组没有任务正在执行
        group_tasks_[group].emplace(std::move(task)); // 将任务放入相应组
        if (waiting)
            runnable_group_.push(group); // 将任务组ID加入可运行任务组队列
    }
    if (waiting)
        condition_.notify_one();
}
  1. 执行任务
void run() {
    while (stop_token_) {
        uint32_t group{};
        std::function<void()> task{};
        // 根据可运行任务组队列取走任务
        {
            std::unique_lock<std::mutex> lock(mutex_);
            if (!condition_.wait_for(lock, std::chrono::seconds(1), [this]() { return !runnable_group_.empty(); })) {
               continue;
            }
            group = runnable_group_.front(); // 当前可运行的任务组ID
            runnable_group_.pop();
            task = std::move(group_tasks_[group].front()); // 从对应任务组中取走任务
        }
        // 执行任务
        task();

        bool notify{};
        {
            std::lock_guard<std::mutex> lock(mutex_);
            group_tasks_[group].pop();
            // 将当前任务组ID加入可运行任务组队列
            if (!group_tasks_[group].empty()) {
                runnable_group_.push(group);
                notify = true;
            }
        }
        if (notify)
            condition_.notify_one();
    }
}

最新源码见Github: SequentialThreadPool

五、使用

5.1 示例代码

#include "SequentialThreadPool.hpp"
#include <iostream>
#include <sstream>

int main() {
    const auto start = std::chrono::high_resolution_clock::now();

    SequentialThreadPool pool(4);
    for (int task = 0; task < 30; ++task) {
        const int group = task % 5; // Task Grouping
        pool.enqueue(group, [=]() {
            const auto end = std::chrono::high_resolution_clock::now();
            const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
            const int sleep = task == 0 ? 200 : rand() % 100 + 50;
            std::stringstream ss;
            ss << "Time:" << duration << ", group:" << group << ", task:" << task
               << ", tid:" << std::this_thread::get_id() << ", sleep:" << sleep << "ms\n";
            std::cout << ss.str();
            std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
        });
    }

    return 0;
}

5.2 示例输出

Time:5,   group:0, task:0,  tid:23788, sleep:200ms
Time:5,   group:1, task:1,  tid:26944, sleep:91ms
Time:5,   group:2, task:2,  tid:27324, sleep:91ms
Time:5,   group:3, task:3,  tid:21836, sleep:91ms
Time:105, group:4, task:4,  tid:27324, sleep:117ms
Time:105, group:3, task:8,  tid:26944, sleep:117ms
Time:105, group:2, task:7,  tid:21836, sleep:117ms
Time:216, group:1, task:6,  tid:23788, sleep:91ms
Time:231, group:0, task:5,  tid:27324, sleep:84ms
Time:231, group:3, task:13, tid:26944, sleep:84ms
Time:231, group:2, task:12, tid:21836, sleep:84ms
Time:324, group:4, task:9,  tid:21836, sleep:50ms
Time:324, group:2, task:17, tid:23788, sleep:117ms
Time:324, group:1, task:11, tid:27324, sleep:50ms
Time:324, group:0, task:10, tid:26944, sleep:50ms
Time:386, group:0, task:15, tid:27324, sleep:119ms
Time:386, group:4, task:14, tid:21836, sleep:119ms
Time:386, group:3, task:18, tid:26944, sleep:119ms
Time:449, group:1, task:16, tid:23788, sleep:84ms
Time:513, group:0, task:20, tid:26944, sleep:74ms
Time:513, group:3, task:23, tid:21836, sleep:74ms
Time:513, group:2, task:22, tid:27324, sleep:74ms
Time:543, group:4, task:19, tid:23788, sleep:50ms
Time:589, group:1, task:21, tid:21836, sleep:128ms
Time:589, group:3, task:28, tid:27324, sleep:128ms
Time:589, group:2, task:27, tid:26944, sleep:128ms
Time:605, group:0, task:25, tid:23788, sleep:119ms
Time:728, group:1, task:26, tid:23788, sleep:74ms
Time:728, group:4, task:24, tid:21836, sleep:108ms
Time:838, group:4, task:29, tid:21836, sleep:112ms

5.3 示例任务执行顺序图

基于上述执行进行进行图形化后如下,组内任务顺序执行,组间任务并行执行。
在这里插入图片描述

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
libevent是一个事件驱动的网络编程库,它提供了高效的事件处理机制,可以在多种操作系统平台上运行。为了更好地利用libevent提供的优势,我们可以使用线程池来管理多个并发请求。 下面是一个基于libevent的简单线程池实现: ```c #include <event2/event.h> #include <event2/thread.h> #include <pthread.h> #define THREAD_POOL_SIZE 4 struct thread_pool { pthread_t threads[THREAD_POOL_SIZE]; struct event_base *base; }; void* thread_func(void *arg) { struct event_base *base = (struct event_base*)arg; event_base_dispatch(base); return NULL; } struct thread_pool* thread_pool_new() { struct thread_pool *pool = malloc(sizeof(struct thread_pool)); pool->base = event_base_new(); // Initialize libevent thread support evthread_use_pthreads(); // Create worker threads for (int i = 0; i < THREAD_POOL_SIZE; i++) { pthread_create(&pool->threads[i], NULL, thread_func, pool->base); } return pool; } void thread_pool_destroy(struct thread_pool *pool) { // Stop the event loop on all threads event_base_loopexit(pool->base, NULL); // Wait for all threads to exit for (int i = 0; i < THREAD_POOL_SIZE; i++) { pthread_join(pool->threads[i], NULL); } // Free the event base and the pool itself event_base_free(pool->base); free(pool); } void thread_pool_dispatch(struct thread_pool *pool, struct event *ev) { event_base_priority_init(pool->base, 2); event_base_set(pool->base, ev); event_add(ev, NULL); } ``` 这个线程池使用libevent提供的事件循环机制来处理多个并发请求。它启动了一个包含多个线程的事件循环,每个线程都可以处理事件。当一个事件需要处理时,线程池会将该事件加入到事件队列中,由空闲的线程来处理。 线程池的使用非常简单,只需要创建一个线程池对象,然后通过调用`thread_pool_dispatch`函数来将需要处理的事件加入到事件队列中即可。当不再需要使用线程池时,只需要调用`thread_pool_destroy`函数来释放线程池对象和相关资源。 需要注意的是,这个线程池的实现是比较简单的,适用于处理轻量级的请求。如果需要处理更加复杂的请求,可能需要进行更多的优化和调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值