计算机操作系统概述
计算机程序的执行过程
主要是四个过程:预处理、编译、汇编、链接
(1)预处理 根据源代码文件中的以#开头的指令,将包含的头文件加载进入源程序中。
(2)编译 把预处理之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。
(3)汇编 将汇编代码转换为机器语言指令。汇编器的汇编过程相对于编译器来说比较简单,没有复杂的语法,也没有语义,不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译过来。
经汇编后,产生目标文件xxx.o(Linux)、xxx.obj(Window)
(4)将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。
处理器管理
指令与寄存器、处理器模式、中断、进程、多线程、处理器调度算法
外中断和异常(内中断)有什么区别?
外中断是指由CPU执行指令以外的事件引起的,如I/O完成中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。
而异常是由CPU执行指令的内部引起的,如非法操作码、地址越界、算术溢出等。
处理器模式有几种?(计算机有几种运行模式)
计算机一般设置0、1、2、3等四种运行模式,分别对应:0操作系统内核、1系统调用、2共享库程序、3用户程序等保护级别。
0模式可以执行全部指令,3模式只能执行非特权指令,其他每种运行模式可以规定执行的模式子集。
一般来说,现代操作系统只使用0和3两种模式,对应内核模式和用户模式。
(什么是用户态和内核态?)
用户态和内核态是操作系统的两种运行状态。
内核态:处于内核态的CPU可以访问任意的数据,包括外围设备,可以从一个程序切换到另外一个程序,并且占用CPU不会发生抢占情况,一般处于特权级0的状态我们称之为内核态;
用户态:处于用户态的CPU只能受限的访问内存,并且不允许访问外围设备,用户态下的CPU不允许独占,也就是说CPU能够被其他程序获取。一般处于特权级3的状态为用户态。
用户态和内核态是如何切换的?
用户态切换到内核态:由中断、异常、系统调用触发
内核态切换到用户态:由OS执行中断返回指令完成
进程和线程的概念
进程是一个具有一定独立活动的程序关于某个数据集合的一次运行活动。它是OS进行资源分配和调度的一个独立单位。一个进程包括五个实体部分:
(OS管理运行程序的) 数据结构P
(运行程序的) 内存代码C
(运行程序的) 内存数据D
(运行程序的) 通用寄存器信息R
(OS控制程序执行的) 程序状态字信息PSW
线程是进程的一条执行路径,也是CPU调度和分派的基本单位,同一进程中的所有线程共享进程获得的主存空间和资源。
进程和线程的区别
1、进程是系统中运行的一个应用程序,是资源分配的最小单位;线程是进程中的一条执行路径,是CPU调度的最小单位。
2.一个线程只属于一个进程,一个进程可以包含多个线程。
3.创建进程或撤销进程,OS都要为之分配或回收资源,OS开销远大于创建或撤销线程时的开销。
4.不同进程地址空间相互独立,同一进程内的线程共享同一地址空间。一个进程的线程在另一个进程内是不可见的。
为什么有了进程,还要有线程呢
1.创建或撤销进程,OS开销比较大,通过使用线程可以实现快速线程切换,减少系统管理开销;
2.进程在同一时间只能干一件事情,如果在执行过程中发生堵塞,整个进程就会被挂起,即使进程中有些工作不依赖于等待的资源,仍然不会执行。因此引入比进程粒度更小的线程可以减少程序在并发执行时所付出的时间和空间开销,提高并发程度。
3.线程通信更容易实现。
进程切换为什么比线程更消耗资源?
进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈;线程切换时只需要切换硬件上下文和内核栈。
什么是协程?
协程是微线程,在子程序内部执行,可在子程序内部中断,转而执行别的子程序,在适当的时候再返回来接着执行。
线程与协程的区别:
(为什么协程比线程切换的开销小?)
(1)协程执行效率极高。协程直接操作栈基本没有内核切换的开销,所以上下文的切换非常快,切换开销比线程更小。
(2)协程不需要多线程的锁机制,因为多个协程从属于一个线程,不存在同时写变量冲突,效率比线程高。
(3)一个线程可以有多个协程。
协程的优势:
(1)协程调用跟切换比线程效率高:协程执行效率极高。协程不需要多线程的锁机制,可以不加锁的访问全局变量,所以上下文的切换非常快。
(2)协程占用内存少:执行协程只需要极少的栈内存(大概是4~5KB),而默认情况下,线程栈的大小为1MB。
(3)切换开销更少:协程直接操作栈基本没有内核切换的开销,所以切换开销比线程少。
进程的状态转换
进程包括三种状态:等待态、就绪态、运行态。
运行态指进程占有处理器运行;
就绪态指进程具备运行条件等待处理器运行;
等待态指进程由于等待资源、输入输出、信号等而不具备运行条件;
1.就绪->执行:对就绪态进程,当进程调度程序按一种选定的策略从中选中一个就绪进程,为之分配了处理器后,该进程便由就绪状态变为执行态;
2.执行->等待:正在执行的进程由于发生某等待事件而无法执行,则进程由执行态变为等待态,如等待资源(主存空间或外部设备)、等待I/0信息、等待信号等;
3.等待->就绪:处于等待态的进程,在其等待的事件已经发生,如I/O完成、资源得到满足时,处于等待状态的进程并不会立刻转入执行状态,而是先转入就绪状态,然后再由系统进程调度程序在适当的时候将该进程转为执行态;
4.执行->就绪:执行态进程因时间片用完而被暂停执行,或在采用抢先式优先级调度算法的系统中,当有更高优先级的进程要运行而被迫让出处理器时,该进程便由执行状态转变为就绪状态。
进程挂起的概念
OS无法预期进程的数目与资源需求,它在运行过程中可能出现资源不足的情况,表现为性能低和死锁两种情况。
解决方法是剥夺某些进程的内存及其他资源,调入OS管理的对换区,不参加进程调度,待适当时候再调入内存、恢复资源、参与运行,这就是进程挂起。
挂起态与等待态有着本质的区别,后者占有已申请到的资源处于等待,前者没有任何资源。
进程挂起的选择与恢复
一般选择等待态进程进入挂起等待态,也可选择就绪态进程进入挂起就绪态,运行态进程还可以挂起自己。
等待事件结束后,挂起等待态进入挂起就绪态,一般选择挂起就绪态进程予以恢复。
线程混合式策略下的线程状态
处理器调度的层次
高级调度:又称长程调度,作业调度。决定能否加入到执行的进程池中
低级调度:又称短程调度,进程调度。决定哪个可用进程占用处理器执行
中级调度:又称平衡负载调度。决定主存中的可用进程集合
进程的调度算法
调度算法是指根据系统的资源分配策略所规定的资源分配算法。常用的调度算法有:先来先服务调度算法、时间片轮转调度法、短作业优先调度算法、最短剩余时间优先调度算法、高响应比优先调度算法、优先级调度算法等。
先来先服务调度算法 first come first served,FCFS
先来先服务调度算法是一种最简单的调度算法,也称为先进先出或严格排队方案。当每个进程就绪后,它加入就绪队列。当前正运行的进程停止执行,选择在就绪队列中存在时间最长的进程运行。该算法既可以用于作业调度,也可以用于进程调度。先来先去服务比较适合于长作业(进程),而不利于短作业(进程)。
Java模拟算法思想:https://blog.csdn.net/qq_48455576/article/details/116949130
时间片轮转调度算法
时间片轮转调度算法主要适用于分时系统。在这种算法中,系统将所有就绪进程按到达时间的先后次序排成一个队列。进程调度总是选择就绪队列中第一个进程执行,即先来先服务的原则,但仅能运行一个时间片。
短作业优先调度算法
短作业优先调度算法是指对短作业优先调度的算法,从后备队列中选择一个或若干各估计运行时间最短的作业,将它们调入内存运行。短作业优先调度算法是一个非抢占策略,他的原则是下一个选择预计处理时间最短的进程,因此短进程将会越过长作业,跳至队列头。
最短剩余时间优先调度算法
最短剩余时间是针对最短进程优先增加了抢占机制的版本。在这种情况下,进程调度总是选择预期剩余时间最短的进程。当一个进程加入到就绪队列时,他可能比当前运行的进程具有更短的剩余时间,因此只要新进程就绪,调度程序就可能抢占当前正在运行的进程。像最短进程优先一样,调度进程正在执行选择函数是必须有关于处理时间的估计,并且存在长进程饥饿的危险。
高响应比优先调度算法
高响应比优先调度算法主要用于作业调度,该算法是对先来先服务调度算法和短作业优先调度算法的一种综合平衡,同时考虑每个作业的等待时间和估计的运行时间。在每次进行作业调度时,先计算后备作业队列中每个作业的响应比,从中选出响应比最高的作业投入运行。
优先级调度算法
优先级调度算法每次从后备作业队列中选择优先级最高的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列。在进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,将处理器分配给它,使之投入运行。
分级调度算法
又称多队列策略,反馈循环队列。
建立多个不同优先级的就绪进程队列,多个就绪队列间按优先级调度,高优先级就绪进程分配的时间片短,单个就绪进程队列中进程的优先级和时间片相同
彩票调度算法
为进程发放针对系统各种资源(如CPU时间)的彩票;当调度程序需要做出决策时,随机选择一张彩票,持有该彩票的进程将获得系统资源。
合作进程之间的彩票交换。
进程终止的方式?
正常退出、错误退出、严重错误、被其他进程杀死
守护进程、僵尸进程和孤儿进程
守护进程
只在后台运行的,没有控制终端与之相连的进程。它独立于控制终端,周期性地执行某种任务。Linux的大多数服务器就是用守护进程的方式实现的,如web服务器进程http等。
孤儿进程
(任何进程都必须有父进程)
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
如何避免僵尸进程?
通过signal通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程堵塞、waitpid可以通过传递WNONANF使父进程不堵塞立即返回。
如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
通过两次调用fork。父进程首先创建一个子进程然后waitpid等待子进程退出,子进程再fork一个进程后退出。这样子进程退出后会被父进程等待回收,而对孙子进程其父进程已经退出,所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
存储管理
存储管理、虚拟存储器、页式存储管理、页面调度、段式存储管理
常见内存分配错误
1、内存分配为成功,却使用了它。
常用解决方法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理。
2、内存分配虽然成功,但是为初始化就引用它。
3、内存分配成功并且已经初始化,但操作越过了内存的边界。
4、忘记释放内存,造成内存泄露。
动态内存的申请和释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。
5、释放了内存却继续使用它。
程序中的对象调用过于复杂,实在难以搞清某个对象是否已经释放了内存;
函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁;
使用free或delete释放了内存后,没有将指针设置为NULL。导致产生了“野指针”。
内存交换中,被换出的进程保存在哪里?
保存在磁盘中,也就是外存中。具有兑换功能的操作系统中,通常把磁盘空间分为文件区和对换区两部分。文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式;对换区空间只占磁盘空间的小部分,被患处的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常对换区采用连续分配方式。总之,对换区的I/O速度对文件区的更快。
分页和分段的区别?
1、段是信息的逻辑单位,它是根据用户的需要划分的,因此段对用户是可见的;页是信息的物理单位,是为了管理内存的方便而划分的,对用户是透明的;
2、段的大小不固定,由它所完成的功能决定;页的大小固定,由系统决定;
3、段向内存提供二维地址空间;页向用户提供的是一维地址空间;
4、段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制。
物理地址、逻辑地址、虚拟内存的概念?
物理地址:是内存单元真正的地址,进程在运行时执行指令和访问数据最后都要通过物理地址从内存中存取。
逻辑地址:是指计算机用户看到的地址。
虚拟内存:是计算机系统内存管理的一种技术,它使得应用程序认为它拥有连续的可用的内存,而实际上,它通常是被分隔成多个物理碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
页面置换算法有哪些?
请求调页,即对不在内存中的"页",当进程执行时要用的时候才调入,否则有可能到程序结束时也不会调入。而内存中给页面留的位置是有限的,在内存中以帧为单位放置页面。为了防止请求调页的过程出现过多的内存页面错误而使得程序执行效率下降,我们需要设计一些页面置换算法,页面按照这些算法进行相互替换时,可以尽量达到较低的错误率。常用的页面置换算法如下:
先进先出置换算法(FIFO)
先进先出,即淘汰最早调入的页面。
最佳置换算法(OPT)
选未来最远将使用的页淘汰,是一种最优的方案,可以证明缺页数最小。
最近最久未使用算法(LRU)
选择最近最久未使用的页面淘汰。
时钟置换算法(NRU)
该算法为每个页面设置一位访问位,将内存中的所有页面都通过链接指针链成一个循环队列。
抖动是什么,它也叫颠簸现象
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称为抖动,或颠簸。产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数(分配给进程的物理块不够)
设备管理和文件管理
I/O、文件系统、进程间通信、死锁
什么是缓冲区溢出,有何危害?
缓冲区为暂时置放输入输出数据的内存。缓冲区溢出是指当计算机向缓冲区填充数据时超出了缓冲区的容量,溢出的数据覆盖在合法数据上。造成缓冲区溢出的原因主要是程序中没有仔细检查用户输入是否合理。
计算机中,缓冲区溢出会造成的危害主要有以下两点:
程序崩溃导致拒绝服务和跳转并且执行一段恶意代码。
并发程序设计
并发程序、PV操作、管程、进程通信、死锁
原子操作的是如何实现的
如何理解并发和并行
并发:当有多个线程在操作时,如果系统只有一个CPU,那它就不可能真正同时运行一个以上的线程,它只能将CPU运行时间划分成若干个时间段,再将时间段分配给每个线程执行,在一个时间段的线程运行时,其他线程处于挂起状态,这种方式就是并发(Concurrent)。
并行:多处理器系统中,拥有一个以上的CPU,当一个CPU执行一个线程时,另外一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式就是并行(Parallel).
区别:并行是指在不同实体上两个或多个事件在同一时刻发生,而并发是指在同一实体上的两个或多个事件在同一时间间隔发生;
同步、异步、阻塞、非阻塞
同步:当一个同步调用发出后,调用者要一直等待返回结果。通知后,才能进行后续的操作。
异步:当一个异步过程调用后,调用者不能立刻得到返回结果。实际处理这个调用的部分在完成后,通过状态、通知和回调来通知调用者。
阻塞:是指调用结果返回前,当前线程会被挂起。
非阻塞:是指即使调用结果没返回,也不会阻塞当前线程。
进程间的通信方式
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC的方式通常有管道(普通管道、流管道、命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中Socket和Streams支持不同主机上的两个进程IPC。
管道是内核所开辟的一段缓存空间。进程间通过管道通信时,本质上是通过共享操作这段缓存来实现,操作这段缓存的方式,是读写文件的形式。
普通管道(无名管道)
1.它是半双工的通信方式,具有固定的读端和写端;
2.它只能用于父子进程或者兄弟进程之间的进程的通信;
流管道
相对于普通管道来说,流管道可以双向传输。
命名管道(有名管道)
相对于普通管道来说,命名管道没有进程关系限制,可以在无关进程之间进行数据交换。
消息队列
1.消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符ID来标识;
2.消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级;
3.消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会删除;
4.消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
信号量
(介绍一下信号量)
信号量(semaphore)用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。它是一个计数器,用来控制多个进程对共享资源的访问。
1.信号量用于进程间同步,若要在进程间传递数据需要结合共享内存;
2.信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作;
3.每次对信号量的PV操作不仅限于对信号量加1或减1,而且可以加减任意正整数;
4.支持信号量组。
共享内存
1.共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。它是最快的一种IPC,因为进程是直接对内存进行存取。
为什么共享内存速度最快?
因为共享内存的整个通信过程对信息的复制只有两次:从数据来源复制到共享内存,从共享内存复制到数据目的地。
而管道、消息队列等对信息的复制需要四次,因为有缓冲区的存在,读写都要经过缓冲区。
请介绍线程之间的通信方式
锁机制:包括互斥锁、条件变量、读写锁互斥锁提供了以排他方式防止数据结构被并发修改的方法。读写锁允许多个线程同时读共享数据,而对写操作是互斥的。条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
信号机制(Signal):类似进程间的信号处理线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
什么是死锁?
死锁是指每一个进程都在等待被另一个进程所占有的、不能抢占的资源。
产生死锁的原因?
竞争资源
PV操作使用不当
同类资源使用不当
对临时性资源使用不加限制
产生死锁的因素不仅与系统拥有的资源数量有关,而且与资源分配策略,进程对资源的使用要求以及并发进程的推进顺序有关。
说明:
系统中的资源可以分为两类:
1、可剥夺资源:是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺资源;
2、不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如打印机等。
死锁产生的必要条件?
1、互斥条件:进程应互斥使用资源,任一时刻一个资源仅为一个进程独占。
2、占有和等待条件:一个进程请求资源得不到满足而等待时,不释放已占有的资源。
3、不剥夺条件:任一进程不能从另一进程那里抢占资源。
4.循环等待条件:存在一个循环等待链,每一个进程分别等待它前一个进程所持有的资源。
解决死锁的基本方法
死锁防止、死锁避免、死锁检测和恢复。
死锁的防止?
破坏四个必要条件之一,死锁就可防止。
破坏互斥条件:把独占型资源改造成共享性资源,使资源可同时访问而不是互斥使用。这个办法对许多资源往往是不能做到的。
破坏占有和等待条件:使用静态分配,一个进程必须在执行前就申请它所要的全部资源,并且直到它所要的资源都得到满足之后才开始执行。
破坏不剥夺条件:采用剥夺式进程调度方法,但目前只适用于对主存资源和处理器资源的分配,而不适用于所有资源。
破坏循环等待条件:采用层次分配,资源被分成多个层次,一个进程只能申请层次比较低的资源之后再去申请层次比较高的资源,当它要释放一个资源时,必须先释放所占用的较高层的资源,当一个进程获得某一层的一个资源后,它想申请该层中的另一个资源就必须先释放该层中的已占资源。
怎么避免死锁?
当不能防止死锁的产生时,如果能掌握并发进程中与每个进程有关的资源申请情况,仍然可以避免死锁的产生,只需要在为申请者分配资源前先测试系统状态,若把资源分配给申请者会产生死锁的话,则拒绝分配,否则接收申请,为它分配资源。
银行家算法
系统首先检查申请者对资源的最大需求量,如果现存的资源可以满足它的最大需求量时,就满足当前的申请。
死锁的检测
可设计两张表格来记录进程使用资源的情况:
等待资源表记录每个被阻塞进程所等待的资源,占用资源表记录每个进程占有的资源;
进程申请资源时,先查询该资源是否为其他进程所占用;若资源空闲,则把该资源分配给申请者且登入占用资源表;否则,登入进程等待资源表。
死锁检测程序定时检测这两张表,若有进程Pi等待资源rk,且rk被进程Pj占用,则Pi和Pj具有“等待占用关系”,记为W(Pi,Pj)。
死锁检测程序反复检测这两张表,可以列出所有的“等待占用关系”,如果出现一组循环等待资源的进程,也就是说出现了死锁。死锁检测程序可用传递闭包算法检测是否有死锁产生。
怎么解除死锁?
可采用重新启动进程执行的方法,恢复工作应包含重启动一个或全部进程,以及从哪一点开始重启动。
全部卷入死锁从头开始启动,但这样的代价相当大。
在进程执行过程中定时设置校验点,从校验点开始重执行。
中止一个卷入死锁的进程,以后重执行。
动态链接库和静态链接库
静态链接库就是在编译链接时直接将需要的执行代码拷贝到调用处,优点就是在程序发布的时候就不需要依赖库,也就是不再需要带着库一起发布,程序可以独立执行,但是体积可能会相对大一些。
动态链接就是在编译的时候不直接拷贝可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到执行的代码时,去共享执行内存中加载动态库可执行代码,最终达到运行时链接的目的。优点是多个程序可以共享同一段代码,而不需要在磁盘上存储多个拷贝,缺点是由于是运行时加载,可能会影响程序的前期执行性能。
锁
读写锁
多个读者可以同时进行读
写者必须互斥(只允许一个写着写,也不能读者写者同时进行)
写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
互斥锁
互斥锁指相互排斥,它是最基本的同步方式。互斥锁用于保护临界区,以保证任何时刻只有一个线程在执行其中的代码、
一次只能一个线程拥有互斥锁,其他线程只有等待
互斥锁是在抢锁失败的情况下主动放弃CPU进入睡眠状态直到锁的状态改变时再唤醒,而操作系统负责线程调度,为了实现锁的状态发生改变时唤醒阻塞的线程或者进程,需要把锁交给操作系统管理,所以互斥锁在加锁操作时涉及上下文的切换。
条件变量
互斥锁用于上锁,条件变量则用于等待。
条件不满足, 阻塞线程
当条件满足, 通知阻塞的线程开始工作
自旋锁
如果进程、线程无法取得锁,进程、线程不会立刻放弃CPU时间片,而是一直循环尝试获取锁,直到获取锁为止。如果进程长时间占有锁,那么自旋就是在浪费CPU做无用功,所以自旋锁一般用于加锁时间很短的场景。
自旋锁有以下特点:
用于临界区互斥
在任何时刻最多只能有一个执行单元获得锁
要求持有锁的处理器所占用的时间尽可能短
等待锁的线程进入忙循环
自旋锁与互斥锁的区别:
自旋锁与互斥锁都是为了实现保护资源共享的机制。
无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。
获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。
悲观锁
悲观锁并不是某一个锁,是一个锁类型,无论是否并发竞争资源,都会锁住资源,并等待资源释放下一个线程才能获取到锁。 这明显很悲观,所以就叫悲观锁。这明显可以归纳为一种策略,只要符合这种策略的锁的具体实现,都是悲观锁的范畴。
悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁
与悲观锁相对的,乐观锁也是一个锁类型。当线程开始竞争资源时,不是立马给资源上锁,而是进行一些前后值比对,以此来操作资源。例如常见的CAS操作,就是典型的乐观锁。
乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。