1.使用智能指针进行C++资源管理
(1). 使用智能指针:shared_ptr<T>
每个shared_ptr<T>拥有一个其所管理的底层对象指针,及该对象的引用计数.在shared_ptr<T>对象拷贝构造/赋值时,会对参与运算的对象的底层对象引用计数进行更新,正确反映对其引用的shared_ptr<T>对象个数.引用计数变为0时,底层对象被销毁.
注意点:
a. 一旦使用shared_ptr<T>作为动态分配对象的生命期管理手段,就应在new T()外的其他所有地方,全部使用shared_ptr<T>作为对象访问的手段,不要使用对象原始指针.
b. 在使用shared_ptr<T>调T的成员函数内部,
如有基于调用的shared_ptr<T>对象产生shared_ptr<T>的需求,应该:
b.1.T公共继承std::enable_shared_from_this<T>
b.2. 在需要基于调用shared_ptr<T>对象产生shared_ptr<T>处,使用shared_from_this().shared_from_this()将返回一个shared_ptr<T>对象,该对象和调用基于的shared_ptr<T>,拥有相同的底层对象及引用计数对象.
(2). 使用智能指针:weak_ptr<T>
一个weak_ptr<T>只能基于一个shared_ptr<T>对象来得到.weak_ptr<T>对象拥有与基于的shared_ptr<T>对象,一样的底层对象及引用计数对象.
但特色是:
a.weak_ptr<T>本身不能访问底层对象,如要通过weak_ptr<T>访问底层对象,先通过lock成员函数,返回shared_ptr<T>,再通过返回的shared_ptr<T>进行对底层对象的访问.
b.weak_ptr<T>虽然拥有与基于的shared_ptr<T>对象
一样的底层对象,引用计数对象,但基于shared_ptr<T>得到weak_ptr<T>对象过程中,不会导致引用计数对象+1.
在一些你需要记录shared_ptr<T>拥有的底层对象,以便后续访问.但同时,不想因此而导致底层对象因为你的记录,而无法销毁的场合,weak_ptr<T>非常适合.weak_ptr<T>后续使用时,可检查其包含的底层对象是否还有效,有效,则访问.无效,则不能访问.记录底层对象但不干涉对象的生命期管理是weak_ptr<T>存在的意义.
(3). 使用智能指针:unique_ptr<T>unique_ptr<T>存在的意义在于,unique_ptr<T>所关联的底层对象只能被一个unique_ptr<T>对象所拥有.不存在,shared_ptr<T>的多个shared_ptr<T>对象共享同一底层对象,及引用计数对象因此大于1的情况.
2.基于std::function和std::bind的回调std::bind接收回调函数,允许预先设置回调参数.返回std::function.std::function的返回值和std::bind绑定的函数的返回值类型一致.std::function的参数列表由std::bind绑定的函数的参数列表中,未预先设置的参数构成.
std::function对象可向普通的类对象一样,执行拷贝构造/拷贝赋值.若std::function通过std::bind得到,且std::bind绑定了实参,实参会随着std::function的拷贝而被拷贝.
3.多线程编程
(1). 线程特定数据
a. 传统方式pthread_once让一个回调仅在首次执行到pthread_once时被调用一次.在回调中通过pthread_key_create得到一个供所有线程使用的key,每个线程通过此key结合pthread_getspecific/pthread_setspecific,来获取或设置线程特定数据.
b. 新方式__thread修饰全局变量.
被修饰的变量,每个线程有自己独立的一份.每个线程通过同一变量名访问全局变量,实际访问的是线程各自的版本,多个线程间版本互不干扰.
__thread对修饰对象要求:
b.1. 用于修饰POD类型,不能修饰class类型
b.2. 可用于修饰全局变量,函数内静态变量,不能修饰函数的局部变量或class的普通成员变量
b.2.__thread变量的初始化只能用编译期常量
(2). 获得线程id
#include <sys/syscall.h>#define gettid() syscall(__NR_gettid)或
pid_tgettid(){returnstatic_cast<pid_t>(::syscall(SYS_gettid));}主线程id和进程id一致.多个线程id彼此互不相同.
(3). 线程间互斥
通过线程间共享的互斥锁对象,每个线程对共享数据访问时,对共享的互斥锁对象上锁,访问结束,释放锁.可保证多线程间对共享数据的有序访问.
(4). 线程间同步
互斥锁+条件变量,通过多个线程共享的一个互斥锁+条件变量对象.每个线程对共享数据访问按以下模式.
// 等待者加锁while(操作条件检测不满足){释放锁+阻塞等待在条件变量}操作 释放锁// 唤醒者加锁 操作 借助条件变量,执行唤醒 释放锁a. 阻塞在条件变量等待的线程,唤醒继续时,先获取互斥锁,再继续.
唤醒继续时,无法立即加锁,需阻塞等待,获取成功后,再继续.
b. 执行唤醒时,若此时无人等待.则啥也不做.后续产生的等待,需依赖后续的唤醒操作来唤醒.
4.Linux下NonBlock I/O+I/O Multiplex
新一代的I/O Multiplex机制:使用epoll替代select/poll
a. 相对于select和poll来说,epoll更加灵活,没有描述符限制。
b.epoll的wait直接返回发生的事件集合,避免select/poll下对值-结果参数的低效轮询.
c.epoll在注册描述符时,允许绑定自定义数据.
在返回事件时,自定义数据也返回.给予事件处理提供语境支持,利用语境,可增强事件处理功能/灵活性.
intepoll_create(intsize);intepoll_ctl(intepfd,intop,intfd,structepoll_event*event);intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);对epoll_ctl:
第一个参数是epoll_create()的返回值,第二个参数表示动作.用三个宏来表示:EPOLL_CTL_ADD:注册新的fd到epfd中;EPOLL_CTL_MOD:修改已经注册的fd的监听事件;EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,
structepoll_event{__uint32_t events;/* Epoll events */epoll_data_tdata;/* User data variable */};events可以是以下几个宏的集合:EPOLLIN:表示对应的文件描述符可以读;EPOLLOUT:表示对应的文件描述符可以写;EPOLLPRI:表示对应的文件描述符有紧急的数据可读EPOLLERR:表示对应的文件描述符发生错误;EPOLLHUP:表示对应的文件描述符被挂断;快速获取对端关闭通知.EPOLLET:
将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的。
水平触发条件满足,每次执行epoll_wait,对监控了可读的套接字,只要其接收缓存区存在数据或其他引起可读通知事件到达,就产生可读事件.对监控了可写的套接字,只要其发送缓存区存在多余空间可供继续写入或其他引起可写通知事件达到,就产生可写事件.
边沿触发下,每次执行epoll_wait,对监控了可读的套接字,只在其接收缓存区从空变为非空后的首个epoll_wait中会产生一次可读事件.对监控了可写的套接字,只在其发送缓存区从满变为非满后的首个epoll_wait中会产生一次可写事件.
这样,事件通知次数少了.但每次可读事件通知时,要求我们必须持续读,直到返回EAGIN(接收缓存区读取完毕).或遭遇错误.才能结束本次事件处理.否则,后续epoll_wait中我们永远无法再次收到新的可读事件通知了.
同样的,每次可写事件通知时,要求我们必须持续执行异步发送写入.直到返回EAGIN(表示发送缓存区满了).或遭遇错误.或连接对象的发送缓存区中数据已被全部写入后才可结束.此时,还会要求每次通过连接对象执行发送时,写直接通过send写.在写入完毕,操作错误,或返回EAGIN时,才可停止.返回EAGIN后,剩余未立即发送数据,将在后续可写事件中通过异步写入方式完成写入处理.
EPOLLONESHOT:
只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里.否则,就是注册一次描述符持续有效.后续可以对已经注册的描述符执行修改/删除,但无法执行添加.
对epoll_wait,参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,参数timeout是超时时间.(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
5.Muduo对epoll的恰到好处的使用
a.epoll中注册的描述符都是非阻塞的.
这样有效避免,虚假唤醒下,事件处理时,阻塞在一处.而导致后续事件处理/事件监听因此而迟迟无法响应问题.通过搭配非阻塞描述符,可以始终保证每个事件的处理,在一个极短时间内完成.从而保持epoll高响应时效性.
b. 多线程环境下,对epoll的使用全部集中在一个线程.有效避免了,多线程同时使用epoll各种可能的混乱.
这需要利用:
b.1.std::bind,std::function回调函数及回调参数保存机制.
b.2. 在C++成员函数调C的API,调用时,this指向对象构成调用时语境.
为了实现高实时性的响应,利用了
a.epoll处理的均是非阻塞描述符
b.epoll监听的描述符中有一个专门用于及时唤醒
会在epoll所在this对象待处理回调集合非空时,被设置为可读.从而导致,若epoll本来处于wait时,立即由于事件存在而返回.
Muduo如何实现这点:
a. 一个事件循环对象a启动事件循环便独占一个线程t.
循环执行:epoll事件等待
事件处理
回调函数集合处理
b. 多个线程可获得事件循环a的指针
获得a指针的线程可以修改a的回调函数集合,这样在a的事件循环执行回调函数集合时,通过在线程t执行回调,而从而去与epoll交互
c. 在事件处理中,由于epoll事件允许绑定一个用户自定义数据
利用自定义数据也可实现,事件处理时,与epoll的交互.
上述实现多线程下,对指定事件循环a的epoll始终是a所在的线程,在与其交互.
在C++成员函数中执行C的API调用的优势:
成员函数总是可以获得this,this指向一个对象,对象本身可存储成员变量
对象及其成员变量构成了我们调C的API时候的一个语境.这样在API返回,或调用前传参的时候,我们可以基于调用发生时候的语境(this对象内容).来获取我们需要的关联信息,作对应处理.
6.eventfdeventfd包含一个由内核维护的64位无符号整型计数器,创建eventfd时会返回一个文件描述符,进程可对这个文件描述符进行read/write来读取/改变计数器的值,从而实现进程间通信。
inteventfd(unsignedint,int);参数1:创建eventfd时它所对应的64位计数器的初始值;
参数2:eventfd文件描述符的标志EFD_CLOEXEC/EFD_NONBLOCK/EFD_SEMAPHORE。EFD_CLOEXEC,表示返回的eventfd文件描述符在fork后exec其他程序时会自动关闭EFD_NONBLOCK,设置返回的eventfd非阻塞;EFD_SEMAPHORE,表示将eventfd作为一个信号量来使用。
a. 读eventfdread函数会从eventfd对应的64位计数器中,读取一个8字节的整型变量;read函数设置的接收buf的大小不能低于8个字节,否则read函数会出错,errno为EINVAL;read函数返回的值是按小端字节序的;
如果eventfd设置了EFD_SEMAPHORE,那么每次read就会返回1,并且让eventfd对应的计数器减一;
如果eventfd没有设置EFD_SEMAPHORE,那么每次read就会直接返回计数器中的数值,read之后计数器就会置0。
不管是哪一种,当计数器为0时,如果继续read,那么read就会阻塞(如果eventfd没有设置EFD_NONBLOCK)或者返回EAGAIN错误(如果eventfd设置了EFD_NONBLOCK)。
b. 写eventfd
在没有设置EFD_SEMAPHORE的情况下,write函数会将发送buf中的数据写入到eventfd对应的计数器中,最大只能写入0xffffffffffffffff,否则返回EINVAL错误;
在设置了EFD_SEMAPHORE的情况下,write函数相当于是向计数器中进行“添加”,比如说计数器中的值原本是2,如果write了一个3,那么计数器中的值就变成了5。如果某一次write后,计数器中的值超过了0xfffffffffffffffe.那么write就会阻塞,直到另一个进程/线程从eventfd中进行了read(如果write没有设置EFD_NONBLOCK),或者返回EAGAIN错误(如果write设置了EFD_NONBLOCK)。
7.万能对象boost::any使用举例
#include<iostream>#include<list>#include<boost/any.hpp>typedefstd::list<boost::any>list_any;voidfill_list(list_any&la){la.push_back(10);la.push_back(std::string("glemontree"));}voidshow_list(list_any&la){list_any::iterator it;boost::any anyone;for(it=la.begin;it!=la.end();++it){anyone=*it;// C++中,typeid用于返回指针或引用所指对象的实际类型,typeid是操作符,不是函数if(anyone.type()==typeid(int)){std::cout<<boost::any_cast<int>(*it)<<std::endl;}elseif(anyone.type()==typeid(std::string)){std::cout<<boost::any_cast<std::string>(*it).c_str()<<std::endl;}}}shared_ptr<void>,一个shared_ptr<void>可以基于任意T下的shared_ptr<T>对象,执行拷贝构造,拷贝赋值.
拷贝构造/拷贝赋值发生时,依然可以,正确处理引用计数更新,指向同一底层对象shared_ptr<void>析构时,可以正确处理引用计数更新,及引用计数为0时,对底层对象的析构处理.
8.用描述符事件模型处理定时器timerfd是Linux为用户程序提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,因此可以配合select/poll/epoll等使用。
inttimerfd_create(intclockid,intflags);clockid:CLOCK_REALTIME,系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,中间时刻如果系统时间被用户改成其他,则对应的时间相应改变.CLOCK_MONOTONIC,从系统启动这一刻起开始计时,不受系统时间被用户改变的影响flags:TFD_NONBLOCK(非阻塞模式),TFD_CLOEXEC(表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递)
structtimespec{time_ttv_sec;longtv_nsec;};structitimerspec{structtimespecit_interval;structtimespecit_value;};inttimerfd_settime(intfd,intflags,conststructitimerspec*new_value,structitimerspec*old_value);设置新的超时时间,并开始计时,能够启动和停止定时器;fd: 参数fd是timerfd_create函数返回的文件句柄flags:为1代表设置的是绝对时间(TFD_TIMER_ABSTIME表示绝对定时器);为0代表相对时间。new_value: 参数new_value指定定时器的超时时间以及超时间隔时间old_value: 如果old_value不为NULL,old_vlaue返回之前定时器设置的超时时间,it_interval不为0则表示是周期性定时器。it_value和it_interval都为0表示停止定时器.
inttimerfd_gettime(intfd,structitimerspec*curr_value);timerfd_gettime()函数获取距离下次超时剩余的时间,curr_value.it_value字段表示距离下次超时的时间,如果该值为0,表示计时器已经解除,该字段表示的值永远是一个相对值,无论TFD_TIMER_ABSTIME是否被设置.curr_value.it_interval定时器间隔时间.
uint64_texp=0;read(fd,&exp,sizeof(uint64_t));可以用read函数读取计时器的超时次数,该值是一个8字节无符号的长整型