一、基本类
基于ROS机器人平台的消费者-生产者模式的实时多任务处理模型。采用线程池(这里主要是消费者采用线程池,生产者其实是单线程发布)和阻塞队列实现。
定义了最终的任务执行函数的格式。这里提供两种实现,一种C语言常用的函数指针形式,一种是Modern C++的std::function特性。这里特别说明一下,在function里面存放的是函数类型,所谓函数类型就是 【返回值类型+参数列表】。
// C语言函数指针实现
// template // 任务参数模板化
// class threadTask {
// public:
// typedef void(* handler_t )(T );
// private:
// T _data;
// handler_t _handler;
// public:
// void setTask (const T& data, const handler_t& handler) {
// _data = data;
// _handler = handler;
// }
// void exec() {
// _handler(_data) ;
// }
// };
// C++特性function实现
template // 任务参数模板化
class threadTask {
private:
T _data;
std::function _handler; // 存放函数类型
public:
void setTask (const T& param, const auto& handler) {
_data = param;
_handler = handler;
}
void exec() {
_handler(_data);
}
};
作为线程池线程访问的公共资源。使用队列的数据结构+互斥锁+条件变量实现。完成线程间的互斥(同步)。这里特别说明一下,生产者与生产者互斥、消费者与消费者互斥、生产者与消费者通过阻塞队列达到互斥与同步。
template
class blockingQueue { // 阻塞队列: 线程间互斥(同步)
private:
std::queue> que;
size_t capacity ;
pthread_mutex_t mutex; // 互斥锁
pthread_cond_t cus_cond, pro_cond; // 条件变量
public:
blockingQueue(size_t s=5): capacity(s) {
pthread_mutex_init ( &mutex, NULL);
pthread_cond_init ( &cus_cond, NULL);
pthread_cond_init ( &pro_cond, NULL);
}
~blockingQueue() {
pthread_mutex_destroy(&mutex) ;
pthread_cond_destroy(&cus_cond);
pthread_cond_destroy(&pro_cond);
}
bool pushBQ(const threadTask& data){ // [生产者]
pthread_mutex_lock(&mutex);
while(que.size() >= capacity){
// 阻塞,等待别个释放条件变量来唤醒signal(&pro_cond)。同时释放锁,让其他持有该锁的对象获得执行。(原子操作)
// 被唤醒返回时,解除阻塞并重新加锁互斥量
pthread_cond_wait( &pro_cond, &mutex);
}
que.push(data); // 任务入队
pthread_cond_signal ( &cus_cond ) ; // 唤醒被 cus_cond阻塞的对象
pthread_mutex_unlock ( &mutex );
return true;
}
bool popBQ(threadTask* data){ // [消费者]
pthread_mutex_lock( &mutex) ;
while(que.empty() ){
pthread_cond_wait( &cus_cond ,&mutex);
}
*data = que.front(); // 取出任务,执行
que.pop();
pthread_cond_signal( &pro_cond );
pthread_mutex_unlock ( &mutex);
return true;
}
};
基于阻塞队列的线程池。提供了C的pthread实现和C++ thread+lambda实现。
template
class threadPools { // 基于阻塞队列的线程池
public:
// static void* threadEntry(void* arg) { // [消费者]
// threadPools* instance = (threadPools *) arg;
// while(1) {
// threadTask task; // 取任务执行
// instance->_queue.popBQ(&task);
// task.exec();
// }
// return nullptr;
// }
threadPools(int max_poools_num = 5, int queue= 10)
: _max_pool_nums (max_poools_num) ,
_queue(queue) {
pthread_t tid[_max_pool_nums];
printf("Thread pool nums: %d\n", _max_pool_nums);
printf("Task queue size: %d\n", queue);
for ( int i=0 ; i<_max_pool_nums ;++i) {
/* C pthread */
// if (pthread_create(&tid[i], NULL, &threadPools::threadEntry, this) == 0) {
// pthread_detach(tid[i]);
// printf("thread%d pid: %ld\n", i, tid[i]);
// }
// else
// exit(-1);
/* C++11 thread lamda */
std::thread([this, &i] () -> void {
std::cout << "thread" << i << " pid: " << std::this_thread::get_id() << std::endl;
while(true) {
threadTask task; // 取任务执行
this->_queue.popBQ(&task);
task.exec();
}
}).detach() ;
}
}
~threadPools() {
printf("thread pools free...\n");
}
// 放入任务队列
bool taskPush(const threadTask& task) { // [生产者]
return _queue.pushBQ(task);
}
private:
int _max_pool_nums;
blockingQueue _queue;
};
二、ROS封装
基于ROS平台的任务结构封装。通过外部话题触发回调进入任务生产,查询任务信息表以及任务执行条件表,将需要执行的任务任务放入阻塞队列,完成任务生产。
消费者线程池中的线程从阻塞队列取任务执行,同时标记任务状态(just从阻塞队列中取出的任务是否在执行态)。
使用std::bind绑定类成员函数和this指针 或者 lambda捕获this指针实现类内函数的绑定。
class ros1Task { // ros平台封装的任务类
public:
struct taskInfo { // 任务参数
int task_id;
bool is_exec;
float s_start;
float s_end;
taskInfo() = default;
taskInfo(int id, bool exec_flag, float start, float end)
: task_id(id),
is_exec(exec_flag),
s_start(start),
s_end(end) { }
};
using Type = taskInfo;
ros1Task(int pool_size, int taskQueue_size) {
// 线程池创建, 等待任务
pool = std::make_shared>(pool_size, taskQueue_size);
// 外部接口:订阅话题, 进入回调发布任务
taskInfo_sub = nh.subscribe("task_exec_s", 1, &ros1Task::taskInfoCallback, this);
// 外部模拟:发布"task_exec_s", 任务放入队列
taskInfo_pub = nh.advertise("task_exec_s", 1);
}
public:
ros::NodeHandle nh;
ros::Subscriber taskInfo_sub;
ros::Publisher taskInfo_pub;
// 任务入队
void taskInfoCallback(const std_msgs::Float32::ConstPtr& msg) {
float cur_loc = msg->data;
// 遍历任务表
for (const auto& info : taskListInfo) {
if (info.s_start < cur_loc && cur_loc < info.s_end) {
if (!std::get<1>(taskList[info.task_id])) {
// 放入该任务进队列,进入运行
Type inf(info);
threadTask addtask;
addtask.setTask(inf, std::get<0>(taskList[info.task_id]));
pool->taskPush(addtask);
printf("already Add task id[%d]\n", info.task_id);
// 标记任务在运行
std::get<1>(taskList[info.task_id]) = true;
}
}
}
}
public:
// 待测试任务
void fun1(Type data) { // 占用线程池线程大概1s
printf("task1: %f thread: %ld pid: %d\n", data.s_end, pthread_self(), getpid());
printf("fun1 start exec...\n");
sleep(1);
printf("fun1 exec finish...\n");
// reset flag
resetTaskListFlag(data);
}
void fun2(Type data) { // 占用线程池线程大概2s
printf("task2: %f thread: %ld pid: %d\n", data.s_end, pthread_self(), getpid());
printf("fun2 start exec...\n");
sleep(2);
printf("fun2 exec finish...\n");
// reset flag
resetTaskListFlag(data);
}
void fun3(Type data) { // 占用线程池线程大概3s
printf("task3: %f thread: %ld pid: %d\n", data.s_end, pthread_self(), getpid());
printf("fun3 start exec...\n");
sleep(3);
printf("fun3 exec finish...\n");
// reset flag
resetTaskListFlag(data);
}
private:
std::shared_ptr> pool; // 线程池
// 任务函数注册
// 任务id, tuple(任务函数, 任务是否锁定(正在执行就将任务锁定,避免重复进线程))
// 注意: [可以使用匿名函数绑定this指针,也可以使用bind函数绑定this指针]
std::map< int, std::tuple, bool> > taskList = {
// {1, std::make_tuple( std::bind(&ros1Task::fun1, this, std::placeholders::_1), false ) },
{1, std::make_tuple( [this](Type param) { this->fun1(param); }, false ) },
{2, std::make_tuple( [this](Type param) { this->fun2(param); }, false ) },
{3, std::make_tuple( [this](Type param) { this->fun3(param); }, false ) }
};
// 任务执行信息表(条件)
std::vector taskListInfo = {
{1, false, 0, 10},
{2, false, 0, 20},
{3, false, 0, 30}
};
// reset flag
inline void resetTaskListFlag(Type param) {
std::get<1>(taskList[param.task_id]) = false;
}
};
三、测试结果
-----based on [product-costom model for thread pools]-----main thread pid: 59082
Thread pool nums: 2
Task queue size: 5
thread1 pid: 139908140226304
thread0 pid: 139908131833600
already Add task id[1]
already Add task id[2]
task1: 10.000000 thread: 139908140226304 pid: 59082
fun1 start exec...
already Add task id[3]
task2: 20.000000 thread: 139908131833600 pid: 59082
fun2 start exec...
fun1 exec finish...
task3: 30.000000 thread: 139908140226304 pid: 59082
fun3 start exec...
fun2 exec finish...
fun3 exec finish...
上述看出,线程池大小2,任务队列大小5,设定的任务1执行睡眠1s,任务2执行睡眠2s,任务3执行睡眠3s,任务队列此时已经加入3个任务。消费者线程池中的线程先从任务队列取出task1、task2执行。task1运行1s结束,消费者线程池中的线程空闲1个随即取出task3执行,此时task2也运行了1s,还剩1s运行结束。task2和task3运行,task2先结束,task3后结束(可以自己分析线程号的交替)。
~~完整的代码框架地址:http://[email protected]:J-alchemist/TaskModelForROS.git