一 直以来感觉 boost 的库作为开源的库文档是非常详细的,绝大部分库的文档由浅入深,一般先有 Overview, 从 Introduction 到简单的 Tutorial 到复杂的 example, 再到 rationale ,应有尽有, 但是 boost::thread 是个例外 ,没有任何 Introduction ,Tutorial 的内容,上来就是 class/type 的 member function ,头文件列举,列举完了了事,连一个 example 也没有,最奇怪的 boost 库文档绝对非其莫属,甚至《 Beyond the C++ Standard Library: An Introduction to Boost 》这本书中也只字未提 thread 库, 这样的确为学习 boost::thread 库加大了难度。对于初学者就更难受了,毕竟,本来多线程就是一个不那么容易的东西。。。。
但是,不要以为此库就是 boost 中最默默无名的库了,为 C++ 添加多线程库的呼声一直比较高(虽然 B.S. 以前在 D&E 中认为其应该由第三方库来完成这样和操作平台相关性比较大的内容),亏 boost::thread 库还提案了好几次,结果文档都没有完善 -_-! 起码也算是可能进入 C++ 标准的东西,咋能这样呢?
最新的提案信息,可以在其文档中搜寻到,已经进入 Revision 1 的阶段了。《 Multi-threading Library for Standard C++ (Revision 1) 》
其实,个人认为,一个多线程库可以很简单,实现简单的临界区用于同步就足够应付绝大部分情况了,相对而言, boost::thread 这样的库还是稍微庞大了一点。类似于 Python 中的 thread 库其实就不错了(据《 Programming Python 》作者说原型来自于 JAVA ),通过继承形式使用线程功能( template method 模式),还算比较自然,其实我们公司自己内部也实现了一套与之类似的 C++ 版的线程库,使用也还算方便。但是 boost::thread 走的是另一条路。由于其文档中没有 Introduction 和 Tutorial ,我纯粹是摸石头过河似的实验,有用的不对的地方那也就靠大家指出来了。
一、 Introduction :
boost::thread 不是通过继承使用线程那种用了 template method 模式的线程模型,而是通过参数传递函数 ( 其实不仅仅是函数,只要是 Callable , Copyable (因为需要复制到线程的本地数据)的就行)。这种模型是好是坏,我一下也没有结论,但是 boost::thread 库的选择总归是有些道理的,起码从个人感觉来说,也更符合标准库一贯的优先使用泛型而不是继承的传统和作风,这样的模型对于与 boost::function,boost::bind 等库的结合使用的确也是方便了很多,
1. 题外话:
假如你对 win32/linux 下的多线程有一定的了解有助于理解 boost::thread 的使用,假如没有 win32/linux 的多线程使用经验,那么起码也需要对多线程程序有概念性的了解,起码对于 3 个概念要有所了解, context switching,rare conditions, atomic operation ,最好也还了解线程间同步的一些常见形式,假如对于我上面提及的概念都不了解,建议先补充知识,不然,即便是 HelloWorld ,估计也难以理解。 另外,毕竟本文仅仅是个人学习 boost::thread 库过程中的一些记录,所以不会对操作系统,线程等知识有透彻的讲解,请见谅。
2. boost::thread 的 HelloWorld:
example1:
#include <windows.h>
#include <boost/thread.hpp>
#include <iostream>
using namespace std ;
using namespace boost ;
void HelloWorld ()
{
char * pc = "Hello World!" ;
do
{
cout <<*pc ;
}while (*pc ++);
cout <<endl ;
}
void NormalFunThread ()
{
thread loThread1 (HelloWorld );
thread loThread2 (HelloWorld );
HelloWorld ();
Sleep (100);
}
int main ()
{
NormalFunThread ();
return 0;
}
不知道如此形式的程序够不够的上一个 thread 的 helloworld 程序了。但是你会发现, boost::thread 的确是通过构造函数的方式,(就是构造函数),老实的给我们创建了线程了,所以我们连一句完成的 helloworld 也没有办法正常看到,熟悉线程的朋友们,可以理解将会看到多么支离破碎的输出,在我的电脑上,一次典型的输出如下:
HHeellloHl eoWl olWrool rdWl!od
l d
!
呵呵,其实我不一次输出整个字符串,就是为了达到这种效果 -_-! 这个时候需要同步, join 函数就是 boost::thread 为我们提供的同步的一种方式,这种方式类似于利用 windows API WaitForSingleObject 等待线程结束。 下面利用这种方式来实现。
example2:
#include <boost/thread.hpp>
#include <iostream>
using namespace std ;
using namespace boost ;
void HelloWorld ()
{
char * pc = "Hello World!" ;
do
{
cout <<*pc ;
}while (*pc ++);
cout <<endl ;
}
void NormalFunThread ()
{
thread loThread1 (HelloWorld );
loThread1 .join ();
thread loThread2 (HelloWorld );
loThread2 .join ();
HelloWorld ();
}
int main ()
{
NormalFunThread ();
return 0;
}
这样,我们就能完成的看到 3 句 hello world 了。但是这种方式很少有意义,因为实际上我们的程序同时还是仅仅存在一个线程,下一个线程只在一个线程结束后才开始运行,所以,实际中使用的更多的是其他同步手段,比如,临界区就用的非常多,但是我在 boost::thread 中没有找到类似的使用方式,倒是有 mutex (互斥),其实两者对于使用是差不多的。下面看使用了 mutex 同步线程的例子:
example3:
#include <windows.h>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>
using namespace std ;
using namespace boost ;
mutex mu ;
void HelloWorld ()
{
mu .lock ();
char * pc = "Hello World!" ;
do
{
cout <<*pc ;
}while (*pc ++);
cout <<endl ;
mu .unlock ();
}
void NormalFunThread ()
{
thread loThread1 (HelloWorld );
thread loThread2 (HelloWorld );
HelloWorld ();
loThread1 .join ();
loThread2 .join ();
}
int main ()
{
NormalFunThread ();
return 0;
}
我们还是能看到 3 个完好的 helloworld ,并且,这在实际使用中也是有意义的,因为,在主线程进入 HelloWorld 函数时,假如第一个线程还没有执行完毕,那么,可能同时有 3 个线程存在,第一个线程正在输出,第二个线程和主线程在 mu.lock(); 此句等待(也叫阻塞在此句)。其实 , 作为一个多线程的库,自然同步方式不会就这么一种,其他的我就不讲了。
作为 boost 库,有个很大的有点就是,互相之间结合的非常好。这点虽然有的时候加大了学习的难度,当你要使用一个库的时候,你会发现一个一个顺藤摸瓜,结果都学会了 , 比如现在,关于 boost 库的学习进行了很久了,(写了 4 , 5 篇相关的学习文章了),从 boost::for_each,boost::bind,boost::lambda,boost::function,boost:: string_algo, 到现在的 boost::thread ,其实原来仅仅是想要好好学习一下 boost::asio 而已。当你真的顺着学下来,不仅会发现对于 C++ 语言的理解,对 STL 标准库的理解,对于泛型的理解,等等都有更深入的了解,我甚至在同时学习 python 的时候,感觉到 boost 库改变了 C++ 的很多语言特性。。。虽然是模拟出来的。呵呵,题外话说多了,其实要表达的意思仅仅是 boost::thread 库也是和其他 boost 库有很多紧密结合的地方,使得其使用会非常的方便。这里一并的在一个例子中演示一下。
example4:
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
using namespace std ;
using namespace boost ;
void HelloWorld ()
{
char * pc = "Hello World!" ;
do
{
cout <<*pc ;
}while (*pc ++);
cout <<endl ;
}
void NormalFunThread ()
{
thread loThread1 (HelloWorld );
thread loThread2 (HelloWorld );
HelloWorld ();
loThread1 .join ();
loThread2 .join ();
}
void BoostFunThread ()
{
thread loThread1 (HelloWorld );
function < void (void ) > lfun = bind (HelloWorld );
thread loThread2 (bind (HelloWorld ));
thread loThread3 (lfun );
loThread1 .join ();
loThread2 .join ();
loThread3 .join ();
}
int main ()
{
// NormalFunThread();
BoostFunThread ();
return 0;
}
一如既往的乱七八糟:
HHHeeelllllolo o W WoWoorrrlldld!d!
但是,正是这样的乱七八糟,告诉了我们,我们进入了真实的乱七八糟的多线程世界了 -_-!
还记得可怜的 Win32 API 怎么为线程传递参数吗?
看看其线程的原型
DWORD ThreadProc(
LPVOID
lpParameter
);
这里有个很大的特点就是,运行线程的函数必须是这样的,规矩是定死的,返回值就是这样,参数就是 LPVOID(void*) ,你没有选择,函数原型没有选择,参数传递也没有选择,当你需要很多数据时,唯一的办法就是将其塞入一个结构,然后再传结构指针,然后再强行使用类型转换。其实这是很不好的编程风格,不过也是无奈的折衷方式。
注意到没有,其实我们的 HelloWold 根本就是没有符合这个要求,不过我们一样使用了,这也算是 boost::thread 的一个很大优点,最大的优点还是在于参数传递的方式上,彻底摆脱了原来的固定死的框架,让你到了随心所欲的使用线程的地步。
看个例子:
example5:
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
using namespace std ;
using namespace boost ;
mutex mu ;
void HelloTwoString (char *pc1 , char *pc2 )
{
mu .lock ();
if (pc1 )
{
do
{
cout <<*pc1 ;
}while (*pc1 ++);
}
if (pc2 )
{
do
{
cout <<*pc2 ;
}while (*pc2 ++);
cout <<endl ;
}
mu .unlock ();
}
void BoostFunThread ()
{
char * lpc1 = "Hello " ;
char * lpc2 = "World!" ;
thread loThread1 (HelloTwoString , lpc1 , lpc2 );
function < void (void ) > lfun = bind (HelloTwoString , lpc1 , lpc2 );
thread loThread2 (bind (HelloTwoString , lpc1 , lpc2 ));
thread loThread3 (lfun );
loThread1 .join ();
loThread2 .join ();
loThread3 .join ();
}
int main ()
{
BoostFunThread ();
return 0;
}
这里不怀疑线程的创建了,用了同步机制以方便查看结果,看看参数的传递效果,是不是真的达到了随心所欲的境界啊:)
最最重要的是,这一切还是建立在坚实的 C++ 强类型机制磐石上,没有用 hack 式的强制类型转换,这个重要性无论怎么样强调都不过分,这个优点说他有多大也是合适的。再一次的感叹,当我责怪牛人们将 C++ 越弄越复杂的时候。。。。。。。。先用用这种复杂性产生的简单的类型安全的高效的库吧。。。。。。关于 boost::thread 库就了解到这里了,有点浅尝辄止的感觉,不过,还是先知其大略,到实际使用的时候再来详细了解吧,不然学习效率也不会太高。