首页 > 编程语言 > 详细

C++11 —— 使用 thread 实现线程池

时间:2020-01-01 23:54:44      阅读:161      评论:0      收藏:0      [点我收藏+]

1. 引言

在新的 C++11 标准中,引入并发编程的一些基础组件:线程(thread)互斥锁(mutex)条件变量(condition_variable) 等,凭借这些,就足够我设计一个平台无关的 线程池 组件了。下面就详细介绍一下这个线程池组件。

2. 结构设计图

技术分享图片

需要特别说明的是,这个线程池组件,在增加了“存在关联性的任务对象顺序执行”的功能后,原本的任务队列就分成了两级任务队列,目的是为了降低 “任务提交”“任务提取” 之间(属于一种生产/消费的关系)的锁竞争。

3. 源码说明

源码有点多,这里就不贴出来了,直接给下载地址:https://github.com/Gaaagaa/xthreadpool 。主要的线程池类 x_threadpool_txthreadpool.h 中已完整实现,在实际项目应用中,只需要 xthreadpool.h 这一个文件就足够了。

测试程序的编译命令:

  1. MSVC++2017:cl /EHsc main.cpp
  2. gcc :g++ -Wall -std=c++11 -lpthread -o main main.cpp

技术特点:

  1. 使用 C++11 的 thread 实现,可跨平台,亲测的编译器有 MSVC++2017、gcc 4.8.5、gcc 8.2.0;
  2. 支持传统的面向对象编程的任务对象类接口:继承抽象任务对象接口类,实现多态(可结合对象池的模式进行资源复用);
  3. 支持泛型接口的任务对象,如:C 函数接口、lambda 表达式、仿函数对象、类对象的成员函数调用;
  4. 支持动态调整工作线程的数量:通过 resize() 接口实现,该功能当前的实现方式还不够好,仍有优化的方式;
  5. 支持运行时的线程状态检测(判断当前工作线程是否需要退出);
  6. 存在关联性的任务对象可顺序执行(这一特点只针对一些特别的应用场景,后续示例中会展示)。

4. 使用说明与示例代码

4.1 启动与关闭

#include "xthreadpool.h"
#include <chrono>
#include <stdio.h>

int main(int argc, char * argv[])
{
    // 工作线程数量
    // 若为 0,将取 hardware_concurrency() 返回值的 2倍 + 1
    int nthreads = 4;

    // 线程池对象
    x_threadpool_t xht_pool;

    // 启动线程池
    if (!xht_pool.startup(nthreads))
    {
        printf("startup return false!\n");
        return -1;
    }

    //======================================
    // 提交任务对象
    // ......

    //======================================

    // 等待所有任务执行完成
    while (xht_pool.task_count() > 0)
        std::this_thread::sleep_for(std::chrono::milliseconds(1));

    // 关闭线程池
    xht_pool.shutdown();

    return 0;
}

4.2 提交“传统 C 回调函数”的任务对象

#include "xthreadpool.h"
#include <chrono>
#include <stdio.h>

void func_task(int task_id, int task_iter)
{
    int count = 1;
    do
    {
        printf("func_task[%d, %d] => count: %d\n", task_id, task_iter, count);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    } while (count++ < 10);
}

int main(int argc, char * argv[])
{
    // 线程池对象
    x_threadpool_t xht_pool;

    // 启动线程池
    if (!xht_pool.startup(0))
    {
        printf("startup return false!\n");
        return -1;
    }

    //======================================
    // 提交任务对象 : C 函数接口的任务

    for (int iter = 0; iter < 100; iter += 10)
    {
        xht_pool.submit_task_ex(func_task, iter, iter * iter);
    }

    //======================================

    // 等待所有任务执行完成
    while (xht_pool.task_count() > 0)
        std::this_thread::sleep_for(std::chrono::milliseconds(1));

    // 关闭线程池
    xht_pool.shutdown();

    return 0;
}

4.3 提交“仿函数对象”的任务对象

#include "xthreadpool.h"
#include <chrono>
#include <stdio.h>

/**
 * @struct functor_task_A
 * @brief  仿函数模式的任务对象类。
 */
