实习面试记录2

C++11的新特性

C++ 中有四种智能指针:auto_pt、unique_ptr、shared_ptr、weak_ptr 其中后三个是 C++11 支持,第一个已经被 C++11 弃用且被 unique_prt 代替,不推荐使用。

各自的特点以及区别:

    1、std::unique_ptr 对其持有的堆内存具有唯一拥有权,也就是 std::unique_ptr 不可以拷贝或赋值给其他对象,其拥有的堆内存仅自己独占,std::unique_ptr 对象销毁时会释放其持有的堆内存。鉴于 std::auto_ptr 的前车之鉴,std::unique_ptr 禁止复制语义,为了达到这个效果,std::unique_ptr 类的拷贝构造函数和赋值运算符(operator =)被标记为 delete。

    2、std::shared_ptr 持有的资源可以在多个 std::shared_ptr 之间共享,每多一个 std::shared_ptr 对资源的引用,资源引用计数将增加 1,每一个指向该资源的 std::shared_ptr 对象析构时,资源引用计数减 1,最后一个 std::shared_ptr 对象析构时,发现资源计数为 0,将释放其持有的资源。多个线程之间,递增和减少资源的引用计数是安全的。(注意:这不意味着多个线程同时操作 std::shared_ptr 引用的对象是安全的)。std::shared_ptr 提供了一个 use_count() 方法来获取当前持有资源的引用计数。除了上面描述的,std::shared_ptr 用法和 std::unique_ptr 基本相同。

    3、std::weak_ptr 是一个不控制资源生命周期的智能指针,是对对象的一种弱引用,只是提供了对其管理的资源的一个访问手段,引入它的目的为协助 std::shared_ptr 工作。

          std::weak_ptr 可以从一个 std::shared_ptr 或另一个 std::weak_ptr 对象构造,std::shared_ptr 可以直接赋值给 std::weak_ptr ,也可以通过 std::weak_ptr 的 lock() 函数来获得 std::shared_ptr。它的构造和析构不会引起引用计数的增加或减少。std::weak_ptr 可用来解决 std::shared_ptr 相互引用时的死锁问题(即两个std::shared_ptr 相互引用,那么这两个指针的引用计数永远不可能下降为 0, 资源永远不会释放)。


智能指针的大小:一个 std::unique_ptr 对象大小与裸指针大小相同(即 sizeof(std::unique_ptr) == sizeof(void)),而 std::shared_ptr 的大小是 std::unique_ptr 的一倍。

在 32 位机器上,std_unique_ptr 占 4 字节,std::shared_ptr 和 std::weak_ptr 占 8 字节。

在 64 位机器上,std_unique_ptr 占 8 字节,std::shared_ptr 和 std::weak_ptr 占 16 字节。

也就是说,std_unique_ptr 的大小总是和原始指针大小一样,std::shared_ptr 和 std::weak_ptr 大小是原始指针的一倍。


std::shared_ptr指针的实现原理:

实现原理:shared_ptr是利用一个计数器,无论我们使用拷贝构造函数、赋值运算符重载、作为函数返回值、或作为参数传给一个参数时计数器+1,当shared_ptr被赋予一个新值或者需要销毁时,计数器-1,直到计数器为0时,调用析构函数,释放对象,并销毁其内存。shaerd_ptr不直接支持管理动态数组,如果希望使用shared_ptr管理一个动态数组,必须定制自己的删除器。

shared_ptr实现机制


多线程

C++多线程详细讲解

