boost::asio::io_service创建线程池简单实例

简介


boost::asio提供了一个跨平台的异步编程IO模型库,io_service类在多线程编程模型中提供了任务队列和任务分发功能。

io_service最常用的接口是:run, post, stop。

本文简要介绍io_service的使用,详细内容可以参阅相关reference。

启动一个线程


使用run()启动。

run()会阻塞,直到:

  • 所有的任务已经完成并且没有任务需要分发处理
  • 或者调用了stop()
启动任务

简单测试例程如下:

#include 
#include 

int main(int argc, char *argv[])
{
		// 创建io_service
    boost::asio::io_service io_service;
    // 附加任务
    boost::asio::io_service::work work(io_service);
		// 启动并阻塞
    io_service.run();
    
    std::cout << "Yes, run() is returned!" << std::endl;

    return 0;
}

注意,程序打印文本,也就是说io_service阻塞在了run()。我们使用了work类,它会使得run()一直在任务待执行而不会退出,如果注释掉这一行,可以看到run会立即执行完毕并打印文本。

结束任务

使用work时会阻塞run,可以使用work类型的指针来结束所有任务。

boost::asio::io_service io_service;
boost::shared_ptr< boost::asio::io_service::work > work(new boost::asio::io_service::work( io_service ));

// 结束任务
work.reset();
// 立即返回
io_service.run();

启动线程池


在多个线程中调用run()即可开启线程池,io_service负责执行任务处理。所有在线程池中等待的线程是平等的,io_service会随机选择一个线程去执行任务。

使用thread库

这里使用了boost::thread库,例子如下:

#include 
#include 
#include 

boost::asio::io_service io_service;

void WorkerThread()
{
	std::cout << "Thread Start\n";
	io_service.run();
	std::cout << "Thread Finish\n";
}

int main( int argc, char * argv[] )
{
	boost::shared_ptr< boost::asio::io_service::work > work(new boost::asio::io_service::work( io_service ));

	std::cout << "Press [return] to exit." << std::endl;

	boost::thread_group worker_threads;
	for( int x = 0; x < 4; ++x )
	{
		worker_threads.create_thread( WorkerThread );
	}

	std::cin.get();

	io_service.stop();

	worker_threads.join_all();

	return 0;
}

stop()会告知io_service,所有的任务需要终止。它的调用可能会使已经进入队列的任务得不到执行。

如果想让它们都执行,最好先销毁work对象。如果想让系统马上停止,则调用stop()。

使用bind

使用boost::bind可以把函数调用包装成为对象。

调用常规函数的示例:

#include 
#include 

void F2( int i, float f )
{
	std::cout << "i: " << i << std::endl;
	std::cout << "f: " << f << std::endl;
}

int main( int argc, char * argv[] )
{
	boost::bind( &F2, 42, 3.14f )();// 最后的括号表明调用该函数,如果去掉括号表示仅封装对象
	return 0;
}

调用类成员函数示例:

class MyClass
{
public:
	void F3( int i, float f )
	{
		std::cout << "i: " << i << std::endl;
		std::cout << "f: " << f << std::endl;
	}
};

int main( int argc, char * argv[] )
{
	MyClass c;
	boost::bind( &MyClass::F3, &c, 42, 3.14f )();
	return 0;
}

回到io_service,使用bind可以改写程序:

#include 
#include 
#include 
#include 

boost::mutex global_stream_lock;

void WorkerThread( boost::shared_ptr< boost::asio::io_service > io_service )
{
	global_stream_lock.lock();
	std::cout << "[" << boost::this_thread::get_id() << "] Thread Start" << std::endl;
	global_stream_lock.unlock();

	io_service->run();

	global_stream_lock.lock();
	std::cout << "[" << boost::this_thread::get_id() <<	"] Thread Finish" << std::endl;
	global_stream_lock.unlock();
}

int main( int argc, char * argv[] )
{
	boost::shared_ptr< boost::asio::io_service > io_service(new boost::asio::io_service);
	boost::shared_ptr< boost::asio::io_service::work > work(new boost::asio::io_service::work( *io_service ));

	global_stream_lock.lock();
	std::cout << "[" << boost::this_thread::get_id() << "] Press [return] to exit." << std::endl;
	global_stream_lock.unlock();

	boost::thread_group worker_threads;
	for( int x = 0; x < 4; ++x )
	{
		worker_threads.create_thread( boost::bind( &WorkerThread, io_service ) );
	}

	std::cin.get();

	io_service->stop();

	worker_threads.join_all();

	return 0;
}
post任务

至此,启动了线程,其实并没有给线程做任务。也就是说,启动的线程处于空闲状态。

可以使用post/dispatch分发任务,它们的区别是:

  • 如果可以,dispatch会立即执行任务,否则把任务加入到queue
  • post只会把任务加入到队列

具体使用何种方式需要根据具体情况确定。

如下的程序打印数字:

boost::mutex global_stream_lock;

void WorkerThread( boost::shared_ptr< boost::asio::io_service > io_service )
{
	global_stream_lock.lock();
	std::cout << "[" << boost::this_thread::get_id() << "] Thread Start" << std::endl;
	global_stream_lock.unlock();

	io_service->run();

	global_stream_lock.lock();
	std::cout << "[" << boost::this_thread::get_id() << "] Thread Finish" << std::endl;
	global_stream_lock.unlock();
}

void Dispatch( int x )
{
	global_stream_lock.lock();
	std::cout << "[" <<  boost::this_thread::get_id()  << "] " << __FUNCTION__  << " x = " << x <<  std::endl;
	global_stream_lock.unlock();
}

