历史上影响最大的程序员Simonyi后来致力于意念式编程的研究,并为此成立了专门的公司(1)。对于很多热爱生活的人们而言投身于软件行业意味着可以痛痛快快的大干一场了,不是吗,还有什么行业比程序员更容易发挥个人才华的呢?
然而一旦经历过激情四溢的初始时刻,很多人发现他们是在用自己的生命在和代码纠缠不休:多少次对着凝结着自己心血的程序大发脾气,恨不得推倒重来;曾经引以为豪的代码,如今又是多么可笑,毫无价值!
我们有过快乐的“童年”,哪怕是晦涩难懂的汇编,都可以毫无倦意的一口气写上成百上千行,整夜的调试,逐行的Debug。然而现实的残酷无情终究是每个人要去面对的 --- 这个世界不是想象的那般美好,人类手头的工具比起思想而言,落后的差距是如此的巨大,我们的努力始终追不上我们想得到的。但这也是我们进步的动力和源泉,我们要相信自然界会善待她的子民的,那么我们是否真的能如愿按照意念去构造程序呢,我们的困惑究竟在哪里?
[线程、同步和异步]
本来是打算跳过这一章的,因为其中介绍的方法已经过时了。但又考虑到这里的内容更普遍一些,也就更容易理解和产生共鸣,所以也就不在乎献丑了。
线程已经成为现代程序的一个普遍特性,虽然使用起来还是存在一些陷阱,总体上它为我们编写一些复杂应用提供了极大的便利。但是因为需要操作系统或底层硬件的支持,传统的语言都没有直接把它纳入进来。即便是Java等这种跨平台语言环境也仅仅以标准库的形式加以支持。对于C++用户而言,在线程处理上往往不得不与具体的环境打交道,虽然逐渐已经有了一些跨平台的线程库(2)的出现。
[posix标准及其他]实际上在操作系统领域也是有标准的,最具有代表性的就是unix上发展而来的posix标准。这曾经也是美国政府和行业领域的标准,因为这个原因,包括微软的WindowsNT等大部分操作系统都提供了对posix标准的支持(可能并不完整的)。然而由于某些原因Win32系统已经远远超过了posix的影响力,至少在微软触角伸到的地方是如此。这在某种程度上说并不是好事,可以说妨碍了行业发展。当然我并不想诋毁微软做出的贡献。
好消息是C++委员会也已经为我们准备好了弥补这种平台差异的封装库boost::thread,同时可能成为未来语言标准的一部分。总体上看,boost::thread还是比较容易上手的,因为语言本身特性的缘故,甚至比起原始的线程库都更加的简洁。我们这里简单的示例一下:
代码:
void func()
{
// some code...
}
或者一个函数对象:
代码:
struct Func
{
// some fields or methods
void operator()()
{
// some code...
}
};
创建基于上述函数的线程对象:
代码:
#include <boost/thread.hpp>
boost::thread thread(&func);
boost::thread thread(Func());
一个线程也就是一个基于函数的对象,在这个对象构造(创建)出来的同时,线程同时也就被创建了。你甚至可以不用理会它的生命周期,即使这个对象已经越过了它的生命周期,但线程没有结束的话,它就仍然在继续运行,只是自动转换到detach模式下,也就是不需要人为的通过join处理线程结束后的一些清理工作。当然人们常常可能是需要保留它的句柄的,如果线程是在局部创建的,而我们需要保持的话,可以考虑boost:

hare_ptr这样的智能指针,减轻我们的负担:
代码:
#include <boost/smart_ptr.hpp>
#include <boost/thread.hpp>
boost::share_ptr<boost::thread> thread = new boost::thread(&func);
// some code ...
thread->join();
如果你使用了boost::thread,那么你就应该会同boost::bind打交道。因为boost::thread仅仅支持最简单的一种函数类型,即void()。如果我们需要向线程中传递参数怎么办呢?这个时候就要用到boost::bind;啊,你的线程一般是没有参数的,那么异步方法呢?什么是异步方法?请参考这一篇文章(3);当然你可以通过对象化,初始化等办法来做这些事情,但为什么要为这些事情写上一大堆毫无意义的代码呢?C信徒们一直在批判这种厚胶合层的方案(4)。好的,来看看boost::bind如何处理这个问题吧:
代码:
void func(int param)
{
// some code...
}
boost::thread thread(boost::bind(&func, 1));
等同于在线程中调用:
这样你需要建立对象类型的线程时就再也不用为参数传递而烦恼了,是不是很方便。难怪boost::thread搞得这么简单,还有boost:

