小知识总结

  1. C语言中的struct:只有成员变量
    C++中的struct:成员变量/函数默认为public,继承关系默认为public
    C++中的class
  2. 调用不带参数的构造函数时不需要加小括号
  3. #define public private //正确,把public宏定义为private
  4. 初始化列表的初始化顺序与变量的声明顺序一致,而不是按照出现在初始化列表中的顺序
  5. 必须使用初始化列表:当类中含有const,reference成员变量和基类的构造函数时都必须使用初始化列表
  6. 构造函数内调用构造函数只是在栈上生成了一个临时对象,对于本身没有任何影响。而且构造函数的互相调用可能导致栈溢出
  7. 被explicit修饰的单参数构造函数或者其余参数都有默认参数的构造函数,只能被显示调用,无法隐式调用。
  8. delete和delete[],内置类型的数组用delete和delete[]没有任何区别,而自定义类型的数组需要使用delete[]来调用每个成员的析构函数,如果自定义数据中使用到了系统资源,比如socket、File,thread等则更加明显。
  9. muduo单例:
template<typename T>
class Singleton{
public:
	static T& instance()
	{
		pthread_once(&ponce_, &Singleton::init);
		assert(value_ != NULL);
		return value_;
	}

private:
	static void init()
	{
		value_ = new T();
		::atexit(destory);
	}
	static void destroy()
	{
		delete value_;
		value_ = NULL;
	}
private:
	static pthread_once_t ponce_;
	static T*             value_;
};

template<typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;

template<typename T>
T* Singleton<T>:: value_ = NULL;
  1. 重载、重定义、重写:
  • 重载:在同一定义域中,函数名相同,形参不同,返回值不影响
  • 重定义:也叫隐藏。在继承体系中,子类有非虚函数和父类函数名相同,形参和返回值不影响
  • 重写:也叫覆盖。子类在继承父类的时候,对父类的虚函数进行了覆盖,函数要被virtual修饰,函数名必须相同,参数列表必须相同,返回值可以不相同,但是必须是父子关系的指针或引用。
  1. 虚基表和虚表(虚函数表)毫无关系,排列关系:
    在这里插入图片描述
    参考博客:虚基表和虚表
  2. 守卫锁
class Mutexlock{
public:
	Mutexlock()
	  :holder(0)
	{ pthread_mutex_init(&_mutex,NULL);}
	~Mutexlock()
	{
		holder = 0;
		pthread_mutex_destroy(&_mutex)
	}
	lock()
	{
		pthread_mutex_lock(&_mutex);
		holder = gettid();
	}
	unlock()
	{
		holder = 0;
		pthread_mutex_unlock(_mutex);
	}
	bool isLockedByThisThread(){return holder == gettid();}
private:
	pid_t gettid(){	return static_cast<pid_t>(::syscall(SYS_gettid)); }
	pthread_mutex_t _mutex;
	pid_t holder;
};

class MutexlockGuard{
public:
	MutexlockGuard(MutexlockGuard& mutex)
	  :_mutex(mutex)
	{_mutex.lock();}
	~MutexlockGuard(){_mutex.unlock();}
private:
	Mutexlock& _mutex;
};

#define MutexlockGuard(x) error "false, missing mutex guard var name"
//防止出现MutexlockGuard(mutex);形式的调用,遗漏变量名,产生一个临时变量又马上销毁了,导致锁不住临界区
//正确的调用:MutexlockGuard lock(mutex)
//临界区
  1. 二叉树,AVL树,B树(B-树),B+树
    二叉树有单链表的情形。AVL树保证任意节点的左右子树高度差不超过1。B树相较于AVL树来说它是多叉树,减少了树的高度,也就减少了I/O次数,B树还保证所有的叶子节点都位于同一层。B+树就是在B树的基础上,将所有的叶子节点连接起来,从小到大排序,方便范围查找。B+树有两种节点:索引节点和叶子节点。索引节点只用于索引,所有数据都保存在叶子节点中
  2. 红黑树
    性质:
  • 根结点是黑色的
  • 每个结点不是黑色就是红色
  • 没有连续的红结点
  • 每个叶子结点都是黑色(此处的叶子节点是空)
  • 每个结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点

