1. 进程与线程的区别和联系
进程是系统进行资源分配和调度的一个独立单位。线程是进程的一个实体,是CPU调度的基本单位。线程自己基本上不拥有系统资源,但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。由于线程比进程更小,基本上不拥有系统资源,线程上下文切换比进程上下文切换要快得多,所以线程调度的开销就会小得多,从而可以显著提高系统资源的利用率和吞吐量。
(一) 定义
(1)进程 :是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程一般由程序,数据集合和进程控制块三部分组成。
(2)线程 :线程是操作系统调度的最小单位。一个进程可以包含多个线程,每个线程可以看做一个独立的逻辑流,线程也称为轻量级进程。在Linux下其实原本并没有线程的概念,线程和进程对于操作系统都是一样的调度,都有具有自己独立的task_struct(进程描述符),也都有自己独立的pid,但是线程可以共享同一内存地址空间、代码段、全局变量、同一打开文件集合等等。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

(二) 进程切换的开销
开销分成两种:
A.直接开销

  • 切换页表全局目录
  • 切换内核态堆栈
  • 切换硬件上下文:寄存器当中的数据
  • 刷新TLB
  • 执行操作系统调度器的代码

B.间接开销
间接开销指的是由于切换到一个新进程后,各种缓存对于新的进程而言未命中的概率非常大。进程如果跨CPU调度,那么之前的TLB、L1、L2、L3缓存因为运行的进程已经变了,缓存所带来的空间局部性和时间局部性的优势失效,当前缓存起来的代码、数据失效。这将导致新进程需要重新从内存当中获取数据和代码,并将其缓存起来。从而导致穿透到内存的IO会变多,由于CPU和内存读取速度的差异很大,这部分带来的开销也非常大。

(三) 线程切换的开销
线程切换和进程切换之间的主要区别在于:

  • 在线程切换期间,虚拟内存空间保持不变。
  • 进程切换期间,TLB会被刷新,从而使内存访问在一段时间内变得更加昂贵。

(四) 进程线程的本质区别

  1. 进程更安全,一个进程完全不会影响另外的进程。
  2. 进程间通信比线程间通信的性能差很多。
  3. 线程切换开销更低。

(五) 关系
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
(3)处理机分给线程,即真正在处理机上运行的是线程。
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
(六) 区别
(1)进程有自己的独立地址空间,线程没有。进程有自己的独立地址空间(32位进程地址空间0-4G),进程的地址空间之间相互独立,同一进程的各个线程共享进程的资源。线程只有一个内核对象和一个堆栈,所需内存小;
(2)进程是资源分配的最小单位,线程是CPU调度的最小单位
(3)进程和线程通信方式不同,进程通信需要进程同步和互斥的手段辅助,以保证数据的一致性,而线程可以直接读写进程数据段来进行通信
(4)进程的创建,撤销,切换系统开销大,线程开销小,只需保存很设置少量寄存器内容
(5)一个进程挂掉了不会影响其他进程,而线程挂掉了会影响其他线程
(6)引入线程的操作系统,多个进程间可以并发执行,多个线程间也可以并发执行。
(7)与进程的控制表PCB相似,线程也有自己的控制表TCB,但是TCB中所保存的线程状态比PCB表少得多。

  • 优缺点:
    线程开销小,但不利于资源的管理和保护;进程与之相反

2. 进程间的通信方式有哪些?线程间的通信方式呢?
(一) 进程
共享内存,消息队列传递,
无名管道(有血缘关系的进程间),有名管道(允许无亲缘关系关系进程间通信),
信号,信号量(主要作为进程间以及同一进程内不同线程之间的同步手段。),
套接字
几种方式的比较:
(1) 管道:速度慢、容量有限
(2) 消息队列:容量收到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。是用于两个进程之间的通讯,首先在一个进程中创建一个消息队列,然后再往消息队列中写数据,而另一个进程则从那个消息队列中取数据。需要注意的是,消息队列是用创建文件的方式建立的,如果一个进程向某个消息队列中写入了数据之后,另一个进程并没有取出数据,即使向消息队列中写数据的进程已经结束,保存在消息队列中的数据并没有消失,也就是说下次再从这个消息队列读数据的时候,就是上次的数据!
(3) 信号量:不能传递复杂信息,只能用来同步。
(4) 共享内存:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全。
(二) 线程
锁机制(互斥锁、条件变量、读写锁),信号量,信号
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

3. 进程间同步与互斥的区别,线程同步的方式?

  • 区别
    (1) 互斥 :指某一个资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的
    (2)同步 :是指在互斥的基础上(大多数情况下),通过其它机制实现访问者对资源的有序访问。大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
    (3)同步:体现的是一种协作性。互斥:体现的是排它性
    (4)同步机制遵循的原则 :空闲让进,忙则等待,有限等待,让权等待

  • 线程同步的方式:
    (临界区、互斥量、信号量、事件)
    线程同步指多个线程同时访问某资源时,采用一系列的机制以保证同时最多只能一个线程访问该资源。
    临界区:通过对多线程的串行化来访问公共资源或者一段代码,速度快,适合控制数据访问。
    互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以可以保证公共资源不会同时被多个线程访问。
    信号量:它允许多个线程同一时刻访问同一资源,但是需要限制同一时刻访问此资源的最大线程数目。信号量对象与其他前面几种方法不同,信号允许多个线程同时使用共享资源。
    事件(信号):通过通知操作的方式来保持多线程的同步,还可以方便实现多线程的优先级比较操作。

  • 线程同步不同方式间的总结比较:
    (1) 互斥量和临界区很相似,但是互斥量是可以命名的,它可以跨越进程使用,所以创建互斥量所需要的资源更多,如果只是为了在进程内部使用使用临界区会带来速度上的优势并能够减少资源占用量。
    (2) 互斥量、信号量、事件都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,所以可以使用WaitForSingleObject来等待进程和线程退出。
    (3) 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号量对象可以说是一种资源计数器。

4. C++多线程,互斥,同步
(1)互斥与同步

  • 当有多个线程的时候,经常需要去同步(注:同步不是同时刻)这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。
  • 所谓同步,是指在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。 如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
  • 所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。 如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