struct functor_task_A
{
    // constructor/destructor
public:
    functor_task_A(int xtask_id = 0) : m_xtask_id(xtask_id) { }

public:
    void operator()() const
    {
        int count = 1;
        do
        {
            printf("functor_task_A[%d] => count: %d\n", m_xtask_id, count);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        } while (count++ < 10);
    }

    // data members
private:
    int m_xtask_id;
};

/**
 * @struct functor_task_B
 * @brief  仿函数模式的任务对象类。
 */
struct functor_task_B
{
    // constructor/destructor
public:
    functor_task_B(int xtask_id = 0) : m_xtask_id(xtask_id) { }

public:
    void operator()(int flag) const
    {
        int count = 1;
        do
        {
            printf("functor_task_B[%d, %d] => count: %d\n", m_xtask_id, flag, count);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        } while (count++ < 10);
    }

    // data members
private:
    int m_xtask_id;
};

int main(int argc, char * argv[])
{
    // 线程池对象
    x_threadpool_t xht_pool;

    // 启动线程池
    if (!xht_pool.startup(0))
    {
        printf("startup return false!\n");
        return -1;
    }

    //======================================
    // 提交任务对象 : 仿函数对象

    for (int iter = 0; iter < 100; iter += 10)
    {
        xht_pool.submit_task_ex((functor_task_A(iter)));
    }

    for (int iter = 0; iter < 100; iter += 10)
    {
        xht_pool.submit_task_ex((functor_task_B(iter)), iter * iter / 2);
    }

    //======================================

    // 等待所有任务执行完成
    while (xht_pool.task_count() > 0)
        std::this_thread::sleep_for(std::chrono::milliseconds(1));

    // 关闭线程池
    xht_pool.shutdown();

    return 0;
}

4.4 提交“调用类对象的成员函数”的任务对象

#include "xthreadpool.h"
#include <chrono>
#include <stdio.h>

/**
 * @class memfunc_task
 * @brief 调用成员函数的任务对象类。
 */
class memfunc_task
{
    // constructor/destructor
public:
    memfunc_task(int xtask_id = 0) : m_xtask_id(xtask_id) {  }

    // overrides
public:
    /**********************************************************/
    /**
     * @brief 任务对象执行流程的操作接口。
     */
    void memfunc(int task_iter)
    {
        int count = 1;
        do
        {
            printf("memfunc_task[%d, %d] => count: %d\n", m_xtask_id, task_iter, count);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        } while (count++ < 10);
    }

    // data members
private:
    int m_xtask_id;
};

int main(int argc, char * argv[])
{
    // 线程池对象
    x_threadpool_t xht_pool;

    // 启动线程池
    if (!xht_pool.startup(0))
    {
        printf("startup return false!\n");
        return -1;
    }

    //======================================
    // 提交任务对象

    // 注意,这个栈区对象的生命期需要在线程池关闭前存活,
    // 至少要保证所提交的任务都执行完成时,才可结束该对象的生命期
    memfunc_task mftask(0);

    for (int iter = 0; iter < 100; iter += 10)
    {
        xht_pool.submit_task_ex(&memfunc_task::memfunc, &mftask, iter);
    }

    //======================================

    // 等待所有任务执行完成
    while (xht_pool.task_count() > 0)
        std::this_thread::sleep_for(std::chrono::milliseconds(1));

    // 关闭线程池
    xht_pool.shutdown();

    return 0;
}

4.5 提交“重载后的任务对象类”的任务对象

#include "xthreadpool.h"
#include <chrono>
#include <stdio.h>

/**
 * @class user_task
 * @brief 用户自定义的任务对象类。
 */
class user_task : public x_task_t
{
    // constructor/destructor
public:
    user_task(int xtask_id = 0) : m_xtask_id(xtask_id) {  }

    // overrides
public:
    /**********************************************************/
    /**
     * @brief 任务对象执行流程的操作接口。
     */
    virtual void run(x_running_checker_t * xchecker_ptr) override
    {
        int count = 1;
        do
        {
            printf("[%d]user_task[%d] => count: %d\n", (int)xchecker_ptr->thread_index(), m_xtask_id, count);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        } while ((count++ < 10) && xchecker_ptr->is_enable_running());
    }

    // data members
private:
    int m_xtask_id;
};

