Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

!> 很多题目还没有完善,之后会慢慢补充并完善……

进程概念

进程:正在执行程序的一个实例,是资源分配的基本单位。(进程控制块(process control block)描述进程的基本信息和运行状态,所谓的创建和撤销进程,都是指对PCB的操作)

线程概念

线程:进程中的单条流向,是程序独立调度的基本单位。(线程控制块(process control block)描述线程的基本信息和运行状态,所谓的创建和撤销线程,都是指对TCB的操作)

协程概念

协程可以理解为用户态的线程,其实就是可以被暂停以及可以被恢复运行的函数。虽然线程也可以自己暂停和恢复,只不过线程的调度是操作系统实现的,这些对程序员都不可见,而协程是在用户态实现的,对程序员可见。这就是为什么有的人说可以把协程理解为用户态线程的原因。
参考

进程是由什么组成的?有哪些数据?

  1. 进程是由进程控制块、程序段、数据段三部分组成; 进程具有创建其他进程的功能,而程序没有。

  2. 主要有如下几部分数据:Ah732T

❎线程是由什么组成的?有哪些数据?

❎协程是由什么组成的?有哪些数据?

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

❎进程上下文切换细节

进程上下文切换需要切换页表等重量级资源,线程上下文切换只需要切换寄存器等轻量级数据

  • 保存处理机上下文,包括程序计数器和其他寄存器。

  • 更新PCB信息。

  • 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。

  • 选择另一个进程执行,并更新其PCB。

  • 更新内存管理的数据结构。

  • 恢复处理机上下文。

  • ❎线程上下文切换细节

    协程上下文切换细节

    协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

    进程,线程,协程各自的联系与区别

    进程与线程区别:

    1. (拥有资源)一个进程可以有多个线程,由于线程不拥有资源,这几个线程共享进程内的资源。
    2. (资源开销)创建和撤销线程比进程开销小很多,因为创建线程仅仅需要堆栈空间以及程序计数器就可以了而创建进程需要分配地址空间,数据资源等,开销比较大。
    3. (调度)由于线程是独立调度的基本单位,同一进程中线程的切换不会引起进程的切换,但是两个不同进程中的线程切换会引起进程切换。
    4. (通信)线程可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助IPC。
    5. 进程是资源分配的基本单位,线程是独立运行和独立调度的基本单位(CPU上真正运行的是线程)
    6. 多线程程序中只要有一个线程死掉了,整个进程也死掉了,而一个进程死掉了,并不会对另一个进程造成影响,因为进程有自己独立的地址空间

    线程与协程区别
    以goroutine为例

    1. 线程就是内核级线程,协程就是用户级线程,着重点在于编程语言自己设计的调度器的效率高低,如果调度器做的好,并发会比线程好很多。
    2. 创建:协程是一种用户态的轻量级线程,创建开销比线程低。原因:内存栈占用少(原来一个进程可以占用几G,线程占用几M,将goroutine内存大小改为了几KB,从而我们可以大量的使用goroutine)
    3. 切换:协程之间切换成本没有线程切换成本高:就是这个goroutine执行完切换到下个goroutine的执行栈上去执行这个成本超级低,
    4. 调度:GMP是两级线程模型:用户有绑定内核线程,内核主要负责cpu调度,用户主要对接内核

    进程生命周期

    This is a picture without description

    1. 创建状态:系统已为其分配了PCB,但进程所需资源尚未分配,进程还未进入主存,即创建工作尚未完成,进程还不能被调度运行。
    2. 就绪状态:进程已分配到除CPU以外打的所有必要资源,等待获得CPU。
    3. 执行状态:进程已获得CPU,程序正在执行。
    4. 阻塞状态:正在执行的进程由于某事件而暂时无法继续执行时,放弃处理机而自行阻塞。
    5. 终止状态:进程到达自然结束点或者因意外被终结,将进入终止状态,进入终止状态的进程不能再执行,但在操作系统中仍然保留着一个记录,其中保存状态码和一些计时统计数据,供其它进程收集。
      参考:e3u9kh

    线程生命周期

    生命周期原理图
    线程生命周期原理图

    1. 新生状态:在程序中用构造方法(new操作符)创建一个新线程时,如new Thread(r),该线程就是创建状态,此时它已经有了相应的内存空间和其它资源,但是还没有开始执行。
    2. 就绪状态:新建线程对象后,调用该线程的start()方法就可以启动线程。当线程启动时,线程进入就绪状态(runnable)。由于还没有分配CPU,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。当系统挑选一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态。系统挑选的动作称之为“CPU调度”。一旦获得CPU线程就进入运行状态并自动调用自己的run方法。
    3. 运行状态:当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的run()方法。run()方法定义了该线程的操作和功能。运行状态中的线程执行自己的run方法中代码。直到调用其他方法或者发生阻塞而终止。
    4. 阻塞状态:一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入输出操作时,suspend()、 wait()等方法,线程都将进入堵塞状态。堵塞时,线程不能进入排队队列,只有当引起堵塞的原因被消除后,线程转入就绪状态。重将让出 CPU 并暂时中止自己的执行,进入堵塞状态。在可执行状态下,如果调用 sleep()、 新到就绪队列中排队等待,这时被CPU调度选中后会从原来停止的位置开始继续执行。
    5. 死亡状态:线程调用stop()方法、destory()方法或 run()方法执行结束后,线程即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。

    进程之间如何通信

    1. 管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
    2. 有名管道(Names Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
    3. 信号(Signal) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
    4. 消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。
    5. 信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
    6. 共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
    7. 套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

    进程之间的同步方法:

    1、临界区(Critical Section):通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
    优点:保证在某一时刻只有一个线程能访问数据的简便办法

    缺点:虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。

    2、互斥量(Mutex):为协调共同对一个共享资源的单独访问而设计的。

    互斥量跟临界区很相似,比临界区复杂,互斥对象只有一个,只有拥有互斥对象的线程才具有访问资源的权限。

    优点:使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。

    缺点:①互斥量是可以命名的,也就是说它可以跨越进程使用,所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。

    ②通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号量对象可以说是一种资源计数器。

    3、信号量(Semaphore):为控制一个具有有限数量用户资源而设计。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。互斥量是信号量的一种特殊情况,当信号量的最大资源数=1就是互斥量了。

    优点:适用于对Socket(套接字)程序中线程的同步。(例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。)

    缺点:①信号量机制必须有公共内存,不能用于分布式操作系统,这是它最大的弱点;

    ②信号量机制功能强大,但使用时对信号量的操作分散, 而且难以控制,读写和维护都很困难,加重了程序员的编码负担;

    ③核心操作P-V分散在各用户程序的代码中,不易控制和管理,一旦错误,后果严重,且不易发现和纠正。

    4、事件(Event): 用来通知线程有一些事件已发生,从而启动后继任务的开始。

    优点:事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作

    线程之间的同步方式

    🙋 我 :线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。操作系统一般有下面三种线程同步的方式:

    互斥量(Mutex):采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
    信号量(Semphares) :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
    事件(Event) :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操

    协程之间的同步方式

    以goroutine为例

    1. time.Sleep
    2. channel
    3. sync.WaitGroup

    参考

    线程之间如何通信

    1. 使用全局变量:主要由于多个线程可能更改全局变量,因此全局变量最好声明为volatile
    2. 使用消息实现通信:在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。
    3. 使用事件CEvent类实现线程间通信:Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。

    协程之间如何通信

  • 共享内存
  • channel
  • 进程调度算法

  • 先到先服务(FCFS)调度算法 : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  • 短作业优先(SJF)的调度算法 : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  • 时间片轮转调度算法 : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
  • 多级反馈队列调度算法 :前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
  • 优先级调度 : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
  • ❎【没问到】进程之间共享什么,不共享什么

    线程之间共享什么,不共享什么

    共享的资源有:

    1. 堆。 由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的)
    2. 全局变量 。它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的
    3. 静态变量。 虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的
    4. 文件等公用资源。 这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。

    独享的资源有:

    1. 栈 。栈是独享的
    2. 寄存器 。 这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC

    ❎【没问到】协程之间共享什么,不共享什么

    ❎孤儿进程,危害以及如何解决孤儿进程的出现

    ❎僵尸进程,危害以及解决办法

    ❎上层协程结束了,如果通知到子协程也结束

    ❎用户态和内核态

    1. 用户态(user mode):用户态运行的进程可以直接读取用户程序的数据。
    2. 系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
    3. 为什么会有用户态,因为进程在执行过程中需要完成一些操作系统提供的功能,比如读写文件,涉及到了与硬盘打交道,这个过程是由操作系统来完成的,进程只需要给操作系统发出请求,操作系统代表进程在内核中执行,这个时候就处于核心态。这也是核心态体现的地方

    ❎内核态线程和用户态线程的区别如何切换

    ❎fork之后的父子进程虚拟内存空间的相同与不同

    什么是线程安全

    参考

    多线程线程的弊端

    1. 如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换.
    2. 更多的线程需要更多的内存空间
    3. 线程中止需要考虑对程序运行的影响.
    4. 通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生

    一个进程是如何被挂起的

    1. 主动挂起:通过sleep让进程间歇性挂起。sleep的原理之前有分析过,就不再分析。大概的原理:就是设置一个定时器,到期后唤醒进程。而后修改进程为挂起状态,等待唤醒。
    2. 被动挂起:场景比较多,主要是进程申请一个资源,但是资源没有满足条件。
      参考

    ❎一个线程是如何被挂起的

    ❎【没问到】一个协程是如何被挂起的

    ❎进程池 线程池 协程池

    多线程中会对全局变量进行pad操作,请问是为啥?

    面试官解释说是padding操作,也就是struct中的内存对其那种

    1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
    2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

    参考

    参考

    1. JavaGuide

    今日份面试题:

    1. 自我介绍的时候说本科学过java,之后被问现在为什么学Go,说了go比java并发强很多,之后引申出了如下问题
    2. 讲一下进程,线程,协程区别和联系
    3. 说一下进程,线程,协程的组成
    4. fork之后的父子进程虚拟内存空间的相同与不同:fork 复制父进程的页表+写时复制
    5. 僵尸进程与孤儿进程危害,如何解决?
    6. 讲一下线程上下文切换
    7. 讲一下进程的状态和上下文切换
    8. 一个进程是如何被挂起的,线程呢?
    9. 上层协程结束了,如果通知到子协程也结束
    10. 内核态线程和用户态线程的区别如何切换
    11. 线程有共享数据么,共享什么,不共享什么?进程之间呢?协程之间呢?
    12. 计网:https过程讲一下,为啥非对称加密是安全的(自己说了不可逆)
    13. 证书链,根域名?(问不下去了)
    14. 数据库:慢查询优化思路,一般是什么原因?
    15. 算法:有的任务有优先级,有的任务有依赖关系,如何设计,用什么数据结构,之后又问如何设计具体的算法呢?

    评论