(2)多线程同步和互斥有几种实现方法

  • 线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
  • 用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
  • 内核模式下的方法有:事件,信号量,互斥量。
    (1)临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
    (2)互斥量:为协调共同对一个共享资源的单独访问而设计的。
    (3)信号量:为控制一个具有有限数量用户资源而设计。
    (4)事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

5. 进程间同步的几种方法
进程间同步:把异步环境下的一组并发进程因直接制约而互相发送消息而进行互相合作、互相等待,使得各进程按一定的速度执行的过程称为进程间的同步。
(1)信号量,P操作(递减操作)可以用于阻塞一个进程,V操作(增加操作)可以用于解除阻塞一个进程。
(2)管程
管程是由一个或多个过程、一个初始化序列和局部数据组成的软件模块,其主要特点如下:
a. 局部数据变量只能被管程的过程访问,任何外部过程都不能访问。
b. 一个进程通过调用管程的一个过程进入管程。
c. 在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。
(3)生产者-消费者模型

6. 进程调度
1)调度种类
高级调度:又称为作业调度,它决定把后备作业调入内存运行;
低级调度:又称为进程调度,它决定把就绪队列的某进程获得CPU;
中级调度:又称为在虚拟存储器中引入,在内、外存对换区进行进程对换。
2)非抢占式调度与抢占式调度
非抢占式:分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程。
抢占式:操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式。
3)调度策略的设计
响应时间: 从用户输入到产生反应的时间
周转时间: 从任务开始到任务结束的时间

7. 进程的调度算法有哪些?

  • 先来先服务(FCFS):此算法的原则是按照作业到达后备作业队列(或进程进入就绪队列)的先后次序选择作业(或进程)
  • 短作业优先(SJF:Shortest Process First):这种算法主要用于作业调度,它从作业后备序列中挑选所需运行时间最短的作业进入主存运行。
  • 时间片轮转调度算法:当某个进程执行的时间片用完时,调度程序便终止该进程的执行,并将它送到就绪队列的末尾,等待分配下一时间片再执行。然后把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证队列中的所有进程,在已给定的时间内,均能获得一时间片处理机执行时间。
  • 高响应比优先:按照高响应比(已等待时间+要求运行时间)/要求运行时间 优先的原则,在每次选择作业投入运行时,先计算此时后备作业队列中每个作业的响应比RP。选择最大的作业投入运行。
  • 优先权调度算法:按照进程的优先权大小来调度。使高优先权进程得到优先处理的调度策略称为优先权调度算法。注意:优先数越多,优先权越小。
  • 多级队列调度算法:多队列调度是根据作业的性质和类型的不同,将就绪队列再分为若干个队列,所有的作业(进程)按其性质排入相应的队列中,而不同的就绪队列采用不同的调度算法。

8. 什么是临界区?如何解决冲突?
每个进程中访问临界资源的那段程序称为临界区,每次只准许一个进程进入临界区,进入后不允许其他进程进入。
(1)如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入;
(2)任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待;
(3)进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区;
(4)如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。

9. 临界资源与临界区的概念

  • 临界资源:临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有,打印机,磁带机等;软件有消息队列,变量,数组,缓冲区等。诸进程间采取互斥方式,实现对这种资源的共享。
  • 临界区:每个进程中访问临界资源的那段代码称为临界区,每次只允许一个进程进入临界区,进入后,不允许其他进程进入。不论是硬件临界资源还是软件临界资源,多个进程必须互斥的对它进行访问。多个进程涉及到同一个临界资源的的临界区称为相关临界区。使用临界区时,一般不允许其运行时间过长,只要运行在临界区的线程还没有离开,其他所有进入此临界区的线程都会被挂起而进入等待状态,并在一定程度上影响程序的运行性能。

10. 什么是死锁?产生条件?如何避免死锁

  • 死锁的概念: 在2个或多个并发进程中,如果每个进程持有某有资源而又都等待别的进程释放它或他们现在保持的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗地讲,就是2个或多个进程被无限期地阻塞、相互等待的一种状态。
  • 死锁产生的原因:系统资源不足,进程推进顺序非法
  • 产生死锁的必要条件
    1).互斥条件:一个资源每次只能被一个进程使用
    2).不可剥夺条件:进程已获得资源,在未使用完之前,不能被其他进程强行剥夺,只能主动释放
    3).请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
    4).循环等待条件:即进程集合{p0,p1,p2,p3……pn};p0正在等待p1占用的资源,p1正在等待p2占用的资源,pn正在等待p0占用的资源。
     只要上述一个条件不成立,就不会发生死锁。
  • 死锁的解除和预防:
    在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。对资源的分配要给予合理规划
  • 死锁的处理策略
    预防策略、避免策略、检测与解除死锁

11. 进/线程的常见状态?以及各种状态之间的转换条件?
A. 进程的常见状态及转换

  • 常见状态:
    就绪:进程已处于准备好运行的状态,即进程已分配到除CPU外的所有必要资源后,只要再获得CPU,便可立即执行。
    运行:进程已经获得CPU,程序正在执行状态。
    阻塞:正在执行的进程由于发生某事件(如I/O请求、申请缓冲区失败等)暂时无法继续执行的状态。
    创建:进程在在被创建,尚未转换到就绪状态。
    创建进程首先申请一个空白的PCB,并向PCB中填写一些控制和管理进程的信息,然后由系统为该进程分配运行时所必需的资源,最后把程序转换到就绪态
    结束状态:进程正在从系统中消失,可能是进程正常结束或者其他原因中断退出运行。
  • 各种状态之间的转换条件
    就绪->运行:就绪态的进程被调度后,获得处理及资源,转为运行态
    运行->就绪:运行态时间片用完后,让出处理机,转为就绪态
    运行->阻塞:当进程请求某一资源的使用和分配或等待某一事件的发生时,就转换为阻塞态
    阻塞->就绪:进程等待事件到来时,如I/O操作结束或中断结束时,将阻塞态转换为就绪态