int main(int argc, char * argv[])
{
    // 线程池对象
    x_threadpool_t xht_pool;

    // 启动线程池
    if (!xht_pool.startup(0))
    {
        printf("startup return false!\n");
        return -1;
    }

    //======================================
    // 提交任务对象 : 重载的任务对象

    for (int iter = 0; iter < 100; iter += 10)
    {
        xht_pool.submit_task((x_task_ptr_t)(new user_task(iter)));
    }

    //======================================

    // 等待所有任务执行完成
    while (xht_pool.task_count() > 0)
        std::this_thread::sleep_for(std::chrono::milliseconds(1));

    // 关闭线程池
    xht_pool.shutdown();

    return 0;
}

4.6 提交“lambda表达式”的任务对象

#include "xthreadpool.h"
#include <chrono>
#include <stdio.h>

int main(int argc, char * argv[])
{
    // 线程池对象
    x_threadpool_t xht_pool;

    // 启动线程池
    if (!xht_pool.startup(0))
    {
        printf("startup return false!\n");
        return -1;
    }

    //======================================
    // 提交任务对象 : lambda 表达式

    for (int iter = 0; iter < 100; iter += 10)
    {
        xht_pool.submit_task_ex(
            [iter]() -> void
            {
                int task_id = iter;
                for (int jter = 0; jter < 10; ++jter)
                {
                    printf("lambda A task id : %d -> %d\n", task_id, task_id + jter);
                    std::this_thread::sleep_for(std::chrono::milliseconds(100));
                }
            });
    }

    for (int iter = 0; iter < 100; iter += 10)
    {
        xht_pool.submit_task_ex(
            [iter](const std::string & str_name) -> void
            {
                int task_id = iter;
                for (int jter = 0; jter < 10; ++jter)
                {
                    printf("lambda B task id : %d -> %d name : %s\n", task_id, task_id + jter, str_name.c_str());
                    std::this_thread::sleep_for(std::chrono::milliseconds(100));
                }
            }, std::string("Lambda B"));
    }

    //======================================

    // 等待所有任务执行完成
    while (xht_pool.task_count() > 0)
        std::this_thread::sleep_for(std::chrono::milliseconds(1));

    // 关闭线程池
    xht_pool.shutdown();

    return 0;
}

4.7 任务对象执行过程中检测工作线程是否可继续运行

x_threadpool_t 内部提供了一个类 x_running_checker_t 可检测当前工作线程是否可继续执行下去(或者说,是否要立即终止执行的任务流程)。提供此功能,主要目的是针对那些耗时长的任务对象(处于运行状态),在线程池关闭或者需要动态调整(减少)工作线程数量时,能够优雅地终止任务流程。参看如下代码是如何使用的:

#include "xthreadpool.h"
#include <chrono>
#include <stdio.h>

int main(int argc, char * argv[])
{
    // 线程池对象
    x_threadpool_t xht_pool;

    // 启动线程池
    if (!xht_pool.startup(0))
    {
        printf("startup return false!\n");
        return -1;
    }

    //======================================
    // 提交任务对象 : lambda 表达式(检测工作线程的退出标识)

    for (int iter = 0; iter < 100; iter += 10)
    {
        xht_pool.submit_task_ex(
            [iter](x_running_checker_t * xchecker_ptr) -> void
            {
                int task_id = iter;
                for (int jter = 0; (jter < 10) && xchecker_ptr->is_enable_running(); ++jter)
                {
                    printf("rchecker[%d] lambda A task id : %d -> %d\n", (int)xchecker_ptr->thread_index(), task_id, task_id + jter);
                    std::this_thread::sleep_for(std::chrono::milliseconds(100));
                }
            },
            x_running_checker_t::xholder());
    }

    //======================================

    // 等待所有任务执行完成
    while (xht_pool.task_count() > 0)
        std::this_thread::sleep_for(std::chrono::milliseconds(1));

    // 关闭线程池
    xht_pool.shutdown();

    return 0;
}

4.8 使某一批任务对象在线程池中可按提交次序而顺序执行

单看 “按提交次序而顺序执行” 这句话,可能不好理解,先看后面提到的这样应用场景。

