相信很多人都读过《C++沉思录》这本经典著作,在我艰难地读完整本书后,留给我印象最深的只有一句话::“用类表示概念,用类解决问题”。
关于多线程编程,如果不是特别需要,大多数开发人员都不会特意去触碰这个似乎神秘的领域。如果在某些场合能正确并灵活地运用,多线程带来的好处是不言而喻的。然而,任何事物都有两面性,如果程序中引入多线程,那么我们需要谨慎小心地处理许多与之相关的问题,其中最突出的就是:资源竞争、死锁和无限延迟。那么面向对象与这些有什么关系了吗?有,面向对象的基础是封装,是的,正是封装,可以很好的解决多线程环境中的主要困境。
一.多线程环境
在开始之前,有必要先重温一下多线程环境。所谓,多线程编程,指的是在一个进程中有多个线程同时运行,每一个线程都有自己的堆栈,但是这些线程共享所有的全局变量和资源。
在引入互斥量之前,多线程的主要问题是资源竞争,所谓资源竞争就是两个或两个以上的线程在同一时间访问同一资源。为了解决这个问题,我们引入了互斥量,以同步多个线程对同一资源的访问。
然而在引入互斥量之后,新的问题又来了。因为如果互斥量的获得和释放没有得到正确处理,就会引起严重的问题。比如,某线程获得互斥量后,就可以对受保护的资源进行访问,但是如果访问完毕后,忘记了释放互斥量,那么其它的线程永远也无法访问那个受保护的资源了。这是一种较简单的情况,还有一种复杂的,那就是线程1已经拥有了资源A,但是它要拥有资源B后才能释放A,而线程2了恰好相反,线程2已经拥有了资源B但是它要拥有资源A后才能释放B。这样一来,线程1和线程2就永远地相互等待,这就是所谓的死锁。
死锁导致的问题是严重的,因为它使得程序无法正常运行下去。也许引入一些规范或约束有助于减少死锁发生的几率,比如我们可以要求,所有资源的访客(客户,使用者)都必须在真正需要资源的时刻请求互斥量,而当资源一使用完毕,就立即释放它,另外,锁定与释放一定要是成对的。如果上面的线程1和线程2都遵守这个规范,那么上述的那种死锁情况就不会发生了。
然而,规范永远只是规范,规范能被执行多少要依赖于使用者的自觉程度有多高,这个世界上总是有对规范和约束视而不见的人存在。所以,我们希望能够强制执行类似的约束,在对使用者透明的情况下。对于约束的强制实施可以通过封装做到。
二.多线程面向对象解决方案
首先你需要将系统API封装成基础类,这样你就可以用面向对象的武器类对付多线程环境,二是将临界资源与对其的操作封装在一个类中。这两点的核心都是将问题集中在一个地方,防止它们泛滥在程序的各个地方。
1. 将系统API封装成基础类。
1. 将系统API封装成基础类。
厌倦了每次涉及共享资源操作时都需要调用InitializeCriticalSection、DeleteCriticalSection、EnterCriticalSection、LeaveCriticalSection,并且它们是成对使用的,如果你调用了EnterCriticalSection,却忘了调用LeaveCriticalSection,那么锁就永远得不到释放,并且这些API的使用是很不直观的。我喜欢将它们封装成类,只需封装一次,以后就不用再查MSDN,每个API怎么写的了,参数是什么,免去后顾之忧。而且,在类的构造函数中调用InitializeCriticalSection,析构函数中调用DeleteCriticalSection,可以防止资源泄漏。面向对象的封装真是个好东西,我们没有理由拒绝它。
来看看我封装的几个与多线程环境相关的基础类。
// CriticalSection类用于解决对临界资源的保护
class CriticalSection
{
protected:
CRITICAL_SECTION critical_section ;
public:
CriticalSection()
{
InitializeCriticalSection(&this->critical_section) ;
}
virtual ~CriticalSection()
{
DeleteCriticalSection(&this->critical_section) ;
}
void Lock()
{
EnterCriticalSection(&this->critical_section) ;
}
void Unlock()
{
LeaveCriticalSection(&this->critical_section) ;
}
};
//Monitor用于解决线程之间的同步依赖
class Monitor
{
private:
HANDLE event_obj ;
public:
Monitor(BOOL isManual = FALSE)
{
this->event_obj = CreateEvent(NULL ,FALSE ,isManual ,"NONAME") ;
}
~Monitor()
{
//ReleaseEvent()
CloseHandle(this->event_obj) ;
}
void SetIt()
{
// 如果为auto,则SetEvent将event obj设为有信号,当一个等待线程release后,
//event obj自动设为无信号
//如果是manual,则release所有等待线程,且没有后面自动重设
SetEvent(this->event_obj) ;
}
void ResetIt()
{
//手动将event obj设为无信号
ResetEvent(this->event_obj) ;
}
void PulseIt()
{
// 如果为auto,则PulseEvent将event obj设为有信号,当一个等待线程release后,
//event obj自动设为无信号
//如果是manual,PulseEvent将event obj设为有信号,且release所有等待线程,
//然后将event obj自动设为无信号
PulseEvent(this->event_obj) ;
}
DWORD Wait(long timeout)
{
return WaitForSingleObject(this->event_obj ,timeout) ;
}
};
//Thread是对线程的简单封装
class Thread
{
private:
HANDLE threadHandle ;
unsigned long threadId ;
unsigned long exitCode ;
BOOL needTerminate ;
public:
public:
Thread(unsigned long exit_code = 0 )
{
this->exitCode = exit_code ;
this->needTerminate = FALSE ;
}
~Thread(void)
~Thread(void)
{
if(this->needTerminate)
{
TerminateThread(this->threadHandle ,this->exitCode) ;
}
}
long GetTheThreadID()
{
return this->threadId ;
}
void Start(FunPtr pfn ,void* pPara)//启动线程
{
this->threadHandle = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(pfn) ,pPara ,0,&(this->threadId));
}
void SetTerminateSymbol(BOOL need_Terminate)
{
this->needTerminate = need_Terminate ;
}
void wait(void)
{
WaitForSingleObject(this->threadHandle,INFINITE) ; //用于阻塞宿主线程,使其不能早于本线程结束
}
};
在大多数的多线程环境中,上述的几个类已经够用了,不如要实现更强劲的同步机制,你可以仿照上面自己进行封装。
2. 将临界资源与对其的操作封装在一个类中,如果这样做,锁的操作自动在类的实现中完成,而外部使用者不用关心是否处在多线程环境。也就是说这个类是线程安全的,在单线程和多线程环境下都可以使用。
比如我们经常需要使用线程安全的容器,我就自己封装了一个:
// SafeObjectList 线程安全的容器
#include <list>
#include "../Threading/CriticalSection.h"
plate<class T> class SafeObjectList : CriticalSection
{
private:
list<T> inner_list ;
list<T>::iterator itr ;
public:
void Add(T obj)
{
this->Lock() ;
this->inner_list.push_back(obj) ;
this->Unlock() ;
}
void Remove(T obj)
{
this->Lock() ;
for(this->itr = this->inner_list.begin() ;this->itr != this->inner_list.end() ;this->itr++)
{
if(obj == (*(this->itr)))
{
this->inner_list.erase(this->itr) ;
break ;
}
}
this->Unlock() ;
}
void Clear()
{
this->Lock() ;
this->inner_list.clear() ;
this->Unlock() ;
}
int Count()
{
return (int)this->inner_list.size() ;
}
BOOL Contains(T& target)
{
BOOL found = FALSE ;
this->Lock() ;
for(this->itr = this->inner_list.begin() ;this->itr != this->inner_list.end() ;this->itr++)
{
if(target == (*(this->itr)))
{
found = TRUE ;
break ;
}
}
this->Unlock() ;
return found ;
}
BOOL GetElement(int index ,T& result)
{
BOOL succeed = FALSE ;
this->Lock() ;
if(index < (int)this->inner_list.size())
{
int i= 0 ;
for(this->itr = this->inner_list.begin() ;this->itr != this->inner_list.end() ;this->itr++)
{
if(i == index)
{
result = (*this->itr) ;
break ;
}
i++ ;
}
succeed = TRUE ;
}
this->Unlock() ;
return succeed ;
}
};
在将临界资源与对其的操作封装在一个类中的时候,我们特别要需要注意的一点是封装的线程安全的方法(函数)的粒度,粒度太大则难以复用,粒度太小,则可能会导致锁的嵌套。所以在封装的时候,一定要根据你的具体应用,视情况而定。我的经验是这样的,首先可以把粒度定小一点,但是一旦发现有锁的嵌套出现,就加大粒度,把这两个嵌套合并成一个稍微粗一点粒度的方法。
分享到:
相关推荐
《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。...
还提供了上百个如何在vcl环境下使用c++ builder功能的技巧、具有实践性的建议以及数百个可以立即运行的重要解决方案的详细代码,内容主要涉及以下几个方面:标准c++基础知识、vcl库、windows窗体、图形编程、多线程...
《编写高质量代码:改善c#程序的157个建议》一共三个部分,第一部分专注于c#语言本身,一共89条建议,涵盖了c#语言基本要素、集合、linq、泛型、委托、事件、资源管理、序列化、异常处理、异步、多线程、任务和并行...
可移植C多线程库 Python 把C类和映射到Python的中 Pool 内存池管理 smart_ptr 5个智能指针学习智能指针必读份不错参考是来自CUJ文章: Smart Poers in Boost,哦这篇文章可以查到CUJ是提供在线浏览中文版见笔者在Dr....
从Java的基础语法到最高级特性(深入的面向对象概念、多线程。自动项目构建。单元测试和调试等),本书能逐步指导你轻松掌握。 本书内容 经典的Java介绍,完全针对Java1.4版本更新,并增加了许多新主题。 350多个...
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受...
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受...
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受...
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受...
AfxMessageBox()是模态对话框,你不进行确认时程序是否往下运行时,它会阻塞你当前的线程,除非你程序是多线程的程序,否则只有等待模态对话框被确认。 在MFC中,afxmessagebox是全局的对话框最安全,也最方便。...
Java语言的优点主要表现在:简单、面向对象、多线程、分布性等方面。 (1) 简单性 Java与C++语言非常相近,但Java比C++简单,它抛弃了C++中的一些不是绝对必要的 功能,如头文件、预处理文件、指针、结构、运算符...
J2EE 企业级解决方案的开发,基于WEB的开发等,(Java to Enterprise Edition) ; 2、Java的特点: 序号 语言角度 学习角度 1 比C++简单,放弃了对指针的使用; 入门迅速,容易学; 2 目前最好的网络编程语言; 编写...
加入微软之前,他是Credant科技安全解决方案方面的首席软件工程师,同时还在一家为微软Vista提供蓝牙解决方案的大型蓝牙公司兼职。在转向.NET之前,他擅长使用C/C++/ATL开发COM/DCOM。 目录 第1章 c#预览 第2章 c#...
加入微软之前,他是Credant科技安全解决方案方面的首席软件工程师,同时还在一家为微软Vista提供蓝牙解决方案的大型蓝牙公司兼职。在转向.NET之前,他擅长使用C/C++/ATL开发COM/DCOM。 目录 第1章 c#预览 第2章 c#...
1.9 多线程 1.10 永久性 1.11 Java和因特网: 既然Java不过另一种类型的程序设计语言,大家可能会奇怪它为什么值得如此重视,为什么还有这么多的人认为它是计算机程序设计的一个里程碑呢?如果您来自一个传统的程序...
2.了解Windows 2000/XP中多线程的并发执行机制,线程间的同步和互斥。 3.学习使用Windows 2000/XP中基本的同步对象,掌握相应的API。 三、实验要求 1.生产者消费者对缓冲区进行互斥操作。 2.缓冲区大小为10,缓冲区...
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受...
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受...
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受...