介绍RAII (Resource Acquisition Is Initialization) 资源获取即初始化,及其使用。
RAII概念
参考:
-
cppreference: RAII
RAII(Resource Acquisition Is Initialization),,是C++语言的一种管理资源、避免泄漏的机制,它将必须在使用前请求的资源(分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥体、磁盘空间、数据库连接等——任何存在受限供给中的事物)的生命周期绑定与一个对象的生存期相绑定。
C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。
根据 RAII 对象的生存期在退出作用域时结束这一基本状况,此技术的另一名称是作用域界定的资源管理( Scope-Bound Resource Management,SBRM)。
RAII 可总结如下:
- 将每个资源封装入一个类,其中
- 构造函数请求资源,并建立所有类不变式,或在它无法完成时抛出异常,
- 析构函数释放资源并决不抛出异常;
- 始终经由 RAII 类的实例使用满足要求的资源,该资源
- 自身拥有自动存储期或临时生存期,或
- 具有与自动或临时对象的生存期绑定的生存期
拥有 open()/close()、lock()/unlock(),或 init()/copyFrom()/destroy() 成员函数的类是非 RAII 类的典型的例子:
用途
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::mutex m;
void bad()
{
m.lock(); // 请求互斥体
f(); // 若 f() 抛异常,则互斥体永远不被释放
if(!everything_ok()) return; // 提早返回,互斥体永远不被释放
m.unlock(); // 若 bad() 抵达此语句,互斥才被释放
}
void good()
{
std::lock_guard<std::mutex> lk(m); // RAII类:互斥体的请求即是初始化
f(); // 若 f() 抛异常,则释放互斥体
if(!everything_ok()) return; // 提早返回,互斥体被释放
} // 若 good() 正常返回,则释放互斥体
C++标准库中RAII的体现
C++ 标准库:
遵循 RAII 管理其自身的资源:
- std::string、std::vector、std::thread,以及多数其他类在构造函数中获取其资源(错误时抛出异常),并在其析构函数中释放之(决不抛出),而不要求显式清理。
另外,标准库提供几种 RAII 包装器以管理用户提供的资源:
- std::unique_ptr 及 std::shared_ptr 用于管理动态分配的内存,或以用户提供的删除器管理任何以普通指针表示的资源;
- std::lock_guard、std::unique_lock、std::shared_lock 用于管理互斥体。
不适用场景
RAII 不适用于并非在使用前请求的资源:CPU 时间、核心,以及缓存容量、熵池容量、网络带宽、电力消费、栈内存等。
直接使用锁和使用封装器的对比
- 直接用锁
很容易漏unlock,也比较繁琐
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
#include <mutex> // class mutex;
std::mutex g_mutexThing;
int doSomething(const string &input, string &output)
{
int iRetVal = -1;
do
{
g_mutexThing.lock();
if (!condition1)
{
LOGGER_ERROR("error1");
g_mutexThing.unlock();
break;
}
if (!condition2)
{
LOGGER_ERROR("error1");
g_mutexThing.unlock();
break;
}
g_mutexThing.unlock();
iRetVal = 0;
} while (0);
return iRetVal;
}
- 类 lock_guard 是互斥封装器,为在作用域块期间占有互斥提供便利 RAII 风格机制。
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
#include <mutex> // class mutex; class lock_guard;
std::mutex g_mutexThing;
int doSomething2(const string &input, string &output)
{
int iRetVal = -1;
do
{
std::lock_guard<std::mutex> lockGuard(g_mutexThing);
if (!condition1)
{
LOGGER_ERROR("error1");
break;
}
if (!condition2)
{
LOGGER_ERROR("error1");
break;
}
iRetVal = 0;
} while (0);
return iRetVal;
}
- 若只需要在某个小范围执行加锁,则用{}定义语句块,临时变量lockGuard在语句块结束时则析构销毁,析构中进行解锁。
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
#include <mutex> // class mutex; class lock_guard;
std::mutex g_mutexThing;
int doSomething3(const string &input, string &output)
{
int iRetVal = -1;
do
{
{
std::lock_guard<std::mutex> lockGuard(g_mutexThing);
if (!condition1)
{
LOGGER_ERROR("error1");
break;
}
}
func1();
iRetVal = 0;
} while (0);
return iRetVal;
}
以下是对互斥锁mutex的进一步了解说明。 涉及C++版本问题,因此一并整理。
标准库头文件
参考:
- cppreference.com: 标准库头文件 mutex
mutex(互斥量/互斥锁)包含在C++的线程支持库中,C++包含线程、互斥、条件变量和future的内建支持。(C++11开始支持)
C++0x/C++11提供了对thread, mutex, condition_variable这些concurrency相关特性的支持
该标准头文件包含各类型的互斥量的类和函数,截取一部分:
1
2
3
4
5
6
7
8
9
10
11
12
namespace std {
class mutex; // 基本互斥锁 (C++11新增)
class recursive_mutex; // 能被同一线程递归锁定的互斥设施(C++11)
class timed_mutex; // 有时限锁定的互斥锁(C++11)
class recursive_timed_mutex; // 能被同一线程递归锁定的互斥设施,并实现有时限锁定(C++11)
template <class Mutex> class lock_guard; // 严格基于作用域的互斥体所有权包装器(C++11)
template <class Mutex> class unique_lock; // 可移动的互斥体所有权包装器(C++11)
template< class... MutexTypes >class scoped_lock; // 用于多个互斥体的免死锁 RAII 封装器(C++17)
//...
}
关于读写锁:
C++是从14之后的版本才正式支持共享互斥量,也就是实现读写锁的结构。
关于linux下的posix读写锁类型,另外再做介绍。
关于该头文件中各个类型的版本支持,在cppreference.com已经有说明了,也可参考: C++雾中风景12:聊聊C++中的Mutex,以及拯救生产力的Boost
在C++98中没有thread, mutex, condition_variable这些与concurrency相关的特性支持 参考: 漫话C++0x(五)—- thread, mutex, condition_variable
C++各版本简要过程
百度百科:c++0x
- 1998年是C++标准委员会成立的第一年,以后每5年视实际需要更新一次标准。
- 2009年,C++标准有了一次更新,一般称该草案为C++0x。
- C++0x是C++11标准成为正式标准之前的草案临时名字。
- 后来,2011年,C++新标准标准正式通过,更名为ISO/IEC 14882:2011,简称C++11。
维基百科:C++11
- C++11,先前被称作C++0x,即ISO/IEC 14882:2011,是C++编程语言的一个标准。
- 它取代第二版标准ISO/IEC 14882:2003 (第一版ISO/IEC 14882:1998公开于1998年, 第二版于2003年更新,分别通称C++98以及C++03,两者差异很小),且已被C++14取代
- C++14 旨在作为C++11的一个小扩展,主要提供漏洞修复和小的改进。
- 2014年8月18日,经过C++标准委员投票,C++14标准获得一致通过。ISO/IEC 14882:2014
- C++17 又称C++1z,是继 C++14 之后,C++ 编程语言 ISO/IEC 标准的下一次修订的非正式名称。
- 官方名称 ISO/IEC 14882:2017
- 基于 C++ 11,C++ 17 旨在简化该语言的日常使用,使开发者可以更简单地编写和维护代码。
- C++ 17是对 C++ 语言的重大更新
- 参考: C++ 17 标准正式发布
相比于C++03,C++11标准包含核心语言的新机能,而且扩展C++标准程序库,并入了大部分的C++ Technical Report 1程序库(数学的特殊函数除外)。
关于C++11的版本发布过程…:
上一个版本的C++国际标准是2003年发布的,所以叫C++ 03。然后C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是07年发布,所以最初这个标准叫C++ 07。但是到06年的时候,官方觉得07年肯定完不成C++ 07,而且官方觉得08年可能也完不成。最后干脆叫C++ 0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的时候也没完成,最后在2011年终于完成了C++标准。所以最终定名为C++11。 参考:c++ 0x和c++ 11是什么关系?0x又是什么意思?