作者:vollin
2009年4月
目录
目录
引言
一、 线程安全相关的概念
(一) 可重入性
(二) 线程安全
(三) 异步信号安全
(四) 三者的异同
二、 可重入准则
(一) 纯代码(purecode)函数一定是可重入函数
(二) 使用了局部静态变量的函数,是不可重入的
(三) 调用了不可重入函数的函数,是不可重入的
(四) 使用了同步机制的函数,是不可重入的
三、 Easy线程安全
(一) 弱化信号处理函数:
(二) 编写线程安全的类
(三) 简化数据同步
(四) 应用实例
结束语
参考资料:
附录1:基于互斥锁的安全对象及辅助类的完全实现
附录2:使用附录1代码的一个简单示例
引言
并发/并行编程中多线程编译占有重要的地位,编写一份线程安全的程序即使对于一名经验丰富的开发人员也是一种挑战。本文将通过详解线程安全的上的相关概念,确定出一系列的原则,帮忙多线程程序的开发人员能够容易的检查判断自己所编译的程序是否是线程安全的。相信这部分内容对所有有计划或已经编写过多线程程序的程序员会有一定的帮助。
同时根据部分易于产生线程安全问题的情况,针对C++编写多线程程序给出了一些实用的技术及相关代码供大家参考。这部分内容的阅读需要读者对C++有一定的了解;当然就其给出的思想也部分适用于其它面象对象的编程语言。
一、 线程安全相关的概念
可重入性(reentrant)是针对函数的,它有两个方面的内涵:
1, 可并行/并发①[vollin1] 同时进入:指可重入函数被某任务调用时,其它任务可同时进行调用而不产生错误的结果;或称在相同的输入情况下可重入函数的执行所产生的效果,并不因其并发的调用而产生不同,也称并发安全。
2, 中断后可重新进入:指可重入函数可被任意的中断,当中断执行完毕,返回断点可以继续正确的运行下去;或称在相同的输入情况下可重入函数的执行所产生的结果,并不因为在函数执行期间有中断的调用而产生不同,也称中断安全。
线程安全(MT-safe)不仅仅针对函数,它主要是指数据或程序的安全这一结果,所以有不同的层次上讲的线程安全:如线程安全的函数,线程安全的库②[vollin2] 。本文还会引入线程安全的类这一概念。通常意义上一个线程安全的函数是指有可重入函数第一个内涵的函数即并发安全的函数。但需要注意的是即使是一个从函数级别上并不安全的函数,如果使其不安全的因素在特定应用中并不存在时,这个函数对于该应用来讲同样也是线程安全的。例如对于全局变量的访问,一般而言未命名同步方式访问肯定是非线程安全的,但如果所有可能同时发生的访问均是只读访问则从结果上讲也是线程安全的。
信号的本质软中断,中断在本质上是异步的,所谓异步信号安全同线程安全一样,也是占在结果上考虑的,指在信号的中断处理过程中的安全。通常意义上一个异步信号安全的函数是指可以在异步信息处理函数中调用而不会出现异常的函数。同样需要注意到即使一个从函数级别上并非异步信息安全的函数,如果在信息处理函数中调用,也并不一定会产生不安全的结果。
对于一个多线程程序的安全来讲,通常包括了线程安全和异步信号安全这两个部分。从函数级别考虑,仅从概念上就可以发现可重入函数一定是线程安全函数,也是异步信号安全函数;多线程安全函数却要弱得多,并非一定要是可重入函数,它只要求并发无误即可;虽然异步信号函数与可重入函数的描述方式有所不同,但两者从实现层面上讲是完全一致的,或称可重入函数与异步信号安全函数这两个概念是等价的。
二、 可重入准则
如何编写或判断一个函数是否是可重入的呢?以下具体例出的一些经验将帮助您的编写和判断。
(一) 纯代码(purecode)函数一定是可重入函数
如果你编写的一个函数中所使用的所有数据均是局部变量(可以为寄存器变量,因为中断时寄存器中的数据也会入栈的)或形参,没有使用任何外部的全局变量和内部的静态变量,也没有使用系统函数,这样的函数就可以称为纯代码。
例:
int Addition(int n) { int s=0; for(register i=1;i<=n;i++) { s=s+i; } return s; } |
特别提示:一般仅仅只有仅用于计算的函数才属于此类型。
使用局部静态变量的函数都是不可重入的,但可以通过改变参数的方式进行简单的调整使其可重入,以下是一个示例:
//不可重入的版本 char* Str2Lower(char *s) { static char buffer[STR_MAX_LEN]={0}; for(int i=0;’/0’ != str[i];i++) { buffer[i]=tolower(s[i]); } buffer[i]=’/0’; return buffer; } //可重入的版本 char* Str2Lower_r(char *sIn, char* sOut) { for(int i=0;’/0’ != sIn[i];i++) { sOut[i]=tolower(s[i]); } sOut[i]=’/0’; return sOut; } |
特别提示:在函数内使用静态变量,但并不返回静态变量相关的引用,指针等的函数可以通过对静态变量的加解锁机制使其线程安全;但如果有将静态变量的相关信息返回就一定时非线程安全的。
调用了自己编写的函数自不必待言,本节主要说明如何判断一个系统函数是否一个可重入的函数,以使得容易判断自己编写的调用了系统函数的函数是否可重入:
1, 系统函数的函数名中带有“_r”后缀的函数都是可重入的,其对应的无后缀的函数是不可重入的。这类函数多是字符串相关,时间相关的函数,路径地址相关函数;其它以非入参指针为返回值的函数也应当重点怀疑可跳到第4点进行判断;
特别提示:这类非“_r”函数均在函数体中引用了全局变量或静态变量,所以也都是非线程安全的函数。
2, 内存分配与释放函数是不可重入的,因为内存的分配释放函数维护了一个链表,这使得在正常处理函数中调用分配函数时,被中断,且在中断中再调用内存分配释放函数则会出现异常;
特别提示:这类函数是线程安全的。
3, IO相关函数是不可重入的,如printf,及其它与文件描述符相关的操作函数(文件IO,网络IO,管道等本地同步IO);
特别提示:不涉及文件句柄的IO函数是线程安全的,涉及文件描述符的函数如果不是在对同一句柄操作则是线程安全的,仅当在需要多线程对同一文件描述符做操作时才需要添加同步机制,当然更好的办法应该是避免多线程有同时操作同一文件描述符的操作;特别的pread与pwrite即使在对同一个文件描述符操作时也是线程安全。
4, 最后一招:查系统man 手册,或IEEE Std 1003.1,例:关于usleep,IEEE Std 1003.1中有如下描述
The usleep() function need not be reentrant. A function that is not required to be reentrant is not required to be thread-safe.
这表明这个函数即非可重入的,也非线程安全的。
同步机制对于可重入是没有帮助的,或者说在中断面前同步机制是无效的,所以有同步机制的函数肯定是不可重入的,也当然不能够在信号处理函数中调用,但这却是最常用也最有效的消除线程不安全因素的手段。
特别提示:使用自旋锁进行同步是一种比较特殊的情况,自旋锁的实现在各个平台上都有所不同,而且也需要做特殊的处理才能够用于信号处理函数中,所以从严格意义上讲仍然不能算是可重入的函数。
三、 Easy线程安全
前文提到做为一个多线程程序,它的安全性需要从异步信号安全和线程安全两方面考虑,下面提供了一些建议及一些技巧,以使得保证多线程安全更加轻松如意。
信号处理函数对于多进程(特指每进程仅有一个线程的多进程)编程而言,是一个非常强有力的工具,因为对具体的每个进程而言不具有并发性所以需要有一些异步的手段来解决实际的编程问题,并且由于仅有一个线程,所以可以控制信号在希望出现的地方出现,这样能够避开在信号处理函数中仅能调用可重入函数的限制。但对于多线程编程而言信号处理的必要性就大大减弱,完全可以被多线程自身的并发性解决;另一方面由于多线程的并发造成无法保证所有的线程都在同一个段代码中等待信号的出现,这是与多线程编程的思想是相饽的,这也造成了如果要编写安全的信号处理函数,所受到的限制非常多,难于写出强大的信号处理函数。
一种简单易用,且绝对“安全”的信号处理方案如下:
volatile int g_nSignal=0;//用于保存最近一次信号值 void InitSignal(void)//初始化信号处理函数 { signal(SIGPIPE, SIG_IGN); signal(SIGCHLD,SIG_IGN); signal(SIGTERM,RecvSignal); signal(SIGHUP,RecvSignal); signal(SIGALRM,RecvSignal); signal(SIGXFSZ,RecvSignal); } void RecvSignal(int s)//通用信号处理函数 { g_nSignal = s; InitSignal(); } bool HandleSignal()//真实的信号处理函数,返回false表示退出 { int nSignal = g_nSignal; g_nSignal = 0; Switch(nSignal) { case 0: return true; case …. { …..//相关处理 break; } case SIGTERM: { …//安全退出相关代码 return false; } } return true; }
//建立专门的信号处理线程在其中执行 while (HandleSignal()){…//休眠短暂的时间或定时处理其它事务}; _exit(); |
在这个信号处理方案中,信号处理函数被弱化到仅仅做为信号收集的程度,将通过异步处理信号的方式来避免信号处理函数中可能带来的不安全因素。通过代码阅读大家可以发现前文在安全上加引号的原因:该方案在两个或以上信号在很短时间之内同时发生时,有可能产生覆盖,但是可以绝对的保证程序的安全,如果程序的主要处理不依赖信号,仅用信号来处理一些关键性的非频繁事件(如配置重载,程序退出等),则此方案可以绝对安全有效的完成。当然这也是基于前面的假设,我们要尽可能的弱化信号在程序处理中的做用。
实际上可以对以上方案做出少量的修改使得更加安全,但在信号处理的弱化这一大前提下,这样的改进也并非必要。
如果一定要编写信号处理函数,那么需要特别注意在这里只能使用可重入的函数,否则极易出现段异常,慎之慎之。
前文对函数的线程安全做了较多的讨论,如果对于纯函数式的语言如C来讲基本足够了,但对于面向对象的语言如C++,我们要使得程序安全,那么编写线程安全的类就变得重要了。
线程安全的类是怎样的一个概念呢?
1, 从STL的线程安全开始
并没有相关的文章专门讨论过类的线程安全,但似乎我们又经常在用,比如我们经常听到说stl容器是线程安全的,但又有文章称真正线程安全的stl容器并不存在,它们的安全需要程序员自己保证。怎么回事呢?我们要可以查到关于stl容器线程安全相关的声明:
n 多个读取者是安全的。多线程可能同时读取一个容器的内容,这将正确地执行。当然,在读取时不能有任何写入者操作这个容器。
n 对不同容器的多个写入者是安全的。多线程可以同时写不同的容器。
这样的声明清晰而不足够,本文引入线程安全的类这样的概念不但将有利用大家理解上面关于stl线程安全与不安全的争论,而且将利于大家编写出线程安全的类以及在此基础之上的线程安全的程序。
2, 线程安全类的定义
一个线程安全的类是指满足以下条件的类:
Ø 静态成员函数是线程安全的,即在多个线程调用该类的同一静态成员函数,能够正确的执行;
Ø 对于同一实例的const成员函数调用,可以同时安全的进行,当然在此过程中该实例不能有非const成员函数的调用或成员变量的写入操作;
Ø 非静态成员函数对于不同的实例是线程安全的,即可以在多个线程中以不同的实例调用同一动态成员函数,能够正确的执行。
只要一个类满足以上的三个条件,我们就可以在不同的线程中同时安全的使用该类的不同实例;如果需要多线程访问该类的同一实例时也只需要同使用一般全局数据变量一样,即:如果只有同时的读取则可以直接使用,如果有可能有写入操作那么就应该使用同步的方式来保证其数据的安全。
怎样理解这三个条件呢?类从设计的角度来讲就是一组相关的数据及其上的操作的集合,即类同时具有数据及函数两种特质,所以要使其安全也须要考虑这两个方面的因素。
a) 类的静态成员函数在使用方式上实际上同全局函数相差无几,所以我们对它的要求就如果一般的全局函数一样;所以一个线程安全的需要有线程安全的静态函数;
b) 类的具体实例数据(非静态数据)的访问上实际与一般的数据变量的访问相差无几,所以要求也一样:在只有只读访问时是完全的,在有写操作参与时所有操作均需要同步;而const成员函数在使用上是只当作对实例的只读访问的,所以需要这样的保证,实际上只需要多注意一下mutable这种数据成员变量,做出控制就可以达到这一点;
c) 类的非静态成员函数不仅可能涉及到具体实例的数据访问(这应当由类的使用者去保证安全),而且它又同时是一个函数,要线程安全当然需要保证在对全局变量或静态变量的使用符合线程安全的规范,这样才能够保证不同实例在多线程实用同一非静态函数时不会受到相互的影响。
d) 这三类成同函数是有交集的,不过很明显三者的要求,是依次减弱的,即对于静态const成员函数需要满足静态成员函数的要求;非静态const成员函数需要满足const成员函数的要求。
3, 到STL结束
回来STL的线程安全与不安全的争论,我们可以这样理解了:STL容器类是线程安全的类,但对于同一实例的多线程访问,还是需要由使用者去保证安全的。这就如一个其它任何共享数据要在多线程间共享一样,不外如是。
当然也应该注意到一个线程安全的类,与一个安全的结果仍然是不同的。很多时候我们自己定义的类,其实并不满足线程安全的条件,但只要注意其使用方式也是可以保证线程安全的。但如果是编写一个提供给其它程序员使用的类,则一定要注意线程安全类的规则,如果有不符的地方一定要做出特别的说明避免使用者进入陷阱,否则可是会遭人腹绯的哦。
多线程编程的一个极大的好处是多个线程共享存储空间,使得共享数据较多进程方便了不知几许倍。但数据的共享也带来了同步的问题,这无疑是多线程安全中最为重要的一环,也是多线程编程的重点之一,更是易于出现错误的地方,所以需要通过提高其抽象层次降低其出现错误的可能。
多线程同步的机制/方式多种多样,事件,信号量,条件变量,原子操作,锁,关键代码段等;仅拿锁来讲,就有各种分类的方式,各种锁如互拆锁,读写锁,自旋锁,递归锁等,以及他们的组合如递归自旋锁,自旋读写锁等;这些具体方式的讨论主要与同步效率有关,本文主要讨论线程安全所以对同步的具体方式和锁的类型不作多的讨论,仅以POSIX下最常用的默认线程互斥锁为例给出一些技巧以使得数据同步更加方便且不易出现错误。
1, 共享数组的组织
面向对象编程的一个极大的好处是可以提高编程设计的层次,这主要体现在能够将一些相关的数据及操作组织在一起形成一个单独的逻辑体――类。毫无疑问我们应当将总是在一起访问的共享数据组织在一起。另外我们发现每组共享数据的使用总是与一个锁同时使用③[vollin3] 。根据类的设计原则,这个锁对象无疑也应当与数据组织在一起。这将使得共享数据的访问更加方便和容易,然而如果我们只有一项数据要为这项数据专门设计一个类,是否反而会带来麻烦?
其实自从有了模板类以后,这样的烦恼是有解决办法的,以下的代码给出了一个解决方案。
template<class _Tp,class _Mutex = JXMutex> class JSafe { public: typedef _Tp value_type; typedef _Mutex lock_type; JSafe(void); ~JSafe(void); explicit JSafe(const _Tp& other); JSafe(const JSafe& other); JSafe& operator =(const JSafe& other); void Lock() const; void Unlock() const; void Value(const _Tp& other);//设置值 const _Tp Value(void) const; _Tp& Data(void); //加锁后方可安全使用此函数得到资源的引用,如未加锁直接调用可能造成相应的安全问题 protected: volatile _Tp m_Data; //数据成员 mutable _Mutex m_Mutex; //互斥锁成员 }; |
具体的实现很简单,因为涉及后文讲的技巧,此处没有列出,具体的实现可参见附录给出的代码。其中关于锁成员采用的是mutable 类型,这使得非数据的修改都可以是const的,在使用上更像是对常量的访问。JXMutex类是一个简单的互斥锁类。
可以看到有了这样的一个模板类,就可以很方便的将一个数据类型与一个互斥锁组织在了一起,从该模板类提供了一些简单安全设置及获取数据的操作。有点需要注意的是_Tp& Data(void)这个函数,及取出的数据引用都需要在锁的保护下操作;根据这样的思路,我们也可以针对具体的一些常用共享数据类型做一些扩展,甚至可以使得它们可以像一般的数据一样简单的使用,如下面这个安全数字类:
template<class _Tp> class JSafeNum : public JSafe<_Tp> { public: typedef JSafe<_Tp> safe_type; explicit JSafeNum(const _Tp& _Value = 0); JSafeNum& operator ++(); JSafeNum operator ++(int); JSafeNum& operator +=(const _Tp& other); JSafeNum& operator --(void); JSafeNum operator --(int); JSafeNum& operator -=(const _Tp& other); }; typedef JSafeNum<int> JSafeInt; typedef JSafeNum<u_int> JSafeu_int; 。。。 |
有了这样的一个安全数字类,关于数字的相关处理全都封装成线程安全的方法,要写一个全局计算器,只需要:
JSafeInt g_Count;//定义全局安全共享计算器
++g_Count;//在每个线程中如是计数
|
同样我们还可以定义出使作stl的安全序列容器,当然只能包括一些常用的操作,如常对共享的stl序列容器做的操作push_back,swap等,所有的方法都包装是不太现实的。但对于我们向共享序列中加入新的项,或全面处理时都会更加简单高效,而不易产生错误。
将锁与数据组成为一个新的类这种方式与为每组共享数据定义一个全局的锁这种方式来讲,不但少了为各种全局锁取名的麻烦,锁与数据对应的麻烦,减少了出错的可能;更令程序的数据组织结构显得清晰易读,程序的易读性在很多时候甚至比效率更加的重要。
2, 异常安全的锁使用
锁的加解锁需要严格配对,否则将造成死锁。也许这并不是一个很严格的要求,似乎程序员只要仔细一些就可以满足这个要求;然而实际并不尽如此。试想以下几种场景:
a) 在锁保护的代码段中便需要return:难道只能使用C++开发人员难以接受的goto语句?又或者组织一个夸张的多层次嵌套的条件从句?这样当然也能够完成任务,但出现疏漏的可能性大量增加,代码的阅读也将令人窒息;
b) 在锁保护的代码段中调用了一些可能产生异常的函数:难道每次加锁后都需要加一个try,catch语句?然而这些异常并不应该在本函数层次中进行处理啊,这将令写代码与阅读代码都成为一种折磨。当然更多的情况是没有try,异常还是产生了,并由上一级代码处理,而死锁也就这样产生了。
怎样才能写出异常安全的锁的使用?怎样既保证加的锁得到释放而又不用花费太多的心力?很自然的,我们想起了类的构造和析构,如果不是使用new,他们就是严格配对的,并且对于析构的控制并不需要手工的进行,在跨出了定义对象的大括号时④[vollin4] ,就自动的析构了。下面给出了一个辅助加锁类的实现。
template<class _Tp> class JLockHelper: { public: JLockHelper(_Tp& xMutex):m_xMutex(xMutex) { m_xMutex.Lock(); } ~JLockHelper() { m_xMutex.Unlock(); } protected: _Tp& m_xMutex; }; #define LOCK(mclass,mvalue) JLockHelper<mclass > __lock__(mvalue) |
这个实现如果我们需要对一个包含Lock(),Unlock()包成员函数的锁类或JSafe类实现加解锁,只需要在需要在一个大括号范围内定义一个JLockHelper对象即可,当大括号退出则自动解锁,如果某段代码中可能用到多个锁,也最好使用一组嵌套的大括号,将每个锁的控制范围括起来,这将使得每个锁的控制范围都更加的清晰易读。
如果使用Lock宏,则更加简单,因为每个辅助加锁变量的名字都相同,所以如果不用嵌套的大括号分开,还会出现编译器的错误提示,提示程序员需要加上大括号以标记不同锁的作用范围。
我们可以在任意的位置return出当前函数,当前函数中产生任何的异常也不用担心,辅助加锁类的析构函数会自动帮且我们解除锁定。
具体的使用方式如下:
JSafe<myclass> g_safe; //全局共享的安全myclass类 JSafe<otherclass> g_othersafe; //全局共享的安全otherclasss类
某线程使用的一个函数中访问时: Void DoSomethgin() { …. {//对g_safe的同步访问 LOCK(JSafe<myclass>,g_safe); if(…) { ……. return; } else {//对g_othersafe的同步访问 LOCK(JSafe<otherclass>,g_othersafe); …….//一些可能引起异常的调用 } …… } …. }
|
这样的使用方式是否令你感觉到对于多线程的同步来讲,真的是非常简单且决计不易出错。附录1还给出了另一组几乎类似的实现方案,可以使得辅助对象的使用Easy到令你心动以至于想立即试用一下。
3, 其它的同步技巧
关于此点,其实已经超出来线程安全的讨论范围,不过与前两点的使用有关,所以随便提及:
a) 关于JSafe类的成员函数Data(),必须在锁控制下操作,并且其取出的数据也必须在锁控制下操作,这实际上似乎是一种退化,这个函数也不过是为了该类的完整才提供出来。可以说在绝大多数的情况下不应该使用这个函数。首先对于一个简单的数据对象,如原生数据对象,这个函数是没有使用必要的可以用Value()函数来取代;其次对于一个复杂的数据对象,可以从JSafe中继承出来,为其特殊操作,做一个安全的包装,以使得其操作能够更加安全的进行;
b) stl容器对象,在多线程的数据交互中有很重要的地位,如何安全并且高效的使用?这里需要特别注意一个叫做swap的函数,特别对于非连续存储的list,map之类它的代价非常之低,以至于你可以在需要处理时先用一个空对象swap出来,有必要再swap或添加到同步数据中去,这不但能够避免安全风险,更能够减少锁定的时间,提高程序效率。
对于前面提到的一些技巧,给出了一个具体的实例,包括安全对象及辅助加锁类的实现(见附录1),以及一个类似于文件过滤器的多线程小程序(见附录2)。
大家可以在附录中找到具体的代码,代码可以在linux下g++ *.cpp –lpthread直接编译通过。大家可以从实例中看到,对于安全对象的使用会带来多大的便利,与使用原始的同步方式相比,不仅对于代码可阅读性产生无可比拟的提升,更使得多线程的编写成为了一种乐趣。
结束语
虽然本文提供了一些使得我们编写安全的多线程程序更加容易的方法和技巧,但是我仍然还是要说使一个多线程程序安全是一门艺术,也是一个挑战。它所涉及的内容远远超过本文所述,但它也并非那么遥不可及,它是我们触手可及的,只要我们多用心,多总结。
参考资料:
1.《使用可重入函数进行更安全的信号处理》
http://www.ibm.com/developerworks/cn/linux/l-reent.html
2.《IEEE Std 1003.1》 参见1中的参考资料
3.《多线程编程指南》http://docs.sun.com/app/docs/doc/819-7051/mtintro-75924
附录1:基于互斥锁的安全对象及辅助类的完全实现
/*
* =====================================================================================
*
* Filename: safe.h
*
* Description: MT safe
*
* Version: 1.0
* Created: 04/30/2009 01:00:58 PM CST
* Revision: none
* Compiler: gcc
*
* Author: vollinwan,
* Company:
*
* =====================================================================================
*/
#include <sys/types.h>
#include <pthread.h>
#ifndef __VOLLIN_J_SAFE_H__
#define __VOLLIN_J_SAFE_H__
#define LOCK(Mutex) JLockHelper __lock__(Mutex)
class JLockHelper;
struct JAbsXMutex
{
virtual void Lock() const= 0;
virtual void Unlock() const= 0;
};
/*****************************************************
* JXMutex X锁(互斥锁)类
*****************************************************/
class JXMutex:public JAbsXMutex
{
public:
JXMutex()
{
pthread_mutex_init (&m_Mutex,NULL);
}
virtual ~JXMutex()
{
pthread_mutex_destroy(&m_Mutex);
}
private:
JXMutex(const JXMutex&); //禁止锁拷贝
const JXMutex& operator =(const JXMutex &); //禁止锁赋值
public:
void Lock() const
{
pthread_mutex_lock (&m_Mutex); //锁定
}
void Unlock() const
{
pthread_mutex_unlock (&m_Mutex); //解锁
}
public:
mutable pthread_mutex_t m_Mutex; //互斥锁
};
/**************************************************************************************
* JSafe 基于各类锁的安全类模板
**************************************************************************************/
template<class _Tp,class _Mutex = JXMutex>
class JSafe
{
public:
typedef _Tp value_type;
typedef _Mutex lock_type;
JSafe(void){}
~JSafe(void){}
explicit JSafe(const _Tp& other)
{
m_Data = other;
}
JSafe(const JSafe& other)
{
LOCK(other);
m_Data = other.m_Data;
}
operator const _Tp() const //定义强制类型转换,注意已锁定时不能使用;虽然使用起来方便,但实际上都可以由获取值的Value函数代替
{
LOCK(m_Mutex);
return m_Data;
}
JSafe& operator =(const JSafe& other)
{
LOCK(other);
{
LOCK(m_Mutex);
m_Data = other.m_Data;
}
return *this;
}
void Value(const _Tp& other)//设置值
{
LOCK(m_Mutex);
m_Data = other;
}
const _Tp Value(void) const //获取值
{
LOCK(m_Mutex);
return m_Data;
}
void Lock(void) const //加锁
{
m_Mutex.Lock();
}
void Unlock(void) const //解锁
{
m_Mutex.Unlock();
}
_Tp& Data(void) //加锁后方可安全使用此函数得到资源的引用,如未加锁直接调用可能造成相应的安全问题
{
return m_Data;
}
protected:
_Tp m_Data; //数据成员
mutable _Mutex m_Mutex; //锁成员
friend class JLockHelper;
};
/**************************************************************************************
* JLockHelper 使用于有Lock,及Unlock成员函数的安全类或锁类的辅助加锁对象,
* 可以做到异常安全的解锁
* 说明: 在创建类时加锁,析构时解锁;一种简单的但不是必须的使用方法是使用{}
* 将要加锁的代码括起来,在{}中第一行定义该辅助类,则}时将自动解锁
****************************************************************************************/
class JLockHelper
{
public:
JLockHelper(const JAbsXMutex& xMutex):m_xMutex(xMutex)
{
m_xMutex.Lock();
}
template<class _Tp,class _Mutex>
JLockHelper(const JSafe<_Tp,_Mutex>& S):m_xMutex(S.m_Mutex)
{
m_xMutex.Lock();
}
~JLockHelper()
{
m_xMutex.Unlock();
}
private:
const JAbsXMutex& m_xMutex;
};
template<class _Tp>
class JSafeNum : public JSafe<_Tp>
{
public:
typedef JSafe<_Tp> safe_type;
explicit JSafeNum(const _Tp& _Value = 0):JSafe<_Tp>(_Value){}
JSafeNum& operator ++()
{
LOCK(safe_type::m_Mutex);
safe_type::m_Data++;
return *this;
}
JSafeNum operator ++(int)
{
LOCK(safe_type::m_Mutex);
JSafeNum tmp(safe_type::m_Data++);
return tmp;
}
JSafeNum& operator +=(const _Tp& other)
{
LOCK(safe_type::m_Mutex);
safe_type::m_Data += other;
return *this;
}
JSafeNum& operator --(void)
{
LOCK(safe_type::m_Mutex);
safe_type::m_Data--;
return *this;
}
JSafeNum operator --(int)
{
LOCK(safe_type::m_Mutex);
JSafeNum tmp(safe_type::m_Data--);
return tmp;
}
JSafeNum& operator -=(const _Tp& other)
{
LOCK(safe_type::m_Mutex);
safe_type::m_Data -= other;
return *this;
}
JSafeNum& operator =(const _Tp& other)
{
LOCK(safe_type::m_Mutex);
safe_type::m_Data = other;
return *this;
}
};
typedef JSafe<bool> JSafeBool;
typedef JSafeNum<int> JSafeInt;
typedef JSafeNum<long> JSafeLong;
typedef JSafeNum<u_int> JSafeUint;
//安全容器,适用于stl容器类
template<class _Con>
class JSafeCon :public JSafe<_Con>
{
public:
typedef JSafe<_Con> _Base;
typedef JSafeCon<_Con> _Self;
typedef _Con con_type;
typedef typename con_type::iterator iterator;
explicit JSafeCon(void):_Base(){}
bool empty()
{
LOCK(_Base::m_Mutex);
return _Base::m_Data.empty();
}
size_t size()
{
LOCK(_Base::m_Mutex);
return _Base::m_Data.size();
}
void swap(con_type& Seq)
{
LOCK(_Base::m_Mutex);
_Base::m_Data.swap(Seq);
}
//以下两个函数及其使用均需在同步的保护下
iterator begin()
{
return _Base::m_Data.begin();
}
iterator end()
{
return _Base::m_Data.end();
}
};
//安全序列(适用于stl的序列容器类如vector,list,...)
template<class _Sequence>
class JSafeSeq : public JSafeCon<_Sequence>
{
public:
typedef JSafeCon<_Sequence> _Base;
typedef JSafeSeq<_Sequence> _Self;
typedef _Sequence seq_type;
typedef typename seq_type::iterator iterator;
typedef typename seq_type::value_type value_type;
explicit JSafeSeq(void):_Base(){}
void push_back(const value_type& Val)
{
LOCK(_Base::m_Mutex);
_Base::m_Data.push_back(Val);
}
};
//适用于stl前向插入序列如deque等
template<class _FSeq>
class JSafeFSeq:public JSafeSeq<_FSeq>
{
public:
typedef JSafeSeq<_FSeq> _Base;
typedef JSafeFSeq<_FSeq> _Self;
typedef typename _Base::seq_type seq_type;
typedef typename seq_type::iterator iterator;
typedef typename seq_type::value_type value_type;
explicit JSafeFSeq(void):_Base(){}
bool pop_front(value_type& Val)//取出第一个元素
{
LOCK(_Base::m_Mutex);
if (_Base::m_Data.empty())
{
return false;
}
else
{
Val = *(_Base::m_Data.begin());
_Base::m_Data.pop_front();
return true;
}
}
};
//安全map类,适用于stl::map
template<class _Map>
class JSafeMap :public JSafeCon<_Map>
{
public:
typedef JSafeCon<_Map> _Base;
typedef JSafeSeq<_Map> _Self;
typedef _Map map_type;
typedef typename map_type::iterator iterator;
typedef typename map_type::const_iterator const_iterator;
typedef typename map_type::value_type value_type;
typedef typename value_type::first_type key_type;
typedef typename value_type::second_type data_type;
explicit JSafeMap(void):_Base(){}
void insert(const value_type& Val)
{
LOCK(_Base::m_Mutex);
_Base::m_Data.insert(Val);
}
bool find(const key_type& Key) const
{
LOCK(_Base::m_Mutex);
return _Base::m_Data.find(Key) != _Base::m_Data.end();
}
bool find(const key_type& Key,data_type& Val) const
{
LOCK(_Base::m_Mutex);
const_iterator it = _Base::m_Data.find(Key);
if ( _Base::m_Data.end() != it )
{
Val = it->second;
return true;
}
return false;
}
void erase(const key_type& Key)
{
LOCK(_Base::m_Mutex);
_Base::m_Data.erase(Key);
}
data_type get(const key_type& Key,const data_type& Default) const
{
LOCK(_Base::m_Mutex);
const_iterator it = _Base::m_Data.find(Key);
if ( _Base::m_Data.end() == it )
{
return Default;
}
else
{
return it->second();
}
}
bool find_erase(const key_type& Key,data_type& Val)
{
LOCK(_Base::m_Mutex);
iterator it = _Base::m_Data.find(Key);
if ( _Base::m_Data.end() != it )
{
Val = it->second;
_Base::m_Data.erase(it);
return true;
}
return false;
}
void set(const key_type& Key,const data_type& Val)
{
LOCK(_Base::m_Mutex);
_Base::m_Data[Key] = Val;
}
};
#endif //head file
附录2:使用附录1代码的一个简单示例
/*
* =====================================================================================
*
* Filename: mtmatch.cpp
*
* Description: 多线程的匹配工具,类似“grep vollin vollin.txt”
*
* Version: 1.0
* Created: 04/30/2009 01:11:07 PM CST
* Revision: none
* Compiler: g++
*
* Author: vollinwan
* Company:
*
* =====================================================================================
*/
#include "safe.h"
#include <deque>
#include <list>
#include <string>
#include <iostream>
#include <fstream>
using namespace std;
JSafeFSeq<deque<string*> > g_HandleDeque; //待处理队列
JSafeSeq<list<string*> > g_ResList; //结果列表
JSafeBool g_bPutOver(false); //输入结束标志
JSafeInt g_nCurTCnt(0); //当前运行的工作线程数
string g_sFilter; //过滤字符串
const u_int g_uTcnt = 3; //线程数
inline u_int usleep_r(u_int usec)
{
//usleep的线程安全版本
struct timespec rqtp;
struct timespec rem;
memset(&rem,0,sizeof(rem));
rqtp.tv_sec = usec/1000000;
rqtp.tv_nsec = usec%1000000*1000;
nanosleep(&rqtp, &rem);
return rem.tv_sec * 1000000 + rem.tv_nsec / 1000;
}
//工作线程的主函数
void* Work(void* pParam)
{
++g_nCurTCnt;
string* pIn=NULL;
while (1)
{
while (g_HandleDeque.pop_front(pIn))
{
if (pIn->find(g_sFilter) != string::npos)
{
g_ResList.push_back(pIn);
}
else
{
delete pIn;
}
}
if (g_bPutOver)
{
break;
}
else
{
usleep_r(1);
}
}
--g_nCurTCnt;
return NULL;
}
int main(int argc,char** argv)
{
string sUsage = string(argv[0]) + " filter filepath";
if (argc != 3)
{
cout<<sUsage<<endl;
_exit(1);
}
g_sFilter = argv[1];
ifstream f(argv[2]);
if ( !f )
{
cout<<argv[2]<<" can't open!"<<endl;
_exit(1);
}
pthread_t tI[g_uTcnt];
for (int i=0;i<g_uTcnt;i++)
{
if (0 != pthread_create(&tI[i],NULL,Work,NULL) )
{
cout<<"congratulations!"<<endl;
_exit(1);
}
}
string *pLine = new string;
while(getline(f,*pLine))
{
g_HandleDeque.push_back(pLine);
pLine = new string;
}
delete pLine;
g_bPutOver=true;
while (1)
{
list<string*> l;
if (!g_ResList.empty())
{
g_ResList.swap(l);
for (list<string*>::iterator it = l.begin();it != l.end();)
{
cout<<**it<<endl;
delete *it;
it = l.erase(it);
}
}
else if ( g_HandleDeque.empty() && g_nCurTCnt == 0)
{
break;
}
else
{
usleep_r(1);
}
}
return 0;
}