天天看点

《linux多线程服务端编程》---- C++基础前奏1 mutable2 C++二阶构造3 野指针和空悬指针4 observer模式5 值语义6 spinlock自旋锁7 写时复制(COW)8 Explicit关键字9 读写锁10 Read-Copy-Update(RCU)11 POD数据

1 mutable

在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。

该关键字修饰类中数据成员时,会释放掉non-static成员变量的bitwise constnes约束,即使在const成员函数依然可以修改该数据成员变量。

Immutable object (不可变对象) :当对象被创建后,你不能修改对象的状态以及字段。

2 C++二阶构造

【C++深度剖析教程4】C++的二阶构造模式_厚积薄发-CSDN博客

3 野指针和空悬指针

野指针:

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。

成因:

(1)指针变量未初始化

任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

(2)指针释放后之后未置空

有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。

(3)指针操作超越变量作用域

不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

空指针:

 #define NULL    ((void *)0) 

在内存分配方面,较小的地址是不用来存放数据的,也不允许程序访问的。所以,指针指向了它,就是这个指针不能操作它指向的这块较小的地址。

简单来说,空指针有指向,但是它指向的地址是特殊的,该地址不允许存放数据和不允许程序访问,所以空指针不能操作该地址里的东西,我们就理解为“指针指向了空,无法操作了”。

void * 类型指针,

这个类型指针指向了实实在在的存放数据的地址,但是该地址存放的数据的数据类型我们暂时不知道。

空悬指针

指向已经销毁的对象或已经回收的地址。

4 observer模式

C++ 观察者模式_上善若水-CSDN博客_cpp 观察者模式

5 值语义

所谓值语义是一个对象被系统标准的复制方式复制后,与被复制的对象之间毫无关系,可以彼此独立改变互不影响。在C++中使用拷贝构造和赋值。

与值语义对应的是“对象语义/object sematics”,或者叫做引用语义(reference sematics),由于“引用”一词在 C++ 里有特殊含义,所以我在本文中使用“对象语义”这个术语。对象语义指的是面向对象意义下的对象,对象拷贝是禁止的。例如 muduo 里的 Thread 是对象语义,拷贝 Thread 是无意义的,也是被禁止的:因为 Thread 代表线程,拷贝一个 Thread 对象并不能让系统增加一个一模一样的线程。

值语义的对象要么是 stack object,或者直接作为其他 object 的成员,因此我们不用担心它的生命期(一个函数使用自己stack上的对象,一个成员函数使用自己的数据成员对象)。相反,对象语义的 object 由于不能拷贝,我们只能通过指针或引用来使用它。

6 spinlock自旋锁

自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分

何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:

(1)死锁

试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。在递归程序中使用自旋锁应遵守下列策略:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂“自旋”,也无法获得资源,从而进入死循环。

(2) 占用过多CPU资源

过多占用cpu资源。如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了. 因此,一般自旋锁实现会有一个参数限定最多持续尝试次数. 超出后, 自旋锁放弃当前time slice. 等下一次机会。

自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。

信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。

7 写时复制(COW)

Copy On Write机制了解一下_u012501054的博客-CSDN博客_copy on write

8 Explicit关键字

C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).

C++ explicit关键字详解 - 网名还没想好 - 博客园 (cnblogs.com)

9 读写锁

《linux多线程服务端编程》---- C++基础前奏1 mutable2 C++二阶构造3 野指针和空悬指针4 observer模式5 值语义6 spinlock自旋锁7 写时复制(COW)8 Explicit关键字9 读写锁10 Read-Copy-Update(RCU)11 POD数据

10 Read-Copy-Update(RCU)

RCU就是指读-拷贝修改,它是基于其原理命名的。对于被RCU保护的共享数据结构,读操作不需要获得任何锁就可以访问,但写操作在访问它时首先拷贝一个副本,然后对副本进行修改,最后在适当的时机把指向原来数据的指针重新指向新的被修改的数据。这个时机就是所有引用该数据的CPU都退出对共享数据的操作。

Linux内核中内存管理大量的运用到了RCU机制。为每个内存对象增加了一个原子计数器用来继续该对象当前访问数。当没有其他进程在访问该对象时(计数器为0),才允许回收该内存。

从这个流程可以看出,RCU类似于一种读写锁的优化,用于解决读和写之间的同步问题。比较适合读多,写少的情况,当写操作过多的时候,这里的拷贝和修改的成本同样也很大。(写操作和写操作之间的同步还需要其它机制来保证)。

11 POD数据

POD 是 Plain Old Data 的缩写,是 C++ 定义的一类数据结构概念,比如 int、float 等都是 POD 类型的。Plain 代表它是一个普通类型,Old 代表它是旧的,与几十年前的 C 语言兼容,那么就意味着可以使用 memcpy() 这种最原始的函数进行操作。两个系统进行交换数据,如果没有办法对数据进行语义检查和解释,那就只能以非常底层的数据形式进行交互,而拥有 POD 特征的类或者结构体通过二进制拷贝后依然能保持数据结构不变。也就是说,能用 C 的 memcpy() 等函数进行操作的类、结构体就是 POD 类型的数据。

基本上谈到这个概念,一般都是说某某 class、struct、union 是不是 POD 类型的。

是不是 POD 类型的,可以用 is_pod<T>::value 来判断。那什么样的类、结构体是拥有 POD 特性的呢?要求有两个:一个是它必须很平凡、很普通;另一个是布局有序。

什么是 POD 数据类型? - 知乎 (zhihu.com)

继续阅读