B. 线程的常见状态及转换

  • 常见状态
    (1)新建状态(New):新创建了一个线程对象。
    (2)就绪状态(Runnable):线程对象创建后,其它线程调用了该对象的start()方法。该状态的线程位于可执行线程池中,变得可执行,等待获取CPU的使用权。
    (3)执行状态(Running):就绪状态的线程获取了CPU。执行程序代码。
    (4)堵塞状态(Blocked):堵塞状态是线程由于某种原因放弃CPU使用权。临时停止执行。直到线程进入就绪状态,才有机会转到执行状态。
    堵塞的情况分三种:
    (一)等待堵塞:执行的线程执行wait()方法
    (二)同步堵塞:执行的线程在获取对象的同步锁时,若该同步锁被别的线程占用。
    (三)其它堵塞:执行的线程执行sleep()或join()方法,或者发出了I/O请求时。
    (5)死亡状态(Dead):线程运行完了或者因异常退出了run()方法,该线程结束生命周期。

12. 上下文切换
对于单核单线程CPU而言,在某一时刻只能执行一条CPU指令。上下文切换是一种将CPU资源从一个进程分配给另一个进程的机制。从用户角度看,计算机能够并行运行多个进程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。

13. 为什么进程上下文切换比线程上下文切换代价高?

  • 进程切换分两步:
    (1)切换页目录以使用新的地址空间
    (2)切换内核栈和硬件上下文
    对于linux来说,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。
  • 切换的性能消耗:
    (1)线程上下文切换和进程上下文切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。
    (2)另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor’s Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。

14. 内存池、进程池、线程池
首先介绍一个概念“池化技术 ”。池化技术就是:提前保存大量的资源,以备不时之需以及重复使用。池化技术应用广泛,如内存池,线程池,连接池等等。

由于在实际应用当做,分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。

  • 线程池:线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当需要一个开辟一个线程去做具体的工作时,就会唤醒线程池中的某一个睡眠线程,让它去做具体工作,当工作完成后,线程又处于睡眠状态,而不是将线程销毁。
  • 进程池与线程池同理。
  • 内存池:内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。

15. 什么是线程安全

  • 如果多线程的程序运行结果是可预期的,而且与单线程的程序运行结果一样,那么说明是“线程安全”的。
  • 线程安全问题都是由全局变量及静态变量引起的。
    若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

16. 同步与异步
(1)同步:

  • 定义:是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么,这个进程将会一直等待下去,直到收到返回信息才继续执行下去。
  • 特点:
    (1)同步是阻塞模式;
    (2)同步是按顺序执行,执行完一个再执行下一个,需要等待,协调运行;

(2)异步

  • 是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
  • 特点:
    (1)异步是非阻塞模式,无需等待;
    (2)异步是彼此独立,在等待某事件的过程中,继续做自己的事,不需要等待这一事件完成后再工作。线程是异步实现的一个方式。

(3)优缺点

  • 同步可以避免出现死锁,读脏数据的发生。一般共享某一资源的时候,如果每个人都有修改权限,同时修改一个文件,有可能使一个读取另一个人已经删除了内容,就会出错,同步就不会出错。但,同步需要等待资源访问结束,浪费时间,效率低。
  • 异步可以提高效率,但,安全性较低。

17. 守护、僵尸、孤儿进程的概念

  • 守护进程:运行在后台的一种特殊进程,独立于控制终端并周期性地执行某些任务。
  • 僵尸进程:一个进程 fork 子进程,子进程退出,而父进程没有wait/waitpid子进程,那么子进程的进程描述符仍保存在系统中,这样的进程称为僵尸进程。
  • 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,这些子进程称为孤儿进程。(孤儿进程将由 init 进程收养并对它们完成状态收集工作)
    init进程是所有Linux进程的父进程,它的进程号为1。init命令是Linux操作系统中不可缺少的程序之一,init进程是Linux内核引导运行的,是系统中的第一个进程。

18. Semaphore(信号量) Vs Mutex(互斥锁)

  • 当用户创立多个线程/进程时,如果不同线程/进程同时读写相同的内容,则可能造成读写错误,或者数据不一致。此时,需要通过加锁的方式,控制临界区(critical section)的访问权限。对于semaphore而言,在初始化变量的时候可以控制允许多少个线程/进程同时访问一个临界区,其他的线程/进程会被堵塞,直到有人解锁。
  • Mutex相当于只允许一个线程/进程访问的semaphore。此外,根据实际需要,人们还实现了一种读写锁(read-write lock),它允许同时存在多个阅读者(reader),但任何时候至多只有一个写者(writer),且不能于读者共存。

19. 线程共享资源和独占资源问题

  • 一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(/私有的)栈(stack),Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程共享,windows进程还有所谓进程默认堆,用户也可以创建自己的堆。
  • 用操作系统术语,线程切换的时候实际上切换的是一个可以称之为线程控制块的结构(TCB),里面保存所有将来用于恢复线程环境必须的信息,包括所有必须保存的寄存器集,线程的状态等。
  • : 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
  • :是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

20. 一个进程可以创建多少线程,和什么有关

  • 1)Linux中
    a. 在Linux32位平台下,一个进程的虚拟内存是4G,内核分走了1G,留给用户用的只有3G。
    b. 线程会占用多少内存,这取决于分配给线程的调用栈大小,可以用ulimit -s命令来查看大小(一般常见的有10M或者是8M)
    c. 创建一个线程占有了10M内存,总共有3G内存可以使用。于是可想而知,最多可以创建差不多300个左右的线程。
  • 2)Windows中
    a. 默认情况下,一个线程的栈要预留1M的内存空间
    b. 而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程 ,但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小。

21. 进程通信方法(Linux和windows下),线程通信方法(Linux和windows下)