ignal也依赖着boost::bind,等等。
有了线程,然后就多出了同步、互斥的问题来。当然boost::thread库都为我们准备好了这一切,而且是以C++的方式,你再也不用担心死锁等在C中常见的问题,这些负担都扔给编译器吧。这里不打算具体介绍了,参考文档,正确的使用他们很容易。
我们还是来看这篇文章(3)吧,作者通过对Java语言的分析,提出它所希望的一些语言特性,其中的一个主要方面就是“异步”关键字。大家知道,Java有synchronized关键字在语言上提供了对同步的支持,应该说这是一个充分释放程序员的特性(看来Gosling也是行伍出身啊)。因为从操作系统层次上说,为了维持同步这件事情,我们往往需要另外的同步对象或句柄,但对程序本身而言他们是多余的,这会增加我们在构建代码时的负担。
但换个角度看,这种方式也是有缺点的,因为它把同步的事情交给对象属主来决定,这往往不能直接反映真实世界的需要。底层的对象方法的同步异步往往根据具体情况的要求发生变动,当我们需要改动他们时就削弱了我们的预期。当然你也可以不管那么多,让他们都同步上再说,在实际中,这也可能往往不被接受。说到底这种变化没有被提升上来。
好的,既然语言中的基本执行单元是函数,那么在函数式编程中,我们能不能把这种能力交付给函数封装的层次来完成,就好比我们构造void()函数这样,我们构造出来新的函数封装原先的函数并附加上同步的能力。考虑到函数式编程中经常以传递函数子的结合方式,这种想法还是颇有诱惑力的。因为客户端代码就不用考虑同步的问题了,你尽管使用传递过来的函数就可以了。这在结构上来说也是合理的,更高层次的人才享有分派的权利。因为是封装函数,我们就借用boost::function库来实现就可以了,大部分代码仍然是照搬:
代码:
#include <boost/function.hpp>
#include <boost/thread.hpp>
#include <boost/preprocessor/iterate.hpp>
#include <boost/detail/workaround.hpp>
#ifndef BOOST_FUNCTION_MAX_ARGS
# define BOOST_FUNCTION_MAX_ARGS 10
#endif // BOOST_FUNCTION_MAX_ARGS
// Include the prologue here so that the use of file-level iteration
// in anything that may be included by function_template.hpp doesn't break
#include <boost/function/detail/prologue.hpp>
template<typename Signature, typename Allocator = std::allocator<void> >
class Synchronous;
# if BOOST_FUNCTION_MAX_ARGS >= 0
# define FUNCTION_NUM_ARGS 0
# ifndef SYNCHRONOUS_FUNCTION_0
# define SYNCHRONOUS_FUNCTION_0
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
# if BOOST_FUNCTION_MAX_ARGS >= 1
# define FUNCTION_NUM_ARGS 1
# ifndef SYNCHRONOUS_FUNCTION_1
# define SYNCHRONOUS_FUNCTION_1
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
# if BOOST_FUNCTION_MAX_ARGS >= 2
# define FUNCTION_NUM_ARGS 2
# ifndef SYNCHRONOUS_FUNCTION_2
# define SYNCHRONOUS_FUNCTION_2
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
# if BOOST_FUNCTION_MAX_ARGS >= 3
# define FUNCTION_NUM_ARGS 3
# ifndef SYNCHRONOUS_FUNCTION_3
# define SYNCHRONOUS_FUNCTION_3
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
# if BOOST_FUNCTION_MAX_ARGS >= 4
# define FUNCTION_NUM_ARGS 4
# ifndef SYNCHRONOUS_FUNCTION_4
# define SYNCHRONOUS_FUNCTION_4
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
# if BOOST_FUNCTION_MAX_ARGS >= 5
# define FUNCTION_NUM_ARGS 5
# ifndef SYNCHRONOUS_FUNCTION_5
# define SYNCHRONOUS_FUNCTION_5
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
# if BOOST_FUNCTION_MAX_ARGS >= 6
# define FUNCTION_NUM_ARGS 6
# ifndef SYNCHRONOUS_FUNCTION_6
# define SYNCHRONOUS_FUNCTION_6
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
# if BOOST_FUNCTION_MAX_ARGS >= 7
# define FUNCTION_NUM_ARGS 7
# ifndef SYNCHRONOUS_FUNCTION_7
# define SYNCHRONOUS_FUNCTION_7
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
# if BOOST_FUNCTION_MAX_ARGS >= 8
# define FUNCTION_NUM_ARGS 8
# ifndef SYNCHRONOUS_FUNCTION_8
# define SYNCHRONOUS_FUNCTION_8
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
# if BOOST_FUNCTION_MAX_ARGS >= 9
# define FUNCTION_NUM_ARGS 9
# ifndef SYNCHRONOUS_FUNCTION_9
# define SYNCHRONOUS_FUNCTION_9
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
# if BOOST_FUNCTION_MAX_ARGS >= 10
# define FUNCTION_NUM_ARGS 10
# ifndef SYNCHRONOUS_FUNCTION_10
# define SYNCHRONOUS_FUNCTION_10
# include "detail/synchronous_function_template.h"
# endif
# undef FUNCTION_NUM_ARGS
# endif
在detail/synchronous_function_template.h中:
代码:
#include "prologue.h"
#include <boost/smart_ptr.hpp>
#define FUNCTION_TEMPLATE_PARMS BOOST_PP_ENUM_PARAMS(FUNCTION_NUM_ARGS, typename T)
#define FUNCTION_TEMPLATE_ARGS BOOST_PP_ENUM_PARAMS(FUNCTION_NUM_ARGS, T)
#define FUNCTION_PARM(J,I,D) BOOST_PP_CAT(T,I) BOOST_PP_CAT(a,I)
#define FUNCTION_PARMS BOOST_PP_ENUM(FUNCTION_NUM_ARGS,FUNCTION_PARM,BOOST_PP_EMPTY)
#define FUNCTION_ARGS BOOST_PP_ENUM_PARAMS(FUNCTION_NUM_ARGS, a)
#define FUNCTION_ARG_TYPE(J,I,D) \
typedef BOOST_PP_CAT(T,I) BOOST_PP_CAT(arg, BOOST_PP_CAT(BOOST_PP_INC(I),_type));
#define FUNCTION_ARG_TYPES BOOST_PP_REPEAT(FUNCTION_NUM_ARGS,FUNCTION_ARG_TYPE,BOOST_PP_EMPTY)
// Comma if nonzero number of arguments
#if FUNCTION_NUM_ARGS == 0
# define FUNCTION_COMMA
#else
# define FUNCTION_COMMA ,
#endif // FUNCTION_NUM_ARGS > 0
#if FUNCTION_NUM_ARGS == 0
# define FUNCTION_PARTIAL_SPEC R (void)
#else
# define FUNCTION_PARTIAL_SPEC R (BOOST_PP_ENUM_PARAMS(FUNCTION_NUM_ARGS,T))
#endif
template<typename R FUNCTION_COMMA
FUNCTION_TEMPLATE_PARMS,
typename Allocator>
class Synchronous<FUNCTION_PARTIAL_SPEC, Allocator>
: public boost::function<FUNCTION_PARTIAL_SPEC, Allocator>
{
boost::shared_ptr<boost::mutex> mutex_ptr;
typedef typename boost::function<FUNCTION_PARTIAL_SPEC> base_type;
typedef typename base_type::result_type result_type;
typedef Synchronous self_type;
struct clear_type {};
// prevent copy construct.
Synchronous(const self_type& f)
: base_type(static_cast<const base_type&>(f))
, mutex_ptr(f.mutex)
{ }
self_type& operator=(const self_type& f)
{
self_type(f).swap(*this);
return *this;
}
public:
typedef typename base_type::allocator_type allocator_type;
Synchronous(boost::shared_ptr<boost::mutex> _mutex_ptr = boost::shared_ptr<boost::mutex>(new boost::mutex()))
: base_type()
, mutex_ptr(_mutex_ptr)
{ }
template<typename Functor>
Synchronous(Functor f
, boost::shared_ptr<boost::mutex> _mutex_ptr = boost::shared_ptr<boost::mutex>(new boost::mutex())
#ifndef BOOST_NO_SFINAE
,typename boost::enable_if_c<
(::boost::type_traits::ice_not<
(::boost::is_integral<Functor>::value)>::value),
int>::type = 0
#endif
)
: base_type(f)
, mutex_ptr(_mutex_ptr)
{ }
#ifndef BOOST_NO_SFINAE
Synchronous(clear_type*, boost::shared_ptr<boost::mutex> _mutex_ptr = boost::shared_ptr<boost::mutex>(new boost::mutex()))
: base_type()
, mutex_ptr(_mutex_ptr)
{ }
#endif
template<typename Functor>
#ifndef BOOST_NO_SFINAE
typename boost::enable_if_c<
(::boost::type_traits::ice_not<
(::boost::is_integral<Functor>::value)>::value),
self_type&>::type
#else
self_type&
#endif
operator=(Functor f)
{
self_type(f).swap(*this);
return *this;
}
#ifndef BOOST_NO_SFINAE
self_type& operator=(clear_type*)
{
this->clear();
return *this;
}
#endif
Synchronous(const base_type& f, boost::shared_ptr<boost::mutex> _mutex_ptr = boost::shared_ptr<boost::mutex>(new boost::mutex()))
: base_type(static_cast<const base_type&>(f))
, mutex_ptr(_mutex_ptr)
{ }
self_type& operator=(const base_type& f)
{
self_type(f).swap(*this);
return *this;
}
result_type operator()(FUNCTION_PARMS)
{
boost::mutex::scoped_lock lock(*mutex_ptr);
try {
return base_type::operator()(FUNCTION_ARGS);
} catch (...) {
lock.unlock();
throw;
}
}
};
#undef FUNCTION_PARTIAL_SPEC
#undef FUNCTION_COMMA
#undef FUNCTION_ARGS
#undef FUNCTION_PARMS
#undef FUNCTION_PARM
#undef FUNCTION_TEMPLATE_ARGS
#undef FUNCTION_TEMPLATE_PARMS
关键的部分就在于,我们在函数对象构造过程中,生成互斥子(mutex),然后在函数方法被调用时,对其加锁,然后调用其托管的原始函数,最后解锁并返回结果。
[注意]这其中需要注意的就是函数对象的拷贝,复制问题,一般我们希望的同步,是指原始函数和方法的同步,那我们在封装过后,再产生拷贝和复制动作(在函数式编程中函数子的传递可能会经常发生),我们希望仍然保持同步的能力,这里我们使用boost:
hare_ptr维持同样的一个互斥子来达到这个目的。也可以禁止拷贝和复制来阻止这种情况的发生。
以我们将来的实际中的例子来模拟就是,在我们的通信程序中,各个线程共享同一个信道,比如我们用Transport类来命名,这个Transport可能是个函数对象,其中的函数方法有参数发送缓冲区和接收缓冲区和他们的长度:
代码:
class Transport
{
// ...
public:
void operator()(unsigned char* send_buf, int send_len, unsigned char* recv_buf, int recv_len)
{
// ...
}
// ...
};
实际中的Transport可能需要一些参数来初始化和构建,我们这里简化处理了:
代码:
Synchronous<void(unsigned char*, int, unsigned char*, int)> synchronous_transport(
boost::bind(Transport(), _1, _2, _3, _4)
);
这里使用了占位符(place holder)来做函数的组合(composition),这是在实际中经常要用到,进一步推迟参数的实例化。然后把synchronous_transport这个实例传递给客户端代码,比如客户端的某个getData函数:
代码:
void getData(boost::function<void(unsigned char*, int, unsigned char*, int)>)
{
}
// ...
getData(synchronus_transport);
// ...
有了同步,那么异步实现起来就方便了。实际上我们往往不需要与同步相仿的异步功能,交给客户端自己去做好了,这个时候boost::thread结合boost::bind已经可以满足我们的需要了。
当然跟java的关键字相比,我们处理同步的时候需要关注的东西还是稍多一些,也没有synchronized的那样可以在语句块上加锁的能力。但我们解放了对象构建本身需要关注的一些方面,思维上不需要“往返”(round-robin)过程。再辅以恰当的粒化,可以说不亚于语言自身提供的功能。
抛开文中蠢顿的方法不谈,这一章的东西总体上应该是更加整洁了,也更加容易体会到函数式编程中带来的种种变化。
最后仍然来思考一个问题:你现在怎么看在文中提到的异步关键字?总体上我认为是不合理的。和synchronized关键字产生的副作用不一样,“异步方法”的侵入性太强了。
[思考题]本章希望对函数的附加上副作用的想法可以延伸到一个很宽泛的领域,就是眼下流行的面向方面编程(AOP)。请根据你所了解到的知识谈谈你所想象的AOP的实现方案。
参考链接:
(1)
intentsoft.com
(2)
Pthreads-win32
(3)
如果我是国王:关于解决Java编程语言线程问题的建议
(4)
Eric Raymond谈模块化原则,胶合层和面向对象的缺陷