muduo是一个基于非阻塞I/O和事件驱动的现代C++网络库,原生支持one loop per thread这种I/O模型,适合于面向业务的多线程服务端网络应用程序。muduo的线程模型符合one loop per thread + thread pool模型,每个线程最多有一个EventLoop,每个TcpConnection必须归某个EventLoop管理,所有的I/O操作会转移到这个线程,也就是说一个文件描述符只能由一个线程去读写。
TcpServer支持多线程,它有两种模式:

  • 单线程:accept和TcpConnection用同一个线程做I/O。
  • 多线程:accept与EventLoop在同一个线程(main Reactor),另外创建一个EventLoopThreadPool,新到的连接会按照round-robin方式分配到线程池中的某个线程(subReactor)。多个连接可以被分派到多个线程中,以充分利用CPU。
  • muduo采用的是固定大小的Reactor pool,池子的大小通常根据CPU数目来确定,也就是说线程数目是固定的,这样程序的总体处理能力是不会随着连接数增加而下降。另外,由于一个连接完全由一个线程管理,那么请求的顺序性有保证,突发请求也不会占满8个核,如果需要优化突发请求,可以再加一个线程池来处理计算。
  1. fork()/vfork()/popen()/system()
  2. 堆,实际上是一颗完全二叉树,任意父节点的序号为n,子节点的序号是2n+1,2n+2。p(最后一个父节点的序号) = (sz-2)/2。因此我们可以用一个数组来标识一个堆
  3. malloc和内存碎片
    https://blog.csdn.net/Always__/article/details/50990838
    https://blog.csdn.net/yusiguyuan/article/details/39496305
    malloc使用brk和mmap系统调用来申请空间,首先会去缓冲池内获取,如果没有合适的chunk会调用系统调用,当申请的空间大小小于128k的时候,使用brk函数将堆尾的指针(我认为是program break,而不是data段的end_data指针)向上移动,一般是128Kb,当申请的大小大于128K(0x20ff8,131Kb)的时候,调用mmap去文件映射区。释放内存的时候,如果是mmap分配的就直接释放,如果是brk分配的,会先还给malloc的缓存池,和其他的空闲chunk合并,它可能并不会立即释放对应的虚存和物理内存,因为break指针只有一个,如果在这一块刚刚释放的内存上面还有没有释放的内存时就不会被释放,这也是内存碎片产生的原因。缓存池内部维护了一个双链表,又实现了一个数据结构堆(小堆),每次来取内存的时候从堆顶依次往下找,这就是malloc的适配算法,它实际上有两种算法,First fit和Best fit,这种就是Best Fit
  4. 使用初始化列表更快的原因:无论是否在列表中显式初始化成员变量对象,都会在此初始化一次默认构造函数,所以如果不在此处初始化,而是跑到类的构造函数里去初始化的话,等于平白空耗在列表的这次初始化
  5. 编译时多态(静态多态,模板,重载),运行时多态(动态多态,虚函数)
  6. 右值引用是C++11中新增加的一个很重要的特性,他主是要用来解决C++98/03中遇到的两个问题,第一个问题就是临时对象非必要的昂贵的拷贝操作,第二个问题是在模板函数中如何按照参数的实际类型进行转发。https://www.cnblogs.com/qicosmos/p/4283455.html
  7. sizeof后面接变量,一定要有括号,类型可以没有括号
  8. 初始化列表会快一些。C++规定,所有成员变量的初始化发生在进入构造函数之前,此时会调用成员变量默认的构造函数,再进入构造函数中按照用户的实现来调用成员变量的构造函数。
    无论是否在列表中显式初始化成员变量对象,都会在此初始化一次默认构造函数,所以如果不在此处初始化,而是跑到类的构造函数里去初始化的话,等于平白空耗在列表的这次初始化,然后在类的构造函数中再去初始化
  9. 野指针和悬空指针
    野指针:就是没有被初始化过的指针或者指向受限访问的指针(不给用户访问)
    如何防止野指针:初始化指针nullptr
    悬空指针:指针最初指向的内存已经被释放了的指针,虽然指针指向的对象已经释放,但是指针本身还是没有释放,
    如何防止悬空指针:智能指针或者在释放指针前判断指针是否为空,不为空再释放。
  10. 形参和实参
    形参(形式参数)
    在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。
    实参(实际参数)
    函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。
  11. 函数模板和类模板的区别
  • 特化:函数模板只能全特化
  • 实例化方式不同:函数模板实例化由编译程序在处理函数调用时自动完成,类模板实例化需要在程序中显式指定
  • 结果:函数模板会生成一个类,类模板会生成一个类
  • 默认参数:函数模板没有默认参数,类模板可以有
  1. 线程数目的确定
    CPU密集型:核心线程数 = CPU核数 + 1
    IO密集型:核心线程数 = CPU核数 * 2