线程与进程之间的关系

        进程是资源拥有的单位。线程是调度的最小单位。又称为轻进程。他只拥有进程中一些资源,这次资源对于这个线程来说是必不可少的。而所有的线程共享进程的资源。因此不同的线程之间可以共享一些数据变量。而进程则不可以,因为不同进程有不同过的进程控制块,分别处于不同的物理地址空间里面。

        每个线程都有自己的工作内存,也就是栈,如果从更底层的来说,相当于硬件的高速缓存,进程的一些公共资源常存放在堆中也就是主内存。不同线程之间的内容切换,首先要把各自的数据放到主内存中,才可以互相访问切换。

        传统的操作系统中,没有引入线程,则并发是指进程。而对于进程的并发调度,也就是上下文切换,需要浪费极大地系统资源,例如CPU现场的保护,寄存器,内存地址的信息记录,调度算法的执行,以及进程状态的转换如从运行状态转换到阻塞状态,而且还要把把进程转到相应的队列里面。又因为有些进程只有一部分代码需要别的进程提供一些数据。如果因为这一点的东西需要数据而阻塞,然后要发生强大的进程切换,明显是浪费很多资源。

        为此,在后来的操作系统中引入了线程概念。线程就是把一个进程分成好多部分,每一个部分都是一个线程,这所有的线程,共享进程的资源。当有一个线程需要别的进程提供数据,发生阻塞的时候,只有这个线程发生阻塞,其他的线程或许不需要这些数据也可以运行,因此只需要阻塞这个线程。进行单个线程切换,而整个进程则不需要切换,因此效率更高。单个线程的调度只需要保存一些必要的信息,如寄存器值,栈值子指针内容。线程切换,只需要更改一些寄存器的指针,如PC,pd等寄存器,当然这些是通过硬件实现的,效率是很快的,然后指向当前线程的代码开始地址处,当然对于源代码来说,本质上会经过编译程序和链接程序最终编程目标代码存放在内存之中的某一空白的内存地址处,并同时记录该内存地址的首地址,这些都是硬件级别的调用,只要要硬件的一些寄存器之类的东西转换一下就可以。速度相当快。并不需要什么进程调度算法,这些很慢的调度。因此引入线程,后系统的并发度会更高。


线程的5种状态及其之间的切换


如上图所示:

我们可以清楚的看到线程的几种状态

    1.新建:使用NEW关键字来创建线程。

    2.可运行:当前线程调用start()方法,使线程处于Runnable 状态,等待获取CPU。

    3.运行中:如果线程抢到了CPU资源,这时的线程处于Running状态,Runnable和Running是可以相互切换的,比如,其他优先级较高线程抢占CPU资源,这时候线程就会变为Runnable状态。

       进入Runnable状态大体分为5种:

               线程调用sleep()方法经过的时间超过了指定的时间。

               线程正在等待某个通知,其他线程发出了通知。

               处于挂起的线程调用resume()方法。

               线程调用的阻塞IO已返回,阻塞方法执行完毕。

               线程成功的获取到了同步监视器。

       4.阻塞:出现Blocked的情况大概分为5种

               线程调用sleep()方法,主动放弃占用的CPU资源。

               线程调用wait()方法,等待某个通知。

               线程调用suspend()方法将线程挂起,容易导致死锁,尽量避免使用此方法。

               线程调用阻塞式IO方法,在方法返回前,线程被阻塞。

               线程试图获得一个同步监视器,但该同步监视器被其他线程所持有。

    5.死亡:run()方法运行结束后进入销毁阶段,整个线程执行完毕。


用户级线程和内核级线程

        内核级线程,顾名思义,它的调度是依赖于操作系统的,即操作系统控制着内核级线程的切换,比如有A和B两个内核级线程,我们用户是不知道先执行哪个线程的代码和不知道什么时候切换到另一个线程执行代码的,这件事只有操作系统知道,我们无法干预。

        用户级线程,顾名思义,它的调度是依赖于用户的想法的,比如有C和D两个用户级线程,我们用户可以先让A执行一段代码后,然后手动控制让其跳到B去执行一段代码,我们是清楚知道线程间的切换的。

        简单一句话来说就是:内核级线程是由操作系统进行调度的,用户级线程是由用户来控制调度的。

用户级多线程的切换原理


网络通信的学习

网络通信基础知识总结

网络通信原理和过程

你可能感兴趣的:(实习面试记录2)