对象封装

C++与C语言最大的区别在于C++提供了对抽象数据的封装支持,在C语言中,常见的做法是定义struct,然后使用在struct外部定义的方法操作struct中的数据,这种做法剥离了数据与方法之间的联系,不利于保持程序结构的清晰与一致性。C++将结构体的概念扩展为“对象”,使之支持成员函数与继承。初看起来C++对象的概念比C结构体臃肿,实际上现代编译器采取了一系列优化方式降低封装对内存的额外消耗。

C++

线程同步构造

线程同步总体上分为两种构造:用户模式和内核模式。其中用户模式基于CPU指令,内核模式则基于操作系统内核对象。由于用户模式构造由硬件支持,且不发生用户-内核状态切换,因此效率较高。但线程使用基于用户模式的同步时,操作系统也无法捕捉到该线程上的阻塞。因此该线程在操作系统看来总是处于非阻塞状态,这意味着线程总是参与抢占时间片,导致线程无谓的浪费CPU时间。与之相对的是内核模式构造,线程使用操作系统提供的内核对象进行同步,每当进入同步块时,代码需要切换到内核态运行,导致巨大的性能损失。但优点是操作系统可以检测到等待同步对象的线程并将其阻塞,当同步对象可用时再唤醒线程。如果拥有“锁”的线程一直不释放,基于用户模式构造的等待线程将一直在CPU上无意义的运行,称之为“活锁”。基于内核模式构造的等待线程则一直被操作系统阻塞,称之为“死锁”。总体来说“死锁”优于“活锁”,因为前者不消耗CPU时间。

同步I/O

在Windows系统中,应用程序调用I/O函数发起的操作均发生在用户态,而用户态是无法访问I/O硬件设备的,因此Windows把用户态的I/O操作信息打包发送给内核,此时进入内核态访问对应的I/O设备。I/O设备处理请求的操作时,发起I/O请求的线程将被阻塞,只有当I/O操作完成时,才会重新唤醒线程以返回结果。如果在Socket中使用同步I/O操作,当大量客户端请求连接时,将消耗大量线程,造成性能的急剧下降。

使用Task

使用ThreadPool可以方便的发起异步操作,但却无法知道操作在何时完成,且无法直接获取操作的返回值,为此CLR提供了对线程池的再一次包装,即Task类。Task内部实际上也是使用线程池,只不过封装了一些高级功能。

CLR线程池

虽然CLR通过System.Threading名称空间提供了对Windows线程API的封装,但直接创建和销毁线程严重影响性能。过多的线程不仅浪费内存,而且会造成频繁的CPU上下文切换,更重要的是线程大部分时间都处于无所事事的状态,浪费宝贵的CPU时间。其次多线程非常容易引发滥用,特别是那些没什么多线程基础的程序员,为了所谓的“性能”无脑使用线程,即使在一些商业软件中这种现象也比比皆是。为了减少这种现象,CLR提供了原生的线程池支持。对于每个CLR,线程池线程将被所有运行中的托管程序集共享。

双端队列简介

顾名思义,deque支持在头尾进行插入删除操作。类似于vector和array,deque也是顺序存储结构,没有了解过deque的人在主观上可能会以为deque与vector底层结构类似,实际上为了支持头部插入与删除操作,deque采用分段连续存储的数据结构,因此实现上比vector复杂得多。因为采用分段设计,deque的迭代器效率比vector低。
为了管理片段,STL的deque在内部定义了一个_Map成员,_Map底层实际是一个T**,也可以将_Map看作一个数组,每一个数组元素指向内存中的一段连续的元素存储空间

迭代器概念

STL设计迭代器的初衷就是将容器细节与算法分离,转而用迭代器作为两者的桥梁。迭代器作为一种访问容器的结构,其本质上也是一种对指针的高级包装而已,因此对应于容器的结构特点,迭代器也分为单向迭代器(典型如单链表)、双向迭代器(如双链表)和随机访问迭代器(如vector等顺序存储容器)。





载入天数...载入时分秒...  |  总访问量为
Powered by Github and MarkdownPad 2

--------------------------- 别拉了,我是有底线的>_< ---------------------------