A 对象产生(需要执行)的任务对象序列为[ A1, A2, ..., Am ],并按此顺序依次提交到线程池中;与此同时,B 对象产生(需要执行)的任务对象序列为[ B1, B2, ..., Bn ],也按此顺序依次提交到线程池中。而不管是 A 对象还是 B 对象,都期望按所提交顺序执行任务对象。这种情况下,在线程池中的任务队列可能有如下图所示的排列:

技术分享图片

从本质上看,我们只需要保证 A(或 B) 对象产生的任务在同一时刻只能有一个工作线程执行,这就能保证其顺序性。
面对这种应用场景,我在设计任务对象的抽象基类 x_task_t 时,就为此做好了相关的扩展接口。先看下 x_task_t 代码中的两个接口:

struct x_task_t
{
    ......

    // extensible interfaces
public:
    ......

    /**********************************************************/
    /**
     * @brief 判断 任务对象 是否挂起。
     * @note  若任务对象处于挂起状态,工作线程提取任务时,则跳过该对象。
     */
    virtual bool is_suspend(void) const { return false; }

    /**********************************************************/
    /**
     * @brief 设置任务对象的运行标识。
     * 
     * @note
     * <pre>
     *   工作线程在提取到任务对象后,则立即调用 set_running_flag(true) 操作;
     *   执行 run() 操作返回后,又调用 set_running_flag(false) 操作。
     * </pre>
     */
    virtual void set_running_flag(bool xrunning_flag) { }

    ......
};

再看下 x_threadpool_t 工作线程 “提取任务对象” 以及 “执行任务对象” 的部分实现流程(注意任务对象 is_suspend() 和 set_running_flag() 两个接口被调用的地方):

class x_threadpool_t
{
    ......

    x_task_ptr_t get_task(void)
    {
        ......

        for (std::list< x_task_ptr_t >::iterator itlst = m_lst_run_tasks.begin();
             (itlst != m_lst_run_tasks.end()) && is_enable_get_task();
             ++itlst)
        {
            if ((nullptr == *itlst) || !(*itlst)->is_suspend())
            {
                xtask_ptr = *itlst;
                m_lst_run_tasks.erase(itlst);
                m_xst_lst_tasks.fetch_sub(1);
                break;
            }
        }

        if (nullptr != xtask_ptr)
        {
            xtask_ptr->set_running_flag(true);
        }

        return xtask_ptr;
    }

    ......

    void thread_run(size_t xthread_index)
    {
        ......

            xtask_ptr = get_task();
            if (nullptr == xtask_ptr)
            {
                if (get_lst_task_size() > 0)
                    thread_yield(xcounter);
                continue;
            }

            if (xht_checker.is_enable_running())
            {
                xtask_ptr->run(&xht_checker);
            }

            // 执行完任务对象后,将任务对象转换为 非挂起状态,
            // 加锁进行操作,是为了与 get_task() 内的操作保持队列的同步
            {
                // 标识当前不可提取待执行的任务对象,迫使 get_task() 内部迅速解锁
                m_xst_get_task.fetch_add(1);

                m_lock_run_task.lock();
                xtask_ptr->set_running_flag(false);
                m_lock_run_task.unlock();

                m_xst_get_task.fetch_sub(1);
            }

        ......
    }

    ......
};

我们可通过重载 x_task_t 所提供的两个扩展接口:is_suspend() 和 set_running_flag() 进行状态切换(即在调用 set_running_flag() 时让相关任务切换 is_suspend() 挂起/非挂起 两种状态),就可实现顺序执行。

示例代码如下所示:

#include "xthreadpool.h"
#include <chrono>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

////////////////////////////////////////////////////////////////////////////////

class x_order_task_t;

/**
 * @class objectT
 * @brief 产生 x_order_task_t 任务对象序列的测试类。
 */
class objectT
{
    // constructor/destructor
public:
    objectT(const std::string & xstr_name)
        : m_xrunning_flag(false)
        , m_seqno_taskid(0)
        , m_xstr_name(xstr_name)
    {

    }

    // public interfaces
public:
    inline void set_running(bool xrunning_flag)
    {
        m_xrunning_flag = xrunning_flag;
    }

    inline bool is_running(void) const
    {
        return m_xrunning_flag;
    }