(1) 线程间通信:由于多线程共享地址空间和数据空间,所以多个线程间的通信是一个线程的数据可以直接提供给其他线程使用,而不必通过操作系统(也就是内核的调度)。
(2) 进程间的通信则不同,它的数据空间的独立性决定了它的通信相对比较复杂,需要通过操作系统。
(3) 进程的通信机制主要有
管道、有名管道、消息队列、信号量、共享空间、信号、套接字。

  • 管道:它传递数据是单向性的,只能从一方流向另一方,也就是一种半双工的通信方式;只用于有亲缘关系的进程间的通信,亲缘关系也就是父子进程或兄弟进程;没有名字并且大小受限,传输的是无格式的流,所以两进程通信时必须约定好数据通信的格式。管道它就像一个特殊的文件,但这个文件之存在于内存中,在创建管道时,系统为管道分配了一个页面作为数据缓冲区,进程对这个数据缓冲区进行读写,以此来完成通信。其中一个进程只能读一个只能写,所以叫半双工通信,为什么一个只能读一个只能写呢?因为写进程是在缓冲区的末尾写入,读进程是在缓冲区的头部读取,他们各自
    的数据结构不同,所以功能不同。
  • 有名管道:看见这个名字就能知道个大概了,它于管道的不同的是它有名字了。这就不同于管道只能在具有亲缘关系的进程间通信了。它提供了一个路径名与之关联,有了自己的传输格式。有名管道和管道的不同之处还有一点是,有名管道是个设备文件,存储在文件系统中,没有亲缘关系的进程也可以访问,但是它要按照先进先出的原则读取数据。同样也是单双工的。
  • 消息队列:是存放在内核中的消息链表,每个消息队列由消息队列标识符标识,于管道不同的是,消息队列存放在内核中,只有在内核重启时才能删除一个消息队列,内核重启也就是系统重启,同样消息队列的大小也是受限制的。
  • 信号量:也可以说是一个计数器,常用来处理进程或线程同步的问题,特别是对临界资源的访问同步问题。临界资源:为某一时刻只能由一个进程或线程操作的资源,当信号量的值大于或等于0时,表示可以供并发进程访问的临界资源数,当小于0时,表示正在等待使用临界资源的进程数。更重要的是,信号量的值仅能由PV操作来改变。
  • 共享内存:就是分配一块能被其他进程访问的内存。共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。首先说下在使用共享内存区前,必须通过系统函数将其附加到进程的地址空间或说为映射到进程空间。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到 进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。采用共享内存通信的一个显而易 见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而 共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就 解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存 中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。
  • 信号:信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源。信号分为可靠信号和不可靠信号,实时信号和非实时信号。进程有三种方式响应信号1.忽略信号2.捕捉信号3.执行缺省操作。
  • 套接字

A、Windous下多线程通信方法

  • 1.全局变量
    由于同一进程下的线程之间共享数据空间。当需要有多个线程来访问一个全局变量时,通常我们会在这个全局变量前加上volatile声明,以防编译器对此变量进行优化。
  • 2.Message消息机制
    常用的Message通信的接口主要有两个:PostMessage和PostThreadMessage,
    PostMessage为线程向主窗口发送消息。而PostThreadMessage是任意两个线程之间的通信接口。
  • 3.CEvent对象
    CEvent为MFC中的一个对象,可以通过对CEvent的触发状态进行改变,从而实现线程间的通信和同步,这个主要是实现线程直接同步的一种方法。

B、Linux下多线程通信方法

  • 多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
  • 使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

22. 信号量的PV操作

  • wait(s)----------> P操作
  • signal(s)-------> V操作
wait(S){   //S表示资源数目while(S<0);S=S-1;
}
signal(S){S=S+1;
}

记录型信号量

typedef struct{int value;struct process *L;
};semaphore;

22. 哲学家就餐,银行家,读者写者,生产者消费者(怎么加锁解锁,伪代码)
(一)生产者消费者模型