CPU密集型(CPU-bound)
CPU密集型也叫计算密集型,指的是系统花费相对大部分时间在做CPU运算、逻辑判断等,CPU使用率很高,典型的如加密运算。一般来说:大量纯计算就是 CPU 密集型。

IO密集型(I/O bound)
IO密集型指的是系统花费大部分时间在等待相对较慢的I/O操作完成,如硬盘文件的读写。一般来说:大量网络,文件操作就是 io 密集型。

  1. 多线程信号处理
    多线程中的线程有自己的信号屏蔽字,可以通过如下函数设置:
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

多线程程序中的每个线程都可能收到信号,无法确定是哪一个,多线程的两种信号处理方式:

  • 使用sigwait实现信号的同步处理
  • 继续使用进程的信号处理程序处理,signal和sigaction的处理函数是线程共享的,在任一线程修改,都会影响整个进程,只是被打断的线程是不确定的。
    sigwait不改变当前线程的信号屏蔽字,只是从pending表中取出信号去处理,因而不会触发进程的signal handler。

该信号想要触发signal handler,一定要能够被递送到进程,或者说一定要存在一个线程允许递送它,也就是不能完全阻塞。
想要被sigwait接收的信号,一定是pending信号,那么一定要先阻塞对应的信号。
为了防止工作线程系统调用被打断,创建一个线程处理信号,保证signal handler还是sigwait都在该特定线程运行。

  1. 默认情况下,信号将由主进程接收处理,就算信号处理函数是由子线程注册的
  2. 每个线程均有自己的信号屏蔽字,可以使用sigprocmask函数来屏蔽某个线程对该信号的响应处理,仅留下需要处理该信号的线程来处理指定的信号。
  3. 对某个信号处理函数,以程序执行时最后一次注册的处理函数为准,即在所有的线程里,同一个信号在任何线程里对该信号的处理一定相同
  4. 可以使用pthread_kill对指定的线程发送信号
  5. 在主进程中对sigmask进行设置后,主进程创建出来的线程将继承主进程的掩码
  6. pthread_sigmask 跟 sigprocmask 类似;
    sigprocmask 只能用于单进程单线程; fork的子进程拥有一份屏蔽信号拷贝;
    pthread_sigmask 用于多线程 ; 新线程拥有一份pthread_create那个线程的屏蔽信号拷贝;

sigwait的使用:在主进程里把需要处理的信号屏蔽掉,在创建一个线程,里面执行一个while或者for死循环,一直调用sigwait函数,这就多线程的信号处理

  1. 线程退出

exit():整个进程退出
在main中return,整个进程退出
线程退出:

线程里面return,返回的指针不能指向栈上
pthread_exit(),返回的指针不能指向栈上
一个线程取消另外一个线程:int pthread_cancel(pthread_t thread),成功返回0,失败返回错误码 。被取消的线程的退出码是-1。

  1. 线程分离
    别人分离:pthread_detach(pthread_t tid);
    自己分离:pthread_detach(pthread_self());
    线程分离和joinable是冲突的,如果线程在detach之后再调用pthread_join(tid),返回22,而成功时返回0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值