1. 内存管理
C++
- C++的内存分配和释放由malloc管理,空闲内存块被保存在链表中,链表的节点信息和空闲块的信息保存在空闲块的开头;
- 被释放的内存不会马上交还给操作系统,而是按大小加入不同的链表中以便再次分配,fast_bin, small_bin, large_bin;
- 当链表中和top chunk都找不到或切割出合适的空闲块时,调用brk()(主分配区),或者mmap分配新的一片内存;
- 每个线程分配内存时,都会锁定一个分配区,以防止不同线程同时分配的冲突;
Go
- 程序启动时会向操作系统直接申请一大块内存, 并用一个全局的mheap结构体管理;
- 申请一大块的好处是,将空闲块的信息和空闲块本身的地址按一定的偏移关系对应起来,这样可以随时得到某个地址所属的内存块的信息;
- go将内存划分一个个span,每个span中又划分为等大小的object,一共有67类span来分配不同大小的对象;不同类别的span放入不同的mcentral中;
- 为了防止每次goroutine申请内存都要从全局中拿,每个P还维护了一个mcache来保存本地的mspan,当本地没有可用的mspan时再向mcentral申请;
可以看出C++和go在内存分配上的相同点都是每个线程都有自己的分配区,并且将空闲内存按大小分别管理;但go划分链表的方式更为严格,一类span只能分配一种大小的object;这样的好处是不像C++每次malloc要遍历链表去找合适大小的块,但坏处就是必须一开始为各类span分配一些内存;
2. 多线程
C++
- 无论是pthread还是标准库,创建的都是系统线程,系统线程首先是栈比较大,再就是调度完全由操作系统进行;
Go
- 基于GMP模型,创建goroutine的不需要调用系统调用,且需要的资源少,每个P还有g的缓存以便快速创建;G的调度也不用经过操作系统,G切换代价小(只要三个寄存器);
3. 多态
C++
- 基于虚函数和虚表,以及继承,编译时编译器会检查派生类如果有重写虚函数则会在类的虚表中覆盖父类函数的指针;
Go
- 基于interface,实现了接口方法的类型都可以被当成该接口;空接口的实现其实就是一个unsafe.pointer加引用到type的指针,反正空接口也不能调用方法;带方法的接口的实现中包含了指向数据的unsafe.pointer以及一组方法,可见Go的每个interface实例都保存了自己的方法集(虚表),而不像C++一个类保存一个。
- 从interface的初始化过程中可以看出初始化方法集的过程还是利用到了变量的反射找到变量的type信息,那为什么interface还要单独保存一份方法集呢?这样struct在不使用多态时就仅仅是struct不需要额外的方法集;而且利用interface实现多态意味着只有当执行函数的是interface时才从函数列表中找执行入口,而struct本身编译器可以自信地直接找到函数执行入口;
- 反观C++只要调用虚函数,因为编译器无法确定程序本身是否调用多态,都会觉得运行时才能确定调用的是哪个函数;
4. 反射
C++
- 主要通过typeid,dynamic_cast,对于多态类型(编译时无法确定的类型),typeid会从虚表中找到type_info的指针,而dynamic_cast原理也类似;
Go
- 通过interface中的type信息