    inline const std::string & name(void) const
    {
        return m_xstr_name;
    }

    x_order_task_t * new_task(void);

    // data members
private:
    bool         m_xrunning_flag;
    int          m_seqno_taskid;
    std::string  m_xstr_name;
};

/**
 * @class x_order_task_t
 * @brief 保证顺序执行的测试任务对象。
 */
class x_order_task_t : public x_threadpool_t::x_task_t
{
    // constructor/destructor
public:
    x_order_task_t(objectT * obj_owner, int taskid)
        : m_obj_owner(obj_owner)
        , m_taskid(taskid)
    {

    }

    virtual ~x_order_task_t(void) { }

    // overrides
public:
    virtual void run(x_running_checker_t * xchecker_ptr) override
    {
        printf("%s:%02d\n", m_obj_owner->name().c_str(), m_taskid);
    }

    virtual bool is_suspend(void) const override
    {
        return m_obj_owner->is_running();
    }

    virtual void set_running_flag(bool xrunning_flag) override
    {
        m_obj_owner->set_running(xrunning_flag);
    }

    // data members
private:
    objectT * m_obj_owner;
    int       m_taskid;
};

x_order_task_t * objectT::new_task(void)
{
    return (new x_order_task_t(this, m_seqno_taskid++));
}

//====================================================================

int main(int argc, char * argv[])
{
    // 线程池对象
    x_threadpool_t xht_pool;

    // 启动线程池
    if (!xht_pool.startup(0, true))
    {
        printf("startup return false!\n");
        return -1;
    }

    //======================================
    // 便于测试而增加的代码
    // 提交任务对象 : 使线程池的所有工作线程处于暂停状态

    bool is_pause_pool = true;
    for (int iter = 0, nthds = (int)xht_pool.size(); iter < nthds + 1; ++iter)
    {
        xht_pool.submit_task_ex(
            [&is_pause_pool]() -> void
            {
                while (is_pause_pool)
                {
                    std::this_thread::sleep_for(std::chrono::milliseconds(1));
                }
            });
    }

    //======================================
    // 提交任务对象 : 按提交顺序执行任务对象

    srand(time(NULL));

    objectT objA("objA");
    objectT objB("objB");
    for (int iter = 0; iter < 100; ++iter)
    {
        if (0 == (rand() % 2))
        {
            xht_pool.submit_task(objA.new_task());
        }
        else
        {
            xht_pool.submit_task(objB.new_task());
        }
    }

    // 使线程池开始工作(便于测试而增加的代码)
    is_pause_pool = false;

    //======================================

    // 等待所有任务执行完成
    while (xht_pool.task_count() > 0)
        std::this_thread::sleep_for(std::chrono::milliseconds(1));

    // 关闭线程池
    xht_pool.shutdown();

    return 0;
}

看一下测试的输出结果:

objA:00
objB:00
objB:01
objB:02
objB:03
objB:04
objA:01
objA:02
objA:03
objB:05
objA:04
objB:06
objB:07
objB:08
objB:09
objA:05
objA:06
objB:10
objB:11
objB:12
objA:07
objA:08
objA:09
objA:10
objB:13
objA:11
objA:12
objB:14
objB:15
objB:16
objB:17
objB:18
objB:19
objA:13
objA:14
objA:15
objA:16
objA:17
objA:18
objA:19
objB:20
objA:20
objA:21
objA:22
objB:21
objB:22
objB:23
objA:23
objB:24
objA:24
objB:25
objB:26
objB:27
objB:28
objB:29
objA:25
objB:30
objA:26
objA:27
objB:31
objB:32
objB:33
objA:28
objA:29
objA:30
objB:34
objA:31
objA:32
objB:35
objA:33
objA:34
objB:36
objA:35
objA:36
objB:37
objA:37
objA:38
objA:39
objA:40
objB:38
objB:39
objB:40
objA:41
objA:42
objA:43
objA:44
objA:45
objB:41
objA:46
objA:47
objA:48
objB:42
objA:49
objB:43
objA:50
objA:51
objA:52
objA:53
objA:54
objB:44

经检测,这已经达到我们所期望的结果。

C++11 —— 使用 thread 实现线程池

原文:https://www.cnblogs.com/Gaaagaa/p/12130842.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!