C/C++线程池的使用——Applying Thread Pools in ROS1

一、基本类

        基于ROS机器人平台的消费者-生产者模式的实时多任务处理模型。采用线程池(这里主要是消费者采用线程池,生产者其实是单线程发布)和阻塞队列实现。

        1)任务模板类

        定义了最终的任务执行函数的格式。这里提供两种实现,一种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);
		}
};

         2)阻塞队列实现

        作为线程池线程访问的公共资源。使用队列的数据结构+互斥锁+条件变量实现。完成线程间的互斥(同步)。这里特别说明一下,生产者与生产者互斥、消费者与消费者互斥、生产者与消费者通过阻塞队列达到互斥与同步。

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

         3)线程池

        基于阻塞队列的线程池。提供了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

你可能感兴趣的:(C++日常,C/C++,ROS,线程池,任务模型)