2.1继承
线程同步构造的实现。在多线程应用中,经常需要限制并发访问共享资源而提供线程同步。线程同步的构造各式各样,最常见的是3中:信号,互斥和临界区。
信号方式提供了受限的并发。它允许最多给定上限的线程访问共享资源。当并发线程额最大数量为1时,我们称这种特殊的信号量为互斥。互斥方式通过在任何时间允许且只允许一个线程对共享资源操作来保护贡献资源。一般情况下共享资源可以由分散于应用程序各处的各个代码段进行操作。
互斥锁最简单的应用是临界区。临界区是指某一时间只能由一个线程执行的一个代码。线程在进入临界区之前就必须为获得锁而竞争来达到互斥的效果。
为了解决加解锁的问题,使用构造函数和析构函数去获取和共享资源锁类的实现如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Lock
{ public: Lock(pthread_mutex_t &key): theKey(key) { pthread_mutex_lock(&theKey); } ~Lock() { pthread_mutex_unlock(&theKey); } private: pthread_mutex_t &theKey; }; |
编程环境通常提供多种风格的同步构造。
并发级别:信号允许不多于给定最大数量的线程共享资源。互斥只允许一个线程访问共享资源。
嵌套 :某些构造允许线程在已持有一个锁的情况下再次获得该锁。而这种嵌套在另外一些构造的情况下则会发生死锁。
通知:在资源变成可用时,有一些同步构造会通知所有正在等待的线程。这种方式是很低效的,因为除了第一个线程之外,其他所有线程被唤醒后悔发现他们不够快,因为资源已经被其他资源获得。一个更为有效的通知方案是仅唤醒一个正在等待的进程。
读写锁:允许多个线程读取一个受保护的值,但是只允许一个线程修改。
内核/用户空间:某些同步机制只在内核空间中有效。
进程间/进程内: 一般情况下,同一个进程中的线程间同步要比不同进程中的线程同步更为有效。
为了验证C++的锁对象提供C++强大功能,同时不损失效率。
我们做了下面的实验:
1、直接调用pthread_mutex_lock()和pthread_mutex_unlock()。
2、不从基类继承的独立互斥对象。
3、从基类派生的互斥对象。
版本1:
1
2 3 4 5 6 7 8 9 10 11 |
int main()
{ //开始计时 for(int i = 0; i < 10000000; i++) { pthread_mutex_lock(&mutex); sharedCounter++; pthread_mutex_unlock(&mutex); } //停止计时 } |
版本2:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class SimpleMutex
{ public: SimpleMutex(pthread_mutex_t &lock): myLock(lock) { acquire(); } ~SimpleMutex() { release(); } private: int acquire() { return pthread_mutex_lock(&myLock); } int release() { return pthread_mutex_unlock(&myLock); } pthread_mutex_t &myLock; }; |
版本3:加入了继承:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class BaseMutex
{ public: BaseMutex(pthread_mutex_t &lock) {}; virtual ~BaseMutex() {}; }; class DeriveMutex: public BaseMutex { public: DeriveMutex(pthread_mutex_t &lock) : BaseMutex(lock), myLock(lock) { acquire(); } ~DeriveMutex() { release(); } private: int acquire() { return pthread_mutex_lock(&myLock); } int release() { return pthread_mutex_unlock(&myLock); } pthread_mutex_t &myLock; } |
上面三个版本的时间,版本1的时间1.01,版本2的时间是1.01,版本3的时间1.62。
可以得知,锁对象的高效性和无锁对象效率相差无几。另外基于继承的锁对象性能降低了大约60%。
对象的创建和销毁往往会造成性能的损失。在继承层次中,对象的创建将引起其先辈的创建。对象的销毁也是如此。其次,对象相关的开销与对象本身的派生链的长度和复杂性有关。所创建的对象的数量与派生的复杂度成正比。
2.2复合
对象的复合与继承一样,都引入了与对象创建和销毁有关的类似性能问题,在对象被创建和销毁时,必须同时创建它所包含的成员对象。如第一章的Trace类的初始实现的,string类是构造和销毁。2.3缓式构造
对象在需要时创建。
2.4冗余构造
避免隐式初始化,进行显式初始化,explicit。复制构造函数、operator=()函数。
2.4要点
1、构造函数和析构函数可以像手写编写的C代码一样有效。
2、对象的创建触发对父亲对象和成员对象的递归创建。开销。
3、要确保所编写的代码实际了所有创建的对象和这些对象所执行的计算。
4、对象的生命周期不是无偿的。至少对象的创建和销毁会消耗CPU周期。不要随意创建一个对象,除非你打算使用它。
5、编译器必须初始化被包含的成员对象之后在执行构造函数体。