构造函数和析构函数
理论上,构造函数和析构函数是不存在开销的,它们将只执行必要的初始化和清除工作,普通的编译器都会内联它们。本文将从继承、复合、缓式构造、冗余构造等几个方面来分析其性能。
在介绍继承的设计与构造函数和析构函数的开销之间的关系之前,先来介绍一下线程同步构造,因为我们将要借用这个实例。
在多线程应用程序中,常常需要为了限制并发访问共享资源而提供线程同步。线程同步的构造各式各样,最常见的有3种:信号(semaphore)、互斥(mutex)、临界区(citical section)。
信号方式提供了受限并发。它允许最多为给定上限的线程访问共享资源。当并发线程的最大数量为1时,我们称这种特殊的信号量为互斥(Mutual exclusion)。互斥方式通过在任何时间允许且只允许一个线程对共享资源进行操作来保护共享资源。一般情况下共享资源可以由分散于应用程序各处的各个代码进行操作。
互斥锁最简单的应用是临界区。临界区是指某一时间只能由一个线程执行的一个代码段。线程在进入临界区之间就必须为获得锁而竞争来达到互斥的目的,成功获取锁的线程就可以进入临界区。当要退出临界区时,该线程释放锁以允许其他线程进入。如果在代码执行的过程中于某处获得了锁,那么执行任何一条返回语句之前必须释放锁,然而这几乎是很难做到的,因为总会有忽略之前曾获得锁这个事实。这是一个问题。另外一个问题是异常:如果抛出一个异常的同时又持有锁,那么只好在捕获该异常时手工释放锁。这是很别扭的。
C++则为这两个难题提供了很好的解决办法。当一个对象到达其作用域结尾时,会自动调用构造函数。所以我们可以利用自动调用析构函数来解决锁的维护问题。把锁封装在对象内部并让构造函数获得锁,析构函数将自动释放锁。请看下面代码:
class Lock
{
public:
Lock(pthread_mutex_t& key) : theKey(key)
{
pthread_mutex_lock(&theKey);
}
~Lock()
{
pthread_mutex_unlock(&theKey);
}
private:
pthread_mutex_t &theKey;
};
编程环境通常提供各种风格的同步构造。风格区别表现在一下几个方面:
并发级别:信号允许不多于给定最大数量的线程共享资源。互斥只允许一个线程访问共享资源
嵌套:某些构造允许线程在已持有一个锁的情况下再次获得该锁。而这种锁嵌套在另外一些构造的情况下会发生死锁。
通知:在资源为可用时,有一些同步构造会通知所有正在等待的线程。这种方式是很低效的。一个更为有效的方案是仅唤醒一个正在等待的线程。
读写锁:允许多个线程读取一个受保护的值,但是只允许一个线程修改它。
内核/用户空间:某些同步机制只在内核空间有效。
进程间/进程内:一般情况下,同一进程中的线程间同步要比不同进程中的线程同步更为有效。
尽管这些同步构造在语义和性能方面差别很大,但都是用相同的锁/解锁协议。也就是说,他们都继承自同一基类:
class BaseLock
{
public:
BaseLock(pthread_mutex_t &key, LogSource &lsrc)
{};
virtual ~BaseLock()
{};
};
下面来看一下MutexLock类:
class MutexLock : public BaseLock
{
public:
MutexLock(pthread_mutex_t &key, LogSource &lsrc);
~MutexLock();
private:
pthread_mutex_t &theKey;
LogSource &src;
};
//构造函数和析构函数的实现
MutexLock::MutexLock(pthread_mutex_t& key, const LogSource& source) : BaseLock(key, source), theKey(key), src(source)
{
pthread_mutex_lock(&theKey);
#ifdef(DEBUG)
cout << "MutexLock " << &key << "created at " << src.file() << "line" << src.line() << endl;
#endif
}
MutexLock::~MutexLock()
{
pthread_mutex_unlock(&theKey);
#ifdef(DEBUG)
cout << "MutexLock " << &key << "destroyed at " << src.file() << "line" << src.line() << endl;
#endif
}
//其中LogSource类继承自BaseLogSource类,只要是为了获取记录(当前文件名和当前行数)
LogSource对象就这样被创建并作为参数传递给MutexLock对象的构造函数。在得到锁时LogSource对象捕获源文件和行号。这些信息在调试死锁时会派上用场。
未完,待续!