void Post( int x )
{
	global_stream_lock.lock();
	std::cout << "[" <<  boost::this_thread::get_id()  << "] " << __FUNCTION__  << " x = " << x <<  std::endl;
	global_stream_lock.unlock();
}

void Run3( boost::shared_ptr< boost::asio::io_service > io_service )
{
	for( int x = 0; x < 3; ++x )
	{
		io_service->dispatch( boost::bind( &Dispatch, x * 2 ) );
		io_service->post( boost::bind( &Post, x * 2 + 1 ) );
		boost::this_thread::sleep( boost::posix_time::milliseconds( 1000 ) );
	}
}

int main( int argc, char * argv[] )
{
	boost::shared_ptr< boost::asio::io_service > io_service( new boost::asio::io_service);
	boost::shared_ptr< boost::asio::io_service::work > work(new boost::asio::io_service::work( *io_service ));

	global_stream_lock.lock();
	std::cout << "[" <<  boost::this_thread::get_id() << "] The program will exit when all  work has finished." <<  std::endl;
	global_stream_lock.unlock();

	boost::thread_group worker_threads;
	for( int x = 0; x < 1; ++x )
	{
		worker_threads.create_thread( boost::bind( &WorkerThread, io_service ) );
	}

	io_service->post( boost::bind( &Run3, io_service ) );

	work.reset();

	worker_threads.join_all();

	return 0;
}

可以看到,所有的任务得到了执行,但执行顺序可能不同。这个示例也可以看出混合使用post和dispatch可能存在的问题。

strand顺序处理

strand提供顺序化的事件执行器。意思是,如果以“work1->work2->work3”的顺序post,不管有多少个工作线程,它们依然会以这样的顺序执行任务。

#include 
#include 
#include 
#include 
#include 

boost::mutex global_stream_lock;

void WorkerThread( boost::shared_ptr< boost::asio::io_service > io_service )
{
	global_stream_lock.lock();
	std::cout << "[" << boost::this_thread::get_id() << "] Thread Start" << std::endl;
	global_stream_lock.unlock();

	io_service->run();

	global_stream_lock.lock();
	std::cout << "[" << boost::this_thread::get_id() << "] Thread Finish" << std::endl;
	global_stream_lock.unlock();
}

void PrintNum( int x )
{
	std::cout << "[" << boost::this_thread::get_id() << "] x: " << x << std::endl;
}

int main( int argc, char * argv[] )
{
	boost::shared_ptr< boost::asio::io_service > io_service( new boost::asio::io_service);
	boost::shared_ptr< boost::asio::io_service::work > work(new boost::asio::io_service::work( *io_service ));
	boost::asio::io_service::strand strand( *io_service );

	global_stream_lock.lock();
	std::cout << "[" <<  boost::this_thread::get_id()  << "] The program will exit when all  work has finished." <<  std::endl;
	global_stream_lock.unlock();

	boost::thread_group worker_threads;
	for( int x = 0; x < 2; ++x )
	{
		worker_threads.create_thread( boost::bind( &WorkerThread, io_service ) );
	}

	boost::this_thread::sleep( boost::posix_time::milliseconds( 1000 ) );

	//strand.post( boost::bind( &PrintNum, 1 ) );
	//strand.post( boost::bind( &PrintNum, 2 ) );
	//strand.post( boost::bind( &PrintNum, 3 ) );
	//strand.post( boost::bind( &PrintNum, 4 ) );
	//strand.post( boost::bind( &PrintNum, 5 ) );

	io_service->post( boost::bind( &PrintNum, 1 ) );
	io_service->post( boost::bind( &PrintNum, 2 ) );
	io_service->post( boost::bind( &PrintNum, 3 ) );
	io_service->post( boost::bind( &PrintNum, 4 ) );
	io_service->post( boost::bind( &PrintNum, 5 ) );

	work.reset();

	worker_threads.join_all();

	return 0;
}

去掉了打印数据时的锁。如果使用post,会得到比较混乱的输出,而如果使用strand,则能得到清晰的输出,这就是strand!

小结


总之,使用io_service能够方便地构建多线程处理程序。它的总体使用流程总结如下:

#include 
#include 
#include 

/*
 * Create an asio::io_service and a thread_group (through pool in essence)
 */
boost::asio::io_service ioService;
boost::thread_group threadpool;


/*
 * This will start the ioService processing loop. All tasks 
 * assigned with ioService.post() will start executing. 
 */
boost::asio::io_service::work work(ioService);

/*
 * This will add 2 threads to the thread pool. (You could just put it in a for loop)
 */
threadpool.create_thread(
    boost::bind(&boost::asio::io_service::run, &ioService)
);
threadpool.create_thread(
    boost::bind(&boost::asio::io_service::run, &ioService)
);

/*
 * This will assign tasks to the thread pool. 
 * More about boost::bind: "http://www.boost.org/doc/libs/1_54_0/libs/bind/bind.html#with_functions"
 * You can use strand when necessary, if so, remember add "strand.h"
 */
ioService.post(boost::bind(myTask, "Hello World!"));
ioService.post(boost::bind(clearCache, "./cache"));
ioService.post(boost::bind(getSocialUpdates, "twitter,gmail,facebook,tumblr,reddit"));

/*
 * This will stop the ioService processing loop. Any tasks
 * you add behind this point will not execute.
*/
ioService.stop();

/*
 * Will wait till all the threads in the thread pool are finished with 
 * their assigned tasks and 'join' them. Just assume the threads inside
 * the threadpool will be destroyed by this method.
 */
threadpool.join_all();

参考资料:


A guide to getting started with boost::asio
浅谈 Boost.Asio 的多线程模型
How to create a thread pool using boost in C++?

你可能感兴趣的:(cpp)