既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
最近在看线程池这一块的知识点,其中遇到了使用C++类封装互斥锁的办法,特此记录下。
参考书籍:《提高C++性能的编程技术》第二章
一、问题由来
有时我们会遇到两个进/线程共同使用同一个资源的情况,这个资源就称为临界区。临界区是指某一时间只能有一个线程执行的一个代码段。示例:
get_the_lock()
//临界区的开始
....//被保护的计算
//临界区的结束
release_the_lock()
如果是一个简单的代码,我们使用这种直接的方式对每一个临界区进行相对应的加锁、解锁操作即可。可实际情况下当我们遇到一个几百行的项目代码含有多条返回语句时,如果代码在执行过程中获得了锁,那么在执行任何一条返回语句前都需要释放锁。另外一个问题是:如果抛出一个异常的代码同时又持有锁,那么只好在捕获该异常时手工释放锁。
这将是开发工程师的梦魇!
那么如何解决呢?C++利用了一个非常好的特性:当一个对象初始化时自动调用构造函数,当一个对象到达其作用域结尾时,自动调用析构函数。所以我们可以利用这个特性解决锁的维护问题:把锁封装在对象内部!此时,在构造函数时获得锁,在语句返回前自动调用析构函数释放锁。
其实这种做法有个专有的名称,叫做RAII(Resource Acquisition Is Initialization)
二、C++封装互斥锁
使用上文直接的方法有一个非常好的特性:高效。
那么锁对象有哪些好处呢?
- 对包含多个返回点的复杂子程序的维护。
- 从异常中恢复
- 锁操作的多态
- 日志记录的多态。
那么锁对象有哪些坏处呢?
为了解决锁的维护问题(提高了性能),可我们不得不使用面向对象的设计,增加了对象的创建与销毁的开销。
因此,我们下一步的目的是尽量减少它的开销(内联通常是一种补救办法,减少函数调用的开销。)。
利用锁对象可以提供C++新增的强大功能,同时不损失效率。为了验证这个假设,我们测试了三种互斥锁的实现:
- 直接调用pthread_mutex_lock()和pthread_mutex_unlock()
- 不从基类继承的独立互斥对象。
- 从基类派生的互斥对象。
#include <iostream>
#include <ctime>
#include <pthread.h>
using namespace std;
//版本2:锁类,构造函数加锁,析构函数解锁
//用c++简单的封装线程中互斥锁
class SimpleMutex {
public:
SimpleMutex(pthread_mutex_t& lock) :myLock(lock) { acquare(); }
~SimpleMutex() { release(); }
private:
inline int acquare() { return pthread\_mutex\_lock(&myLock); }
inline int release() { return pthread\_mutex\_unlock(&myLock); }
pthread_mutex_t &myLock;//成员变量myLock必须是pthread\_mutex\_t的引用
};
//版本3:锁类加入了继承
class BaseMutex {
public:
BaseMutex(pthread_mutex_t& lock) {};
virtual ~BaseMutex() {};
};
class DerivedMutex: public BaseMutex{
public:
DerivedMutex(pthread_mutex_t& lock)
: BaseMutex(lock),myLock(lock) //派生类构造,成员初始化列表
{
acquare();
}
~DerivedMutex() {
release();
}
private:
inline int acquare() { return pthread\_mutex\_lock(&myLock); }
inline int release() { return pthread\_mutex\_unlock(&myLock); }
pthread_mutex_t& myLock;//成员变量myLock必须是pthread\_mutex\_t的引用
};
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始
int main() {
unsigned long sharedCounter = 0;
time_t startTime,endTime;
double diffTime;
//版本1:简单地在共享资源前后使用互斥锁
startTime = clock();
for(int i=0; i < 1000000; ++i) {
pthread\_mutex\_lock(&mutex);
sharedCounter++;
pthread\_mutex\_unlock(&mutex);
}
endTime = clock();
diffTime = (double) (endTime - startTime)/CLOCKS_PER_SEC;
cout << "version #1:mutex cost " << diffTime << " ms\n";
//版本2:单独的锁类
startTime = clock();
for(int i=0; i < 1000000; ++i) {
SimpleMutex m(mutex);
sharedCounter++;
}
endTime = clock();
diffTime = (double) (endTime - startTime)/CLOCKS_PER_SEC;
cout << "version #2:simple mutex class cost " << diffTime << " ms\n";
//版本3:继承的锁类
startTime = clock();
for(int i=0; i < 1000000; ++i) {
DerivedMutex m(mutex);
sharedCounter++;
}
endTime = clock();
diffTime = (double) (endTime - startTime)/CLOCKS_PER_SEC;
cout << "version #3:derived mutex class cost " << diffTime << " ms\n";
return 0;
}
注意:
在锁对象类中,成员变量myLock必须是pthread_mutex_t的引用,这样才能确保每次调用SimpleMutex m(mutex) 的时候,SimpleMutex ::myLock都是同一份 myLock。如果去掉引用符&
,则每次执行 SimpleMutex m(mutex) 的时候,myLock实际上都是一个不同的 myLock。
这里感谢文章的提醒。
输出:
这是运行100万次测试的时间开销,版本1和版本2的时间开销几乎相同,版本3的时间开销相对高一些。
版本2中编译器将构造函数和析构函数内联从而达到了最高效率,版本3中使用继承相对代价较大,因此浪费了一些开销。
三、C++11互斥锁
对于没有迁移到C++11上的项目可以使用自己封装的Mutex来提高易用性,其实在C++11 线程库中已经有lock guard可以直接使用了(std::lock_guard ,只需要include<mutex>
),不需要自己再写一遍。
#include <iostream>
#include <ctime>
#include <mutex>
using namespace std;
int main() {
std::mutex myMutex;
unsigned long sharedCounter = 0;
//版本4:使用c++11互斥锁
for(int i=0; i < 1000000; ++i) {
![img](https://img-blog.csdnimg.cn/img_convert/c7d31c64e70d871165a47787e7378917.png)
![img](https://img-blog.csdnimg.cn/img_convert/18ddb67daa4b6c98f1552bf9190738fb.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**