semaphore mutex=1;      //临界区互斥信号量
semaphore empty=n;     //空闲缓冲区
semaphore full=0;      //缓冲区初始化为空
producer(){             //生产者进程while(1){produce an item in nextp;   //生产数据P(empty);                 //获取空缓冲区单元P(mutex); (互斥夹紧)      //进入临界区add nextp to buffer;     //将数据放入到缓存区V(mutex);                    //离开缓存区,释放互斥信号量V(full);                  //满缓冲区数加1}
}
consumer(){while(1){P(full);P(mutex);remove an item from buffer;V(mutex);V(empty);consume the item;
}

(二)读者-写者模型

//读进程优先
int count=0;
semaphore mutex=1;
semaphore rw=1;
writer(){               while(1){P(rw);writing;V(rw);}
}
reader(){while(1){P(mutex);if(count==0)P(rw);count++;V(mutex);reading;P(mutex);count--;if(count==0)V(rw);V(mutex);}
}

(三)哲学家就餐

semaphore chopstick[5]={1, 1, 1, 1, 1};     //初始化信号量
semaphore mutex=1;                         //设置取筷子的信号量
Pi(){                           //i号哲学家的进程do{P(mutex);              //取筷子前获得互斥量         P(chopstick[i]);        //取左边筷子P(chopstick[(i+1)%5]);  //取右边筷子V(mutex);eat;V(chopstick[i]);P(chopstick[(i+1)%5]);think;}while(1);
}

(四)吸烟者问题

int random;
semaphore offer1=0;
semaphore offer2=0;
semaphore offer3=0;
semaphore finish=0;
process P1()    //供应者
{while(1){random=任意一个随机数;random=random%3;if(random==0)V(offer1);else if(random==1)V(offer2);else if(random==2)V(offer3);任意两种材料放在桌子上P(finish);}
}
process P2(){while(1){P(offer3);V(finish);}
}
process P3(){while(1){P(offer2);V(finish);}
}
process P4(){while(1){P(offer`);V(finish);}
}

23. 线程池的了解、优点、调度处理方式和保护任务队列的方式
池的概念:就是为了重用,减少因为创建销毁带来的性能开销;
线程池能控制最大的并发数目,提高资源利用率,便于管理和使用

24. 怎么回收线程
为了资源的重复利用,反复创建线程也会需要消耗系统资源的, 线程池一般是一开始就预留的创建几个线程。比如4个。然后会有一些任务队列,当你需要进行任务处理的时候,就把任务push到这个队列。线程池中的线程就会像消费者一样,从队列中pop出任务来进行处理,当队列中没有任务的时候,线程池的线程就阻塞等待等。

25. 僵尸进程问题
子进程结束但是没有等到父进程wait他,那么他将会变为僵尸进程。内核仍然会保留它的一些信息,这些信息会一直保留到系统为她传递给它的父进程为止,如果父进程结束了也over了。

26. 条件变量用在什么场景,和互斥锁的区别是

  • 互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥锁加锁的线程将会阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥锁加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。
  • 条件变量(cond)是在多线程程序中用来实现"等待–》唤醒"逻辑常用的方法。条件变量利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使“条件成立”。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。线程在改变条件状态前必须首先锁住互斥量,函数pthread_cond_wait把自己放到等待条件的线程列表上,然后对互斥锁解锁(这两个操作是原子操作)。在函数返回时,互斥量再次被锁住。
  • 互斥锁和条件变量所解决的,是不同的问题,不同的场景。
    互斥锁解决的是在 shared memory space 模型下,多个线程对同一个全局变量的访问的竞争问题。由于写操作的非原子性(从内存中读进寄存器,修改,如果其他线程完成了对这个变量的修改,则旧的修改就被覆盖,等等问题),必须保证同一时间只有一个线程在进行写操作。这就涉及到了互斥锁,将临界区的操作锁起来,保证只有一个线程在进行操作。多个线程在等待同一把锁的时候,按照 FIFO 组织队列,当锁被释放时,队头线程获得锁(由操作系统管理,具体不表)。没有获得锁的线程继续被 block,换言之,它们是因为没有获得锁而被 block。
  • 互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,他常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程会先被阻塞,然后解开互斥锁,等待条件变量发生变化。一旦其他的某个线程改变了条件变量,会发出一个signal通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。
    总结为:条件变量用在某个线程需要在某种条件才去保护它将要操作的临界区的情况下,从而避免了线程不断轮询检查该条件是否成立而降低效率的情况,这是实现了效率提高。

27. 使用锁是有开销的,如何改进

  • 互斥锁的开销主要在内核态与用户态的切换:
    申请锁时,从用户态进入内核态,申请到后从内核态返回用户态(两次切换);没有申请到时阻塞睡眠在内核态。使用完资源后释放锁,从用户态进入内核态,唤醒阻塞等待锁的进程,返回用户态(又两次切换);被唤醒进程在内核态申请到锁,返回用户态(可能其他申请锁的进程又要阻塞)。所以,使用一次锁,包括申请,持有到释放,当前进程要进行四次用户态与内核态的切换。同时,其他竞争锁的进程在这个过程中也要进行一次切换。
  • 自旋锁
    与互斥锁不同的是自旋锁不会引起调用者睡眠。如果自旋锁已经被别的进程保持,调用者就轮询(不断的消耗CPU的时间)是否该自旋锁的保持者已经释放了锁

28. 了解协程吗?
协程:用户态的轻量级线程,有自己的寄存器和栈

协程,又称微线程。子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

  • 协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
    (1)最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
    (2)第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

29. 进程切换?

  • 所谓的“进程上下文”,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈上的内容,当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
  • 一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。在LINUX中,当前进程上下文均保存在进程的任务数据结构中。在发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中继服务结束时能恢复被中断进程的执行。

30. 进程的内存空间分布?堆与栈的区别?为什么用堆这种数据结构?
(一) 进程的内存空间分布

(二) 堆和栈的区别

  • 堆:是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
  • 栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

31. 线程池的作用是什么?
处理线程多并发,用一个数组保存线程,然后一直放着,如果没用就用条件变量让它休眠,如果加入一个新的任务就唤醒其中一个去执行这个任务。

32. pthread_cond_signal和pthread_cond_broadcast的区别

  • pthread_cond_signal表示唤醒睡眠线程中的一个
    【单播,可能按照优先级或者先来后到的原则】
  • pthread_cond_boardcast表示唤醒所有睡眠线程【广播】

32. 死锁的原因?条件?如何预防?又如何避免?如何解除?
原因:系统资源不足;进程运行推进顺序不合适;资源分配不当
条件:互斥;不剥夺;循环等待;请求与保持
预防:破坏任意一个条件
避免:银行家算法
检测:资源分配图简化法

33. 线程私有和共享那些资源?进程私有和共享那些资源?

  • 线程私有:线程栈,寄存器,程序寄存器
    共享:堆,地址空间,全局变量,静态变量
  • 进程私有:地址空间,堆,全局变量,栈,寄存器
    共享:代码段,公共数据,进程目录,进程ID

34. 什么是守护进程?如何查看守护进程?什么是僵尸进程?如何查看僵尸进程?
守护进程:它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
查看守护进程:ps axj
僵尸进程:进程退出,但是占用资源没有被回收
查看僵尸进程:ps aux | grep Z

35 什么是信号?
进程间通信机制中唯一的异步通信机制

36. kill函数的每一个参数的作用?

int kill(pid_t pid, int sig);

pid>0:发给ID为pid的进程
Pid=0:发给进程组所有的进程
Pid=-1:发给所有的进程
Pid<-1:发给指定进程组的进程

37. 确保线程安全的几种方式?
1)原子操作(2)同步与锁(3)可重入(4)阻止过度优化volatile
38. 创建进程的步骤?
(1)申请空的PCB
(2)为新进程分配资源
(3)初始化PCB
(4)将新进程插入就绪队列中

39. 进程切换发生的原因?处理进程切换的步骤?

  • 原因:中断发生;更高优先级进程唤醒;进程消耗完了时间片;资源阻塞;
  • 步骤:
    (1)保存处理器的上下文
    (2)用新状态和其它相关信息更新正在运行进程的PCB
    (3)将原来的进程移到合适的队列中【就绪,阻塞】
    (4)选择另外一个执行的进程,更新被选中进程的PCB,将它加载进CPU

40. 信号的生命周期?
信号产生-》信号在进程中注册-》信号在进程中的注销-》执行信号处理函数

41. 信号的产生方式?
(1)当用户按某些终端键时产生信号
(2)硬件异常产生信号【内存非法访问】
(3)软件异常产生信号【某一个条件达到时】
(4)调用kill函数产生信号【接受和发送的所有者必须相同,或者发送的进程所有者必须为超级用户】
(5)运行kill命令产生信号

42. 信号处理方式?
1)执行默认处理方式(2)忽略处理(3)执行用户自定义的函数

43 . 两个线程交叉打印A和B

  • 方法一:信号量
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>sem_t blank_number, product_number;void *producer(void *arg)
{while (1) {sem_wait(&blank_number);//给blank_number信号量减1,如果blank_number为0,在这里阻塞,直到blank_number不为0printf("%c\n",65);sem_post(&product_number);//给product_number加1sleep(rand()%5);}
}
void *consumer(void *arg)
{while (1) {sem_wait(&product_number);printf("%c\n",66);sem_post(&blank_number);sleep(rand()%5);
}
}
int main(int argc, char *argv[])
{pthread_t pid, cid;sem_init(&blank_number, 0, 1);//对信号量进行初始化sem_init(&product_number, 0, 0);pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid, NULL);//回收线程,当然这块两个线程由于一直在while循环,线程会一直阻塞在这句话pthread_join(cid, NULL);sem_destroy(&blank_number);//销毁信号量sem_destroy(&product_number);return 0;
}
  • 方法二:互斥量
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>pthread_mutex_t mutex;
int flag=1;//因为是linux下c语言实现的,所以不能写成bool flag=true;因为bool类型是C++新增的类型
void *printA(void *arg)
{while (1) {pthread_mutex_lock(&mutex);if(flag==1)printf("%c\n",65);flag=0;pthread_mutex_unlock(&mutex);sleep(rand()%5);}
}
void *printB(void *arg)
{while (1) {pthread_mutex_lock(&mutex);if(flag==0)printf("%c\n",66);flag=1;pthread_mutex_unlock(&mutex);sleep(rand()%5);}
}
int main(int argc, char *argv[])
{pthread_t aid, bid;pthread_mutex_init(&mutex,NULL);pthread_create(&aid, NULL, printA, NULL);//创建线程pthread_create(&bid, NULL, printB, NULL);pthread_join(aid, NULL);//回收线程pthread_join(bid, NULL);pthread_mutex_destroy(&mutex);//销毁互斥量return 0;
}
  • 方法三
#include <thread>
#include <iostream>
#include <mutex>
#include <condition_variable>std::mutex data_mutex;
std::condition_variable data_var;
bool flag = true;void printA()
{while(1){std::this_thread::sleep_for(std::chrono::seconds(1));std::unique_lock<std::mutex> lck(data_mutex) ;data_var.wait(lck,[]{return flag;});std::cout<<"thread: "<< std::this_thread::get_id() << "   printf: " << "A" <<std::endl;flag = false;data_var.notify_one();}
}void printB()
{while(1){std::unique_lock<std::mutex> lck(data_mutex) ;data_var.wait(lck,[]{return !flag;});std::cout<<"thread: "<< std::this_thread::get_id() << "   printf: " << "B" <<std::endl;flag = true;data_var.notify_one();}
}int main()
{std::thread tA(printA);std::thread tB(printB);tA.join();tB.join();return 0;
}

44. Select,poll和epoll的区别?为什么?
Select和poll缺点:(1)每次调用select都需要将fd集合从用户态拷贝到内核态(2)每一次调用select都需要在内核中遍历所有的fd(3)select支持的文件描述符太小,默认1024,poll没有限制

Epoll:使用红黑树来存储fd,同时每一次通过epoll__ctl来将fd加入内核中,同时通过双向列表来返回已经出发某一个事件的fd

45 互斥锁和自旋锁的相关问题
(一)互斥锁

  1. 互斥锁的作用?
    互斥锁是为实现保护共享资源而提出一种锁机制。采用互斥锁保护临界区,防止竞争条件出现。当某个线程无法获取互斥锁时,该线程会被挂起,当其他线程释放互斥锁后,操作系统会唤醒被挂起在这个锁上的线程,让其运行。
  2. 互斥锁的实现:
    在Linux下互斥锁的实现是通过futex这个基础组件。
    互斥锁加锁解锁开销很大,需要从用户态切换到内核态,上下文切换以及涉及缓存的更新等等。通常很多同步操作发生的时候并没有竞争的产生,此时上述开销就没有必要。考虑到这个因素,futex通过用户空间的共享内存以及原子操作,在共享的资源不存在竞争的时候,不会进行系统调用而是只有当竞争出现的情况下再进行系统调用陷入内核。进程或者线程在没有竞争的情况下可以立刻获取锁。具体来说,futex的优化方式如下:
    futex将同步过程分为两个部分,一部分由内核完成,一部分由用户态完成;如果同步时没有竞争发生,那么完全在用户态处理;否则,进入内核态进行处理。减少系统调用的次数,来提高系统的性能是一种合理的优化方式。
  3. 互斥锁的使用场景:
    (1)解决线程安全问题,一次只能一个线程访问被保护的资源。
    (2)被保护资源需要睡眠,那么可以使用互斥锁。

(二)自旋锁

  1. 自旋锁的作用:
    自旋锁也是为实现保护共享资源而提出一种锁机制。自旋锁不会引起调用线程阻塞,如果自旋锁已经被别的线程持有,调用线程就一直循环检测是否该自旋锁已经被释放。
  2. 自旋锁的特点:
    (1)线程不会阻塞,不会在内核态和用户态之间进行切换。
    (2)消耗 CPU: 因为自旋锁会不断的去检测是否可以获得锁,会一直处于这样的循环当中,这个逻辑的处理过程消耗的 CPU相对其实际功能来说是浪费的。
  3. 自旋锁的实现:
    CAS(compare and swap) 是实现自旋锁的基础。CAS 的实现基于硬件平台的指令。
    CAS涉及到三个操作数:
  • 需要读写的内存值 value1
  • 进行比较的值 value2
  • 拟写入的新值 value3

当且仅当 value1 的值等于 value2时,CAS通过原子方式用新值value3来更新value1的值,否则不会执行任何操作。可以理解为线程会不停的执行一个while循环进行CAS操作,直到达成条件。

  1. 自旋锁的使用场景:
    (1)如果预计线程持有锁的时间比较短,相比使用互斥锁两次上下文切换的开销而言,自旋锁消耗的CPU更少的情况下,那么使用自旋锁比互斥锁更高效。
    (2)如果代码当中经常需要加锁但是实际情况下产生竞争的情况比较少此时可以使用自旋锁进行优化。
    (3)被保护的共享资源需要在中断上下文访问,就必须使用自旋锁。

(三)原子操作

  1. 原子操作的作用:
    原子操作是不可被中断的一个或者一系列操作,原子操作可以避免操作被进程/线程的调度打断,原子操作的过程当中不会出现上下文的切换,保证操作的完整性。同时在多处理器的环境下,原子操作也保证了多处理器之间对内存访问的原子性。
  2. 原子操作的实现:
    原子操作主要是通过硬件操作的方式实现。在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段,通过将总线锁住,保证其他CPU无法在同一时刻操作内存,从而保证操作的原子性。同时由于缓存的存在,原子操作也需要缓存锁来提供复杂内存情况下的实现。

46. 你知道哪些锁?
互斥锁,自旋锁,读写锁,行锁,表锁,乐观锁,悲观锁。

互斥锁:实现互斥操作最简单的方案
自旋锁:无锁操作,比较耗费CPU
读写锁:适合读多写少的场景
行锁:数据库当中细粒度的一种锁实现,只锁一行数据,锁粒度相对较低
表锁:数据库当中粗粒度的一种锁实现,锁整张表,锁粒度较大
乐观锁:当线程去获取数据的时候,乐观地认为别的线程不会修改数据,不对对数据加锁。在更新数据的时候会去通过数据的version(版本号)来判断,如果数据被修改了就拒绝更新。
悲观锁:当线程去获取数据的时候,悲观地以为别的线程会去修改数据,所以线程每次获取数据的时候都会加锁。

47. 锁的作用是什么?
锁的作用是为了控制并发访问的安全性。

48. 自旋锁和互斥锁的使用场景的区别是什么?

  • 互斥锁使用场景:被保护资源需要睡眠,那么只能使用互斥锁或者信号量,不能使用自旋锁。
  • 自旋锁使用场景:锁的持有时间非常短或者被保护的共享资源需要在中断上下文访问。

49. 如何提升并发访问当中锁的性能?

  • 减小锁的粒度:比如锁分段技术
  • 减少锁持有的时间
  • 可以使用自旋锁或者原子操作优化使用互斥锁的地方
  • 读多写少的情况下可以使用特定功能的锁比如读写锁优化互斥锁
  • 读写分离,对读和写的操作采用分离的方式实现

50. 互斥锁的开销有哪些?
线程(进程)在申请锁时,从用户态切换到内核态,申请到锁之后从内核态返回用户态,这个过程会产生两次上下文切换;线程(进程)在使用完资源后释放锁,从用户态切换到内核态,操作系统会唤醒阻塞等待锁的其他进程,线程(进程)返回用户态,这个过程也会产生两次上下文的切换;而进程上下文切换又包含直接消耗和间接消耗:

  • 直接消耗包括CPU寄存器保存和加载
  • 间接消耗包括TLB的刷新等等

51. 线程共享哪些进程的资源?

  1. 进程代码段 ;
  2. 进程的公有数据;
  3. 进程打开的文件描述符;
  4. 信号的处理器;
  5. 进程的当前目录;
  6. 进程用户ID与进程组ID。

52. 线程独立的资源有哪些?

  1. 线程ID;
  2. 寄存器组的值;
  3. 线程栈;
  4. 错误返回码;
  5. 线程的信号屏蔽码;
  6. 线程的优先级。

53. 说一说你知道的多线程和多进程的场景?
多线程场景比如一些web server,每到达一个请求使用一个线程去处理请求,在链接数量不大的情况下,比进程开销低很多,还可以使用线程池去优化创建和销毁线程的开销。

多进程场景比如 Nginx,一个 Master 多个 Worker,进程间只进行有限的通信,并不传递数据,每个进程使用IO多路复用去管理事件,是一个典型的多进程场景。

54. 除了进程和线程你还知道哪些概念?
协程。协程是用户级线程,在用户空态去调度协程,维护和操作系统线程的多对多的关系。

55. 常见信号有哪些?
SIGINT,SIGKILL(不能被捕获),SIGSTOP(不能被捕获)、SIGTERM(可以被捕获),SIGSEGV,SIGCHLD,SIGALRM

56. wait和waitpid区别
1). wait会令调用者阻塞直至某个子进程终止;
2). waitpid则可以通过设置一个选项来设置为非阻塞,另外waitpid并不是等待第一个结束的进程而是等待参数中pid指定的进程。
waitpid中pid的含义依据其具体值而变:
  pid==-1 等待任何一个子进程,此时waitpid的作用与wait相同
  pid >0 等待进程ID与pid值相同的子进程
  pid==0 等待与调用者进程组ID相同的任意子进程
  pid<-1 等待进程组ID与pid绝对值相等的任意子进程
waitpid提供了wait所没有的三个特性

  • waitpid使我们可以等待指定的进程
  • waitpid提供了一个无阻塞的wait
  • waitpid支持工作控制

57. 父进程fork后父子进程共享的内容
fork之后,子进程会拷贝父进程的数据空间、堆和栈空间(实际上是采用写时复制技术),二者共享代码段。 所以在子进程中修改全局变量(局部变量,分配在堆上的内存同样也是)后,父进程的相同的全局变量不会改变。
共享fd,以及fd对应的文件表项。

不同进程打开同一个文件

进程的fork与文件描述符的拷贝 进程的所打开文件和在fork后的结构图如下所示,子进程是共享父进程的文件表项。

58. 产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不可强行占有:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

59. 处理死锁的基本方法:
死锁预防:通过设置某些限制条件,去破坏死锁的四个条件中的一个或几个条件,来预防发生死锁。但由于所施加的限制条件往往太严格,因而导致系统资源利用率和系统吞吐量降低。
死锁避免:允许前三个必要条件,但通过明智的选择,确保永远不会到达死锁点,因此死锁避免比死锁预防允许更多的并发。
死锁检测:不须实现采取任何限制性措施,而是允许系统在运行过程发生死锁,但可通过系统设置的检测机构及时检测出死锁的发生,并精确地确定于死锁相关的进程和资源,然后采取适当的措施,从系统中将已发生的死锁清除掉。
死锁解除:与死锁检测相配套的一种措施。当检测到系统中已发生死锁,需将进程从死锁状态中解脱出来。常用方法:撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程。死锁检测盒解除有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。

60. vfork函数
vfork() 子进程与父进程共享数据段。vfork()保证子进程先运行,在它调用exec或_exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

61. 线程进程的区别体现在几个方面

  • 因为进程拥有独立的堆栈空间和数据段,所以每当启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,系统开销比较大,而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,切换速度也比进程快,效率高,但是正由于进程之间独立的特点,使得进程安全性比较高,也因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。一个线程死掉就等于整个进程死掉。
  • 体现在通信机制上面,正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。。
  • 属于同一个进程的所有线程共享该进程的所有资源,包括文件描述符。而不同过的进程相互独立。
  • 线程必定也只能属于一个进程,而进程可以拥有多个线程而且至少拥有一个线程;

62. 进程与线程的选择取决以下几点
1)、需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。
2)、线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应
3)、因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;
4)、并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求;
5)、需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。

63. 守护进程
守护进程是运行在后台的一种特殊进程, 不受终端控制,Linux系统的大多数服务器就是通过守护进程实现的。一个守护进程的父进程是init进程。

1)创建子进程,父进程退出
2)在子进程中创建新会话
3)改变当前目录为根目
4)重设文件权限掩码
5) 关闭文件描述符

操作系统学习(2) 进程管理相关推荐

  1. 计算机操作系统学习笔记----进程管理

    进程与线程 进程是资源分配的基本单位,也是独立运行的基本单位.进程是资源分配的基本单位,这是与线程的主要区别. 程序的顺序执行具有如下特征: 顺序性:处理器的操作严格按照程序所规定的顺序执行. 封闭性 ...

  2. (王道408考研操作系统)第二章进程管理-第三节10:经典同步问题之哲学家进餐问题

    本文接: (王道408考研操作系统)第二章进程管理-第三节6:经典同步问题之生产者与消费者问题 ((王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题 (王道408 ...

  3. (王道408考研操作系统)第二章进程管理-第三节8:经典同步问题之吸烟者问题

    本文接: (王道408考研操作系统)第二章进程管理-第三节6:经典同步问题之生产者与消费者问题 ((王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题 文章目录 一 ...

  4. (王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题

    注意:生产者与消费者问题Linux系统编程专栏有案例讲解 Linux系统编程39:多线程之基于阻塞队列生产者与消费者模型 Linux系统编程40:多线程之基于环形队列的生产者与消费者模型 本文接:(王 ...

  5. 笔记篇:操作系统第二章 进程管理

    笔记篇:操作系统第二章 进程管理 目录 笔记篇:操作系统第二章 进程管理 2.1 进程的基本概念 2.1.1 程序的顺序执行及其特征 2.1.2 前驱图 2.1.3 程序的并发执行及其特征 2.1.4 ...

  6. Linux内核学习008——进程管理(四)

    Linux内核学习007--进程管理(四) 进程家族树 Unix系统的进程之间存在一个明显的继承关系,所有的进程都是PID为1的init进程的后代.内核在系统启动的最后阶段启动init进程,然后ini ...

  7. 操作系统——实验二 进程管理

    1.实验目的 (1)加深对进程概念的理解,明确进程和程序的区别. (2)进一步认识并发执行的实质. (3)分析进程竞争资源现象,学习解决进程互斥的方法. 2.实验预备内容 (1)阅读Linux的sch ...

  8. 操作系统学习:进程、线程与Linux0.12初始化过程概述

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 ps:基于x86硬件的pc系统 进程 进程是一种控制流集合,集合中至少包含一条 ...

  9. 操作系统第二章 进程管理

    写在前面:本文参考王道论坛的 操作系统考研复习指导单科书 文章目录 第二章 进程管理 进程同步 读者写者问题 哲学家就餐问题 练习题 哲学家就餐:加碗(2019真题) 既是生产者又是消费者 和尚取水( ...

最新文章

  1. 这场编程语言的发布会,不参加可太亏了!
  2. excel中如何et vb根据数据自动生成表格_如何实现excel与PPT互联互通(动态生成PPT)...
  3. UOS系统下FFmpeg源码编译安装及注意事项
  4. Flink 助力美团数仓增量生产
  5. 博客中gitalk最新评论的获取 github api使用
  6. 数据结构之树、森林、二叉树的转化
  7. Java中的Comparable和Comparator到底该怎么用看完就全明白了
  8. TensorFlow2.0:模型的保存与加载
  9. 计算机视觉基础:图像处理 Task 04:图像滤波
  10. [转载] Python连接MySQL、Mongodb、SQLite
  11. Consider the following: If you want an embedded database (H2, HSQL or Der...
  12. 题目448-寻找最大数
  13. QMS-云质-质量管理软件-企业数字化质量管理解决方案
  14. Squid代理服务器应用及配置(图文详解)
  15. 关于*.md文件打开的问题及图片丢失
  16. 计算机检索的辅助检索方法有,中国知网等文献检索的一般方法.docx
  17. 信通院牵头数列科技参与主编的《信息系统稳定性保障能力建设指南》正式发布
  18. python爬取小说爬取_用python爬取笔趣阁小说
  19. echarts柱状图顶部与柱体中同时显示数值
  20. linux安装到什么硬盘比较好,硬盘安装四大发行版Linux比较

热门文章

  1. IDEA高效使用技巧--->IDEA批量修改变量快捷键和全局搜索键
  2. PyCharm + PySide2/PySide6 外部工具配置
  3. Maven命令行 打包
  4. 10大PPT模板可直接套用助PM正确写好年终总结
  5. Python中的set()函数使用
  6. 路由声明式传参和编程式传参
  7. 第一讲 Matlab/Simulink入门——简单系统模型的Simulink仿真
  8. 最小割问题-Karger‘s algorithm
  9. enc易能变频_ENC易能变频器专修公司
  10. android实现新闻内容显示功能,Android开发实现自定义新闻加载页面功能实例