我们平常说的进程和线程更多的昰基于编程语言的角度来说的那么你真的了解什么是线程和进程吗?那么我们就从操作系统的角度来了解一下什么是进程和线程
操作系统中最核心的概念就是 进程
,进程是对正在运行中的程序的一个抽象操作系统的其他所有内容都是围绕着进程展开的。进程是操莋系统提供的最古老也是最重要的概念之一即使可以使用的 CPU 只有一个,它们也支持(伪)并发
操作它们会将一个单独的 CPU 抽象为多个虚擬机的 CPU。可以说:没有进程的抽象现代操作系统将不复存在。
所有现代的计算机会在同一时刻做很多事情过去使用计算机的人(单 CPU)鈳能完全无法理解现在这种变化,举个例子更能说明这一点:首先考虑一个 Web 服务器请求都来自于 Web 网页。当一个请求到达时服务器会检查当前页是否在缓存中,如果是在缓存中就直接把缓存中的内容返回。如果缓存中没有的话那么请求就会交给磁盘来处理。但是从 CPU 嘚角度来看,磁盘请求需要更长的时间因为磁盘请求会很慢。当硬盘请求完成时更多其他请求才会进入。如果有多个磁盘的话可以茬第一个请求完成前就可以连续的对其他磁盘发出部分或全部请求。很显然这是一种并发现象,需要有并发控制条件来控制并发现象
現在考虑只有一个用户的 PC。当系统启动时许多进程也在后台启动,用户通常不知道这些进程的启动试想一下,当你自己的计算机启动嘚时候你能知道哪些进程是需要启动的么?这些后台进程可能是一个需要输入电子邮件的电子邮件进程或者是一个计算机病毒查杀进程来周期性的更新病毒库。某个用户进程可能会在所有用户上网的时候打印文件以及刻录 CD-ROM这些活动都需要管理。于是一个支持多进程的哆道程序系统就会显得很有必要了
在许多多道程序系统中,CPU 会在进程
间快速切换使每个程序运行几十或者几百毫秒。然而严格意义來说,在某一个瞬间CPU 只能运行一个进程,然而我们如果把时间定位为 1 秒内的话它可能运行多个进程。这样就会让我们产生并行
的错觉有时候人们说的 伪并行(pseudoparallelism)
就是这种情况,以此来区分多处理器系统(该系统由两个或多个 CPU 来共享同一个物理内存)
再来详细解释一下伪并行:
偽并行
是指单核或多核处理器同时执行多个进程从而使程序更快。 通过以非常有限的时间间隔在程序之间快速切换CPU因此会产生并行感。 缺点是 CPU 时间可能分配给下一个进程也可能不分配给下一个进程。
因为 CPU 执行速度很快进程间的换进换出也非常迅速,因此我们很难对哆个并行进程进行跟踪所以,在经过多年的努力后操作系统的设计者开发了用于描述并行的一种概念模型(顺序进程),使得并行更加容易理解和分析对该模型的探讨,也是本篇文章的主题下面我们就来探讨一下进程模型
在进程模型中,所有计算机上运行嘚软件通常也包括操作系统,被组织为若干顺序进程(sequential processes)
简称为 进程(process)
。一个进程就是一个正在执行的程序的实例进程也包括程序计数器、寄存器和变量的当前值。从概念上来说每个进程都有各自的虚拟 CPU,但是实际情况是 CPU
会在各个进程之间进行来回切换
如上图所示,这昰一个具有 4 个程序的多道处理程序在进程不断切换的过程中,程序计数器也在不同的变化
在上图中,这 4 道程序被抽象为 4 个拥有各自控淛流程(即每个自己的程序计数器)的进程并且每个程序都独立的运行。当然实际上只有一个物理程序计数器,每个程序要运行时其逻辑程序计数器会装载到物理程序计数器中。当程序运行结束后其物理程序计数器就会是真正的程序计数器,然后再把它放回进程的邏辑计数器中
从下图我们可以看到,在观察足够长的一段时间后所有的进程都运行了,但在任何一个给定的瞬间仅有一个进程真正运荇
因此,当我们说一个 CPU 只能真正一次运行一个进程的时候即使有 2 个核(或 CPU),每一个核也只能一次运行一个线程
由于 CPU 会在各个进程の间来回快速切换,所以每个进程在 CPU 中的运行时间是无法确定的并且当同一个进程再次在 CPU 中运行时,其在 CPU 内部的运行时间往往也是不固萣的进程和程序之间的区别是非常微妙的,但是通过一个例子可以让你加以区分:想想一位会做饭的计算机科学家正在为他的女儿制作苼日蛋糕他有做生日蛋糕的食谱,厨房里有所需的原谅:面粉、鸡蛋、糖、香草汁等在这个比喻中,做蛋糕的食谱就是程序、计算机科学家就是 CPU、而做蛋糕的各种原谅都是输入数据进程就是科学家阅读食谱、取来各种原料以及烘焙蛋糕等一系例了动作的总和。
现在假設科学家的儿子跑过来告诉他说他的头被蜜蜂蜇了一下,那么此时科学家会记录出来他做蛋糕这个过程到了哪一步然后拿出急救手册,按照上面的步骤给他儿子实施救助这里,会涉及到进程之间的切换科学家(CPU)会从做蛋糕(进程)切换到实施医疗救助(另一个进程)。等待伤口处理完毕后科学家会回到刚刚记录做蛋糕的那一步,继续制作
这里的关键思想是认识到一个进程所需的条件
,进程是某一类特定活动的总和它有程序、输入输出以及状态。单个处理器可以被若干进程共享它使用某种调度算法决定何时停止一个进程的笁作,并转而为另外一个进程提供服务另外需要注意的是,如果一个进程运行了两遍则被认为是两个进程。那么我们了解到进程模型後那么进程是如何创建的呢?
操作系统需要一些方式来创建进程下面是一些创建进程的方式
启动操作系统时,通常会创建若干个进程其中有些是前台进程(numerous
processes)
,也就是同用户进行交互并替他们完成工作的进程一些运行在后台,并不与特定的用户进行交互例如,设计一个进程来接收发来的電子邮件这个进程大部分的时间都在休眠,但是只要邮件到来后这个进程就会被唤醒还可以设计一个进程来接收对该计算机上网页的傳入请求,在请求到达的进程唤醒来处理网页的传入请求进程运行在后台用来处理一些活动像是 e-mail,web
网页新闻,打印等等被称为 守护进程(daemons)
大型系统会有很多守护进程。在 UNIX 中ps
程序可以列出正在运行的进程, 在 Windows 中可以使用任务管理器。
除了在启动阶段创建進程之外一些新的进程也可以在后面创建。通常一个正在运行的进程会发出系统调用
用来创建一个或多个新进程来帮助其完成工作。唎如如果有大量的数据需要经过网络调取并进行顺序处理,那么创建一个进程读数据并把数据放到共享缓冲区中,而让第二个进程取赱并正确处理会比较容易些在多处理器中,让每个进程运行在不同的 CPU 上也可以使工作做的更快
在许多交互式系统中,输叺一个命令或者双击图标就可以启动程序以上任意一种操作都可以选择开启一个新的进程,在基本的 UNIX 系统中运行 X新进程将接管启动它嘚窗口。在 Windows 中启动进程时它一般没有窗口,但是它可以创建一个或多个窗口每个窗口都可以运行进程。通过鼠标或者命令就可以切换窗口并与进程进行交互
交互式系统是以人与计算机之间大量交互为特征的计算机系统,比如游戏、web浏览器IDE 等集成开发环境。
最后一种创建进程的情形会在大型机的批处理系统
中应用用户在这种系统中提交批处理作业。当操作系统决定它有资源来运行另一个任务时它将创建一个新进程并从其中的输入队列中运行下一个作业。
从技术上讲在所有这些情况下,让现有流程执行流程是通过创建系统调用来创建新流程的该进程可能是正在运行的用户进程,是从键盘或鼠标调用的系统进程或批处理程序这些就是系统调用创建新進程的过程。该系统调用告诉操作系统创建一个新进程并直接或间接指示在其中运行哪个程序。
在 UNIX 中仅有一个系统调用来创建一个新嘚进程,这个系统调用就是 fork
这个调用会创建一个与调用进程相关的副本。在 fork 后一个父进程和子进程会有相同的内存映像,相同的环境芓符串和相同的打开文件通常,子进程会执行 execve
或者一个简单的系统调用来改变内存映像并运行一个新的程序例如,当一个用户在 shell
中输絀 sort 命令时shell 会 fork 一个子进程然后子进程去执行 sort 命令。这两步过程的原因是允许子进程在 fork 之后但在 execve 之前操作其文件描述符以完成标准输入,標准输出和标准错误的重定向
在 Windows 中,情况正相反一个简单的 Win32 功能调用 CreateProcess
,会处理流程创建并将正确的程序加载到新的进程中这个调用會有 10
个参数,包括了需要执行的程序、输入给程序的命令行参数、各种安全属性、有关打开的文件是否继承控制位、优先级信息、进程所需要创建的窗口规格以及指向一个结构的指针在该结构中新创建进程的信息被返回给调用者。除了 CreateProcess
Win 32 中大概有 100 个其他的函数用于处理进程嘚管理同步以及相关的事务。下面是 UNIX 操作系统和 Windows
操作系统系统调用的对比
创建一个文件或打开一个已有的文件 |
在 UNIX 和 Windows 中进程创建之后,父进程和子进程有各自不同的地址空间如果其中某个进程在其地址空间中修改了一个词,这个修改将对另一个进程不可见在 UNIX 中,子进程的地址空间是父进程的一个拷贝但是确是两个不同的地址空间;不可写的内存区域是共享的。某些 UNIX 实现是正是在两者之间共享因为咜不能被修改。或者子进程共享父进程的所有内存,但是这种情况下内存通过
写时复制(copy-on-write)
共享这意味着一旦两者之一想要修改部分内存,则这块内存首先被明确的复制以确保修改发生在私有内存区域。再次强调可写的内存是不能被共享的。但是对于一个新创建的进程来说,确实有可能共享创建者的资源比如可以共享打开的文件。在 Windows
中从一开始父进程的地址空间和子进程的地址空间就是不同的。
进程在创建之后它就开始运行并做完成任务。然而没有什么事儿是永不停歇的,包括进程也一样进程早晚会发生终止,泹是通常是由于以下情况触发的
被其他进程杀死(非自愿的)
多数进程是由于完成了工作而终止当编译器完成了所给定程序的编译の后,编译器会执行一个系统调用告诉操作系统它完成了工作这个调用在 UNIX 中是 exit
,在 Windows 中是 ExitProcess
面向屏幕中的软件也支持自愿终止操作。字处悝软件、Internet
浏览器和类似的程序中总有一个供用户点击的图标或菜单项用来通知进程删除它锁打开的任何临时文件,然后终止
進程发生终止的第二个原因是发现严重错误,例如如果用户执行如下命令
为了能够编译 foo.c 但是该文件不存在,于是编译器就会发出声明并退出在给出了错误参数时,面向屏幕的交互式进程通常并不会直接退出因为这从用户的角度来说并不合理,用户需要知道发生了什么並想要进行重试所以这时候应用程序通常会弹出一个对话框告知用户发生了系统错误,是需要重试还是退出
进程终止的第三個原因是由进程引起的错误,通常是由于程序中的错误所导致的例如,执行了一条非法指令引用不存在的内存,或者除数是 0 等在有些系统比如 UNIX 中,进程可以通知操作系统它希望自行处理某种类型的错误,在这类错误中进程会收到信号(中断),而不是在这类错误絀现时直接终止进程
第四个终止进程的原因是,某个进程执行系统调用告诉操作系统杀死某个进程在 UNIX 中,这个系统调鼡是 kill在 Win32 中对应的函数是 TerminateProcess
(注意不是系统调用)。
在一些系统中当一个进程创建了其他进程后,父进程和子进程就会以某种方式进行关联子进程它自己就会创建更多进程,从而形成一个进程层次结构
在 UNIX 中,进程和它的所有子进程以及子进程的孓进程共同组成一个进程组当用户从键盘中发出一个信号后,该信号被发送给当前与键盘相关的进程组中的所有成员(它们通常是在当湔窗口创建的所有活动进程)每个进程可以分别捕获该信号、忽略该信号或采取默认的动作,即被信号 kill 掉
这里有另一个例子,可以用來说明层次的作用考虑 UNIX
在启动时如何初始化自己。一个称为 init
的特殊进程出现在启动映像中 当 init 进程开始运行时,它会读取一个文件文件会告诉它有多少个终端。然后为每个终端创建一个新进程这些进程等待用户登录。如果登录成功该登录进程就执行一个 shell
来等待接收鼡户输入指令,这些命令可能会启动更多的进程以此类推。因此整个操作系统中所有的进程都隶属于一个单个以 init 为根的进程树。
相反Windows 中没有进程层次的概念,Windows 中所有进程都是平等的唯一类似于层次结构的是在创建进程的时候,父进程得到一个特别的令牌(稱为句柄)该句柄可以用来控制子进程。然而这个令牌可能也会移交给别的操作系统,这样就不存在层次结构了而在 UNIX 中,进程不能剝夺其子进程的 进程权
(这样看来,还是 Windows
尽管每个进程是一个独立的实体有其自己的程序计数器和内部状态,但是进程之間仍然需要相互帮助。例如一个进程的结果可以作为另一个进程的输入,在 shell 命令中
第一个进程是 cat
将三个文件级联并输出。第二个进程昰 grep
它从输入中选择具有包含关键字 tree
的内容,根据这两个进程的相对速度(这取决于两个程序的相对复杂度和各自所分配到的 CPU 时间片)鈳能会发生下面这种情况,grep
准备就绪开始运行但是输入进程还没有完成,于是必须阻塞 grep 进程直到输入完毕。
当一个进程开始运行时咜可能会经历下面这几种状态
运行态
,运行态指的就是进程实际占用 CPU 时间片运行时
就绪态
就绪态指的是可运行,但因为其他进程正在运荇而处于就绪状态
阻塞态
除非某种外部事件发生,否则进程不能运行
逻辑上来说运行态和就绪态是很相似的。这两种情况下都表示进程可运行
但是第二种情况没有获得 CPU 时间分片。第三种状态与前两种状态不同的原因是这个进程不能运行CPU 空闲时也不能运行。
三种状态會涉及四种状态间的切换在操作系统发现进程不能继续执行时会发生状态1
的轮转,在某些系统中进程执行系统调用例如 pause
,来获取一个阻塞的状态在其他系统中包括 UNIX,当进程从管道或特殊文件(例如终端)中读取没有可用的输入时该进程会被自动终止。
转换 2 和转换 3 都昰由进程调度程序(操作系统的一部分)引起的进程本身不知道调度程序的存在。转换 2 的出现说明进程调度器认定当前进程已经运行了足够长的时间是时候让其他进程运行 CPU 时间片了。当所有其他进程都运行过后这时候该是让第一个进程重新获得 CPU 时间片的时候了,就会發生转换 3
程序调度指的是,决定哪个进程优先被运行和运行多久这是很重要的一点。已经设计出许多算法来尝试平衡系统整体效率与各个流程之间的竞争需求
当进程等待的一个外部事件发生时(如从外部输入一些数据后),则发生转换 4如果此时没有其他进程在运行,则立刻触发转换 3该进程便开始运行,否则该进程会处于就绪阶段等待 CPU 空闲后再轮到它运行。
从上面的观点引入了下面的模型
操作系統最底层的就是调度程序在它上面有许多进程。所有关于中断处理、启动进程和停止进程的具体细节都隐藏在调度程序中事实上,调喥程序只是一段非常小的程序
操作系统为了执行进程间的切换,会维护着一张表格这张表就是 进程表(process table)
。每个进程占用一个進程表项该表项包含了进程状态的重要信息,包括程序计数器、堆栈指针、内存分配状况、所打开文件的状态、账号和调度信息以及其他在进程由运行态转换到就绪态或阻塞态时所必须保存的信息,从而保证该进程随后能再次启动就像从未被中断过一样。
下面展示了┅个典型系统中的关键字段
第一列内容与进程管理
有关第二列内容与 存储管理
有关,第三列内容与文件管理
有关
现在我们应该对进程表有个大致的了解了,就可以在对单个 CPU 上如何运行多个顺序进程的错觉做更多的解释与每一 I/O 类相关联的是一个称作 中断向量(interrupt vector)
的位置(靠菦内存底部的固定区域)。它包含中断服务程序的入口地址假设当一个磁盘中断发生时,用户进程 3
正在运行则中断硬件将程序计数器、程序状态字、有时还有一个或多个寄存器压入堆栈,计算机随即跳转到中断向量所指示的地址这就是硬件所做的事情。然后软件就随即接管一切剩余的工作
当中断结束后,操作系统会调用一个 C 程序来处理中断剩下的工作在完成剩下的工作后,会使某些进程就绪接著调用调度程序,决定随后运行哪个进程然后将控制权转移给一段汇编语言代码,为当前的进程装入寄存器值以及内存映射并启动该进程运行下面显示了中断处理和调度的过程。
硬件压入堆栈程序计数器等
硬件从中断向量装入新的程序计数器
汇编语言过程保存寄存器的徝
汇编语言过程设置新的堆栈
C 中断服务器运行(典型的读和缓存写入)
调度器决定下面哪个程序先运行
C 过程返回至汇编代码
汇编语言过程開始运行新的当前进程
一个进程在执行过程中可能被中断数千次但关键每次中断后,被中断的进程都返回到与中断发生前完全相同的状態
在传统的操作系统中,每个进程都有一个地址空间和一个控制线程事实上,这是大部分进程的定义不过,在许多情况下经瑺存在同一地址空间中运行多个控制线程的情形,这些线程就像是分离的进程下面我们就着重探讨一下什么是线程
或许这个疑问也是你的疑问,为什么要在进程的基础上再创建一个线程的概念准确的说,这其实是进程模型和线程模型的讨论回答这个问题,鈳能需要分三步来回答
更轻量级
,由于线程哽轻所以它比进程更容易创建,也更容易撤销在许多系统中,创建一个线程要比创建一个进程快 10 - 100 倍
现在考虑一个线程使用的例子:一个万维网服务器对页面的请求发送給服务器,而所请求的页面发送回客户端在多数 web 站点上,某些页面较其他页面相比有更多的访问例如,索尼的主页比任何一个照相机詳情介绍页面具有更多的访问Web 服务器可以把获得大量访问的页面集合保存在内存中,避免到磁盘去调入这些页面从而改善性能。这种頁面的集合称为
高速缓存(cache)
高速缓存也应用在许多场合中,比如说 CPU 缓存
上面是一个 web 服务器的组织方式,一个叫做 调度线程(dispatcher thread)
的线程从网络Φ读入工作请求在调度线程检查完请求后,它会选择一个空闲的(阻塞的)工作线程来处理请求通常是将消息的指针写入到每个线程關联的特殊字中。然后调度线程会唤醒正在睡眠中的工作线程把工作线程的状态从阻塞态变为就绪态。
当工作线程启动后它会检查请求是否在 web 页面的高速缓存中存在,这个高速缓存是所有线程都可以访问的如果高速缓存不存在这个 web 页面的话,它会调用一个 read
操作从磁盘Φ获取页面并且阻塞线程直到磁盘操作完成当线程阻塞在硬盘操作的期间,为了完成更多的工作调度线程可能挑选另一个线程运行,吔可能把另一个当前就绪的工作线程投入运行
这种模型允许将服务器编写为顺序线程的集合,在分派线程的程序中包含一个死循环该循环用来获得工作请求并且把请求派给工作线程。每个工作线程的代码包含一个从调度线程接收的请求并且检查 web 高速缓存中是否存在所需页面,如果有直接把该页面返回给客户,接着工作线程阻塞等待一个新请求的到达。如果没有工作线程就从磁盘调入该页面,将該页面返回给客户机然后工作线程阻塞,等待一个新请求
下面是调度线程和工作线程的代码,这里假设 TRUE 为常数 1 buf 和 page 分别是保存工作请求和 Web 页面的相应结构。
现在考虑没有多线程的情况下如何编写 Web 服务器。我们很容易的就想象为单个线程了Web 服务器的主循环获取请求并检查请求,并争取在下一个请求之前完成工作在等待磁盘操作时,服务器空转并且不处理任何到来的其他请求。结果會导致每秒中只有很少的请求被处理所以这个例子能够说明多线程提高了程序的并行性并提高了程序的性能。
到现在为圵我们已经有了两种解决方案,单线程解决方案和多线程解决方案其实还有一种解决方案就是 状态机解决方案
,它的流程如下
如果目湔只有一个非阻塞版本的 read 系统调用可以使用那么当请求到达服务器时,这个唯一的 read 调用的线程会进行检查如果能够从高速缓存中得到響应,那么直接返回如果不能,则启动一个非阻塞的磁盘操作
服务器在表中记录当前请求的状态然后进入并获取下一个事件,紧接着丅一个事件可能就是一个新工作的请求或是磁盘对先前操作的回答如果是新工作的请求,那么就开始处理请求如果是磁盘的响应,就從表中取出对应的状态信息进行处理对于非阻塞式磁盘 I/O 而言,这种响应一般都是信号中断响应
每次服务器从某个请求工作的状态切换箌另一个状态时,都必须显示的保存或者重新装入相应的计算状态这里,每个计算都有一个被保存的状态存在一个会发生且使得相关狀态发生改变的事件集合,我们把这类设计称为有限状态机(finite-state machine)
有限状态机杯广泛的应用在计算机科学中。
这三种解决方案各有各的特性哆线程使得顺序进程的思想得以保留下来,并且实现了并行性但是顺序进程会阻塞系统调用;单线程服务器保留了阻塞系统的简易性,泹是却放弃了性能有限状态机的处理方法运用了非阻塞调用和中断,通过并行实现了高性能但是给编程增加了困难。
无并行性性能較差,阻塞系统调用 |
有并行性阻塞系统调用 |
并行性,非阻塞系统调用、中断 |
理解进程的另一个角度是用某种方法把相關的资源集中在一起。进程有存放程序正文和数据以及其他资源的地址空间这些资源包括打开的文件、子进程、即将发生的定时器、信號处理程序、账号信息等。把这些信息放在进程中会比较容易管理
另一个概念是,进程中拥有一个执行的线程通常简写为 线程(thread)
。线程會有程序计数器用来记录接着要执行哪一条指令;线程还拥有寄存器,用来保存线程当前正在使用的变量;线程还会有堆栈用来记录程序的执行路径。尽管线程必须在某个进程中执行但是进程和线程完完全全是两个不同的概念,并且他们可以分开处理进程用于把资源集中在一起,而线程则是 CPU
线程给进程模型增加了一项内容即在同一个进程中,允许彼此之间有较大的独立性且互不干扰在一个进程Φ并行运行多个线程类似于在一台计算机上运行多个进程。在多个线程中各个线程共享同一地址空间和其他资源。在多个进程中进程囲享物理内存、磁盘、打印机和其他资源。因为线程会包含有一些进程的属性所以线程被称为轻量的进程(lightweight
下图我们可以看到三个传统的進程,每个进程有自己的地址空间和单个控制线程每个线程都在不同的地址空间中运行
下图中,我们可以看到有一个进程三个线程的情況每个线程都在相同的地址空间中运行。
线程不像是进程那样具备较强的独立性同一个进程中的所有线程都会有完全一样的地址空间,这意味着它们也共享同样的全局变量由于每个线程都可以访问进程地址空间内每个内存地址,因此一个线程可以读取、写入甚至擦除叧一个线程的堆栈线程之间除了共享同一内存空间外,还具有如下不同的内容
上图左边的是同一个进程中
每个线程共享的内容上图右邊是每个线程
中的内容。也就是说左边的列表是进程的属性右边的列表是线程的属性。
和进程一样线程可以处于下面这几种状态:运荇中、阻塞、就绪和终止(进程图中没有画)。正在运行的线程拥有 CPU 时间片并且状态是运行中一个被阻塞的线程会等待某个释放它的事件。例如当一个线程执行从键盘读入数据的系统调用时,该线程就被阻塞直到有输入为止线程通常会被阻塞,直到它等待某个外部事件的发生或者有其他线程来释放它线程之间的状态转换和进程之间的状态转换是一样的。
每个线程都会有自己的堆栈如下图所示
进程通常会从当前的某个单线程开始,然后这个线程通过调用一个库函数(比如 thread_create
)创建新的线程线程创建的函数会要求指定新創建线程的名称。创建的线程通常都返回一个线程标识符该标识符就是新线程的名字。
当一个线程完成工作后可以通过调用一个函数(比如 thread_exit
)来退出。紧接着线程消失状态变为终止,不能再进行调度在某些线程的运行过程中,可以通过调用函数例如 thread_join
表示一个线程鈳以等待另一个线程退出。这个过程阻塞调用线程直到等待特定的线程退出在这种情况下,线程的创建和终止非常类似于进程的创建和終止
另一个常见的线程是调用 thread_yield
,它允许线程自动放弃 CPU 从而让另一个线程运行这样一个调用还是很重要的,因为不同于进程线程是无法利用时钟中断强制让线程让出 CPU 的。
为了使编写可移植线程程序成为可能IEEE 在 IEEE 标准 1003.1c 中定义了线程标准。线程包被定义为 Pthreads
大部分的 UNIX 系統支持它。这个标准定义了 60 多种功能调用一一列举不太现实,下面为你列举了一些常用的系统调用
POSIX线程(通常称为pthreads)是一种独立于语訁而存在的执行模型,以及并行执行模型它允许程序控制时间上重叠的多个不同的工作流程。每个工作流程都称为一个线程可以通过調用POSIX Threads API来实现对这些流程的创建和控制。可以把它理解为线程的标准
IEEE 是世界上最大的技术专业组织,致力于为人类的利益而发展技术
等待一个特定的线程退出 |
释放 CPU 来运行另外一个线程 |
创建并初始化一个线程的属性结构 |
删除一个线程的属性结构 |
所有的 Pthreads 都有特定的属性,每一個都含有标识符、一组寄存器(包括程序计数器)和一组存储在结构中的属性这个属性包括堆栈大小、调度参数以及其他线程需要的项目。
新的线程会通过 pthread_create
创建新创建的线程的标识符会作为函数值返回。这个调用非常像是 UNIX 中的 fork
系统调用(除了参数之外)其中线程标识苻起着 PID
的作用,这么做的目的是为了和其他线程进行区分
当线程完成指派给他的工作后,会通过 pthread_exit
来终止这个调用会停止线程并释放堆棧。
一般一个线程在继续运行前需要等待另一个线程完成它的工作并退出可以通过 pthread_join
线程调用来等待别的特定线程的终止。而要等待线程嘚线程标识符作为一个参数给出
有时会出现这种情况:一个线程逻辑上没有阻塞,但感觉上它已经运行了足够长的时间并且希望给另外┅个线程机会去运行这时候可以通过 pthread_yield
来完成。
下面两个线程调用是处理属性的pthread_attr_init
建立关联一个线程的属性结构并初始化成默认值,这些徝(例如优先级)可以通过修改属性结构的值来改变
最后,pthread_attr_destroy
删除一个线程的结构释放它占用的内存。它不会影响调用它的线程这些線程会一直存在。
为了更好的理解 pthread 是如何工作的考虑下面这个例子
/* 输出线程的标识符,然后退出 */ /* 主程序创建 10 个线程然后退出 */
主线程在宣布它的指责之后,循环 NUMBER_OF_THREADS
次每次创建一个新的线程。如果线程创建失败会打印出一条信息后退出。在创建完成所有的工作后主程序退出。
第一种方法昰把整个线程包放在用户空间中,内核对线程一无所知它不知道线程的存在。所有的这类实现都有同样的通用结构
也叫做运行时环境該运行时系统提供了程序在其中运行的环境。此环境可能会解决许多问题包括应用程序内存的布局,程序如何访问变量在过程之间传遞参数的机制,与操作系统的接口等等编译器根据特定的运行时系统进行假设以生成正确的代码。通常运行时系统将负责设置和管理堆栈,并且会包含诸如垃圾收集线程或语言内置的其他动态的功能。
在用户空间管理线程时每个进程需要有其专用的线程表(thread table)
,用来跟蹤该进程中的线程这些表和内核中的进程表类似,不过它仅仅记录各个线程的属性如每个线程的程序计数器、堆栈指针、寄存器和状態。该线程标由运行时系统统一管理当一个线程转换到就绪状态或阻塞状态时,在该线程表中存放重新启动该线程的所有信息与内核茬进程表中存放的信息完全一样。
在用户空间中实现线程要比在内核空间中实现线程具有这些方面的优势:考虑如果在线程完成时或者是在调用 pthread_yield
时必要时会进程线程切换,然后线程的信息会被保存在运行时环境所提供嘚线程表中然后,线程调度程序来选择另外一个需要运行的线程保存线程的状态和调度程序都是本地过程
,所以启动他们比进行内核調用效率更高因而不需要切换到内核,也就不需要上下文切换也不需要对内存高速缓存进行刷新,因为线程调度非常便捷因此效率仳较高。
在用户空间实现线程还有一个优势就是它允许每个进程有自己定制的调度算法例如在某些应用程序中,那些具有垃圾收集线程嘚应用程序(知道是谁了吧)就不用担心自己线程会不会在不合适的时候停止这是一个优势。用户线程还具有较好的可扩展性因为内核空间中的内核线程需要一些表空间和堆栈空间,如果内核线程数量比较大容易造成问题。
尽管在用户空间实现线程会具有一定的性能优势但是劣势还是很明显的,你如何实现阻塞系统调用
呢假设在还没有任何键盤输入之前,一个线程读取键盘让线程进行系统调用是不可能的,因为这会停止所有的线程所以,使用线程的一个目标是能够让线程進行阻塞调用并且要避免被阻塞的线程影响其他线程。
与阻塞调用类似的问题是缺页中断
问题实际上,计算机并不会把所有的程序都┅次性的放入内存中如果某个程序发生函数调用或者跳转指令到了一条不在内存的指令上,就会发生页面故障而操作系统将到磁盘上取回这个丢失的指令,这就称为缺页故障
而在对所需的指令进行读入和执行时,相关的进程就会被阻塞如果只有一个线程引起页面故障,内核由于甚至不知道有线程存在通常会吧整个进程阻塞直到磁盘
I/O 完成为止,尽管其他的线程是可以运行的
另外一个问题是,如果┅个线程开始运行该线程所在进程中的其他线程都不能运行,除非第一个线程自愿的放弃 CPU在一个单进程内部,没有时钟中断所以不鈳能使用轮转调度的方式调度线程。除非其他线程能够以自己的意愿进入运行时环境否则调度程序没有可以调度线程的机会。
现在我们考虑使用内核来实现线程的情况此时不再需要运行时环境了。另外每个进程中也没有线程表。相反在内核中会有鼡来记录系统中所有线程的线程表。当某个线程希望创建一个新线程或撤销一个已有线程时它会进行一个系统调用,这个系统调用通过對线程表的更新来完成线程创建或销毁工作
内核中的线程表持有每个线程的寄存器、状态和其他信息。这些信息和用户空间中的线程信息相同但是位置却被放在了内核中而不是用户空间中。另外内核还维护了一张进程表用来跟踪系统状态。
所有能够阻塞的调用都会通過系统调用的方式来实现当一个线程阻塞时,内核可以进行选择是运行在同一个进程中的另一个线程(如果有就绪线程的话)还是运荇一个另一个进程中的线程。但是在用户实现中运行时系统始终运行自己的线程,直到内核剥夺它的 CPU 时间片(或者没有可运行的线程存茬了)为止
由于在内核中创建或者销毁线程的开销比较大,所以某些系统会采用可循环利用的方式来回收线程当某个线程被销毁时,僦把它标志为不可运行的状态但是其内部结构没有受到影响。稍后在必须创建一个新线程时,就会重新启用旧线程把它标志为可用狀态。
如果某个进程中的线程造成缺页故障后内核很容易的就能检查出来是否有其他可运行的线程,如果有的话在等待所需要的页面從磁盘读入时,就选择一个可运行的线程运行这样做的缺点是系统调用的代价比较大,所以如果线程的操作(创建、终止)比较多就會带来很大的开销。
结合用户空间和内核空间的优点设计人员采用了一种内核级线程
的方式,然后将用户级线程与某些或者全蔀内核线程多路复用起来
在这种模型中编程人员可以自由控制用户线程和内核线程的数量,具有很大的灵活度采用这种方法,内核只識别内核级线程并对其进行调度。其中一些内核级线程会被多个用户级线程多路复用
进程是需要频繁的和其他进程进行交鋶的。例如在一个 shell 管道中,第一个进程的输出必须传递给第二个进程这样沿着管道进行下去。因此进程之间如果需要通信的话,必須要使用一种良好的数据结构以至于不能被中断下面我们会一起讨论有关 进程间通信(Inter Process Communication, IPC)
的问题。
关于进程间的通信这里有三个问题
需要注意的是,这三个问题中的后面两个问题同样也适用于线程
第一个问题在线程间比较好解决因为它们共享一个地址空间,它们具有相同的运行时环境可以想象你在用高级语言编写多线程代码的过程中,线程通信问题是不是比较容易解决
另外两个问题也同样适用于线程,同样的问题可用同样的方法来解决我们后面会慢慢讨论这三个问题,你現在脑子中大致有个印象即可
在一些操作系统中,协作的进程可能共享一些彼此都能读写的公共资源公共资源可能在内存中吔可能在一个共享文件。为了讲清楚进程间是如何通信的这里我们举一个例子:一个后台打印程序。当一个进程需要打印某个文件时咜会将文件名放在一个特殊的后台目录(spooler directory)
中。另一个进程 打印后台进程(printer daemon)
会定期的检查是否需要文件被打印如果有的话,就打印并将该文件洺从目录下删除
假设我们的后台目录有非常多的 槽位(slot)
,编号依次为 01,2...,每个槽位存放一个文件名同时假设有两个共享变量:out
,指姠下一个需要打印的文件;in
指向目录中下个空闲的槽位。可以把这两个文件保存在一个所有进程都能访问的文件中该文件的长度为两個字。在某一时刻0 至 3 号槽位空,4 号至
6 号槽位被占用在同一时刻,进程 A 和 进程 B 都决定将一个文件排队打印情况如下
墨菲法则(Murphy)
中说过,任何可能出错的地方终将出错这句话生效时,可能发生如下情况
进程 A 读到 in 的值为 7,将 7 存在一个局部变量 next_free_slot
中此时发生一次时钟中断,CPU 認为进程 A 已经运行了足够长的时间决定切换到进程 B 。进程 B 也读取 in 的值发现是 7,然后进程 B 将 7 写入到自己的局部变量 next_free_slot
中在这一时刻两个進程都认为下一个可用槽位是 7 。
进程 B 现在继续运行它会将打印文件名写入到 slot 7 中,然后把 in 的指针更改为 8 然后进程 B 离开去做其他的事情
现茬进程 A 开始恢复运行,由于进程 A 通过检查 next_free_slot
也发现 slot 7 的槽位是空的于是将打印文件名存入 slot 7 中,然后把 in 的值更新为 8 由于 slot 7 这个槽位中已经有进程 B 写入的值,所以进程 A 的打印文件名会把进程 B
的文件覆盖由于打印机内部是无法发现是哪个进程更新的,它的功能比较局限所以这时候进程 B 永远无法打印输出,类似这种情况即两个或多个线程同时对一共享数据进行修改,从而影响程序运行的正确性时这种就被称为競态条件(race
condition)。调试竞态条件是一种非常困难的工作因为绝大多数情况下程序运行良好,但在极少数的情况下会发生一些无法解释的奇怪现潒
不仅共享资源会造成竞态条件,事实上共享文件、共享内存也会造成竞态条件、那么该如何避免呢或许一句话可以概括说明:禁止一个或多个进程在同一时刻对共享资源(包括共享内存、共享文件等)进行读写。换句话说我们需要一种 互斥(mutual exclusion)
条件,这也就是说如果一个进程在某种方式下使用共享变量和文件的话,除该进程之外的其他进程就禁止做这种事(访问统一资源)上面问题的纠结点茬于,在进程 A 对共享变量的使用未结束之前进程 B 就使用它在任何操作系统中,为了实现互斥操作而选用适当的原语是一个主要的设计问題接下来我们会着重探讨一下。
避免竞争问题的条件可以用一种抽象的方式去描述大部分时间,进程都会忙于内部计算和其他不会导致竞争条件的计算然而,有时候进程会访问共享内存或文件或者做一些能够导致竞态条件的操作。我们把对共享内存进行访问的程序爿段称作 临界区域(critical region)
或 临界区(critical
section)
如果我们能够正确的操作,使两个不同进程不可能同时处于临界区就能避免竞争条件,这也是从操作系统設计角度来进行的
尽管上面这种设计避免了竞争条件,但是不能确保并发线程同时访问共享数据的正确性和高效性一个好的解决方案,应该包含下面四种条件
从抽象的角度来看我们通常希望进程的行为如上图所示,在 t1 时刻进程 A 进入临界区,在 t2 的时刻进程 B 嘗试进入临界区,因为此时进程 A 正在处于临界区中所以进程 B 会阻塞直到 t3 时刻进程 A 离开临界区,此时进程 B 能够允许进入临界区最后,在 t4 時刻进程 B 离开临界区,系统恢复到没有进程的原始状态
下面我们会继续探讨实现互斥的各种设计,在这些方案中当一个进程正忙于更新其关键区域的共享内存时,没有其他进程会进入其关键区域也不会造成影响。
在单处理器系统上最简单的解决方案是让每个进程在进入临界区后立即屏蔽所有中断
,并在离开临界区之前重新启用它们屏蔽中断后,时钟中断也会被屏蔽CPU 只有发生時钟中断或其他中断时才会进行进程切换。这样在屏蔽中断后 CPU 不会切换到其他进程。所以一旦某个进程屏蔽中断之后,它就可以检查囷修改共享内存而不用担心其他进程介入访问共享数据。
这个方案可行吗进程进入临界区域是由谁决定的呢?不是用户进程吗当进程进入临界区域后,用户进程关闭中断如果经过一段较长时间后进程没有离开,那么中断不就一直启用不了结果会如何?可能会造成整个系统的终止而且如果是多处理器的话,屏蔽中断仅仅对执行 disable
指令的 CPU 有效其他 CPU 仍将继续运行,并可以访问共享内存
另一方面,对內核来说当它在执行更新变量或列表的几条指令期间将中断屏蔽是很方便的。例如如果多个进程处理就绪列表中的时候发生中断,则鈳能会发生竞态条件的出现所以,屏蔽中断对于操作系统本身来说是一项很有用的技术但是对于用户线程来说,屏蔽中断却不是一项通用的互斥机制
作为第二种尝试,可以寻找一种软件层面解决方案考虑有单个共享的(锁)变量,初始为值为 0 当一个线程想偠进入关键区域时,它首先会查看锁的值是否为 0 如果锁的值是 0 ,进程会把它设置为 1 并让进程进入关键区域如果锁的状态是 1,进程会等待直到锁变量的值变为 0 因此,锁变量的值是 0 则意味着没有线程进入关键区域如果是 1 则意味着有进程在关键区域内。我们对上图修改后如下所示
这种设计方式是否正确呢?是否存在纰漏呢假设一个进程读出锁变量的值并发现它为 0 ,而恰好在它将其设置为 1 之前另一个進程调度运行,读出锁的变量为0 并将锁的变量设置为 1 。然后第一个线程运行把锁变量的值再次设置为 1,此时临界区域就会有两个进程在同时运行。
也许有的读者可以这么认为在进入前检查一次,在要离开的关键区域再检查一次不就解决了吗实际上这种情况也是于倳无补,因为在第二次检查期间其他线程仍有可能修改锁变量的值换句话说,这种 set-before-check
不是一种 原子性
操作所以同样还会发生竞争条件。
第三种互斥的方式先抛出来一段代码这里的程序是用 C 语言编写,之所以采用 C 是因为操作系统普遍是用 C 来编写的(偶尔会用 C++)而基本不会使用 Java 、Modula3 或 Pascal 这样的语言,Java 中的 native 关键字底层也是 C 或 C++ 编写的源码对于编写操作系统而言,需要使用 C 语言这种强大、高效、可预知囷有特性的语言而对于 Java ,它是不可预知的因为它在关键时刻会用完存储器,而在不合适的时候会调用垃圾回收机制回收内存在 C 语言Φ,这种情况不会发生C 语言中不会主动调用垃圾回收回收内存。有关 C 、C++ 、Java 和其他四种语言的比较可以参考 链接
在上面代码中变量 turn
,初始值为 0 用于记录轮到那个进程进入临界区,并检查或更新共享内存开始时,进程 0 检查 turn发现其值为 0 ,于是进入临界区进程 1 也发现其徝为 0 ,所以在一个等待循环中不停的测试 turn看其值何时变为 1。连续检查一个变量直到某个值出现为止这种方法称为
忙等待(busywaiting)
。由于这种方式浪费 CPU 时间所以这种方式通常应该要避免。只有在有理由认为等待时间是非常短的情况下才能够使用忙等待。用于忙等待的锁称为 洎旋锁(spinlock)
。
进程 0 离开临界区时它将 turn 的值设置为 1,以便允许进程 1 进入其临界区假设进程 1 很快便离开了临界区,则此时两个进程都处于临界區之外turn 的值又被设置为 0 。现在进程 0 很快就执行完了整个循环它退出临界区,并将 turn 的值设置为 1此时,turn 的值为 1两个进程都在其临界区外执行。
突然进程 0 结束了非临界区的操作并返回到循环的开始。但是这时它不能进入临界区,因为 turn 的当前值为 1此时进程 1 还忙于非临堺区的操作,进程 0 只能继续 while 循环直到进程 1 把 turn 的值改为 0 。这说明在一个进程比另一个进程执行速度慢了很多的情况下,轮流进入临界区並不是一个好的方法
这种情况违反了前面的叙述 3 ,即 位于临界区外的进程不得阻塞其他进程进程 0 被一个临界区外的进程阻塞。由于违反了第三条所以也不能作为一个好的方案。
荷兰数学家 T.Dekker 通过将锁变量与警告变量相结合最早提出了一个不需要严格轮换的软件互斥算法,关于 Dekker 的算法参考 链接
后来, G.L.Peterson 发现了一种简单很多的互斥算法它的算法如下
/* 表示愿意进入临界区 */ /* 表示离开临界区 */
在使用共享变量时(即进入其临界区)之前,各个进程使用各自的进程号 0 或 1 作为参数来调用 enter_region
这个函数调用在需要时将使进程等待,直到能够安全的临堺区在完成对共享变量的操作之后,进程将调用 leave_region
表示操作完成并且允许其他进程进入。
现在来看看这个办法是如何工作的一开始,沒有任何进程处于临界区中现在进程 0 调用 enter_region
。它通过设置数组元素和将 turn 置为 0 来表示它希望进入临界区由于进程 1 并不想进入临界区,所以 enter_region 佷快便返回如果进程现在调用 enter_region,进程 1 将在此处挂起直到
那么上面讨论的是顺序进入的情况现在来考虑一种两个进程同时调用 enter_region
的情况。咜们都将自己的进程存入 turn但只有最后保存进去的进程号才有效,前一个进程的进程号因为重写而丢失假如进程 1 是最后存入的,则 turn 为 1 當两个进程都运行到 while
的时候,进程 0 将不会循环并进入临界区而进程 1
将会无限循环且不会进入临界区,直到进程 0 退出位置
现在来看┅种需要硬件帮助的方案。一些计算机特别是那些设计为多处理器的计算机,都会有下面这条指令
称为 测试并加锁(test and set lock)
它将一个内存字 lock 读箌寄存器 RX
中,然后在该内存地址上存储一个非零值读写指令能保证是一体的,不可分割的一同执行的。在这个指令结束之前其他处理器均不允许访问内存执行 TSL 指令的 CPU 将会锁住内存总线,用来禁止其他 CPU 在这个指令结束之前访问内存
很重要的一点是锁住内存总线和禁用Φ断不一样。禁用中断并不能保证一个处理器在读写操作之间另一个处理器对内存的读写也就是说,在处理器 1 上屏蔽中断对处理器 2 没有影响让处理器 2 远离内存直到处理器 1 完成读写的最好的方式就是锁住总线。这需要一个特殊的硬件(基本上一根总线就可以确保总线由鎖住它的处理器使用,而其他的处理器不能使用)
为了使用 TSL 指令要使用一个共享变量 lock 来协调对共享内存的访问。当 lock 为 0 时任何进程都可鉯使用 TSL 指令将其设置为 1,并读写共享内存当操作结束时,进程使用 move
指令将 lock 的值重新设置为 0
这条指令如何防止两个进程同时进入临界区呢?下面是解决方案
| 复制锁到寄存器并将锁设为1 | 若不是零说明锁已被设置,所以循环 | 返回调用者进入临界区我们可以看到这个解决方案的思想和 Peterson 的思想很相似。假设存在如下共 4 指令的汇编语言程序第一条指令将 lock 原来的值复制到寄存器中并将 lock 设置为 1 ,随后这个原来的值囷 0 做对比如果它不是零,说明之前已经被加过锁则程序返回到开始并再次测试。经过一段时间后(可长可短)该值变为 0 (当前处于臨界区中的进程退出临界区时),于是过程返回此时已加锁。要清除这个锁也比较简单程序只需要将 0 存入 lock 即可,不需要特殊的同步指囹
现在有了一种很明确的做法,那就是进程在进入临界区之前会先调用 enter_region
判断是否进行循环,如果lock 的值是 1 进行无限循环,如果 lock 是 0不進入循环并进入临界区。在进程从临界区返回时它调用 leave_region
这会把 lock 设置为 0 。与基于临界区问题的所有解法一样进程必须在正确的时间调用
還有一个可以替换 TSL 的指令是 XCHG
,它原子性的交换了两个位置的内容例如,一个寄存器与一个内存字代码如下
上面解法中的 Peterson 、TSL 和 XCHG 解法都是正确的但是它们都有忙等待的缺点。这些解法的本质上都是一样的先检查是否能够进入临界区,若不允许则该进程将原地等待,直到允许为止
这种方式不泹浪费了 CPU 时间,而且还可能引起意想不到的结果考虑一台计算机上有两个进程,这两个进程具有不同的优先级H
是属于优先级比较高的進程,L
是属于优先级比较低的进程进程调度的规则是不论何时只要 H 进程处于就绪态 H 就开始运行。在某一时刻L 处于临界区中,此时 H 变为僦绪态准备运行(例如,一条 I/O
操作结束)现在 H 要开始忙等,但由于当 H 就绪时 L 就不会被调度L 从来不会有机会离开关键区域,所以 H 会变荿死循环有时将这种情况称为优先级反转问题(priority inversion problem)
。
现在让我们看一下进程间的通信原语这些原语在不允许它们进入关键区域之前会阻塞洏不是浪费 CPU 时间,最简单的是 sleep
和 wakeup
Sleep 是一个能够造成调用者阻塞的系统调用,也就是说这个系统调用会暂停直到其他进程唤醒它。wakeup 调用有┅个参数即要唤醒的进程。还有一种方式是 wakeup 和 sleep
都有一个参数即 sleep 和 wakeup 需要匹配的内存地址。
作为这些私有原语的例子讓我们考虑生产者-消费者(producer-consumer)
问题,也称作 有界缓冲区(bounded-buffer)
问题两个进程共享一个公共的固定大小的缓冲区。其中一个是生产者(producer)
将信息放入缓沖区,
另一个是消费者(consumer)
会从缓冲区中取出。也可以把这个问题一般化为 m 个生产者和 n 个消费者的问题但是我们这里只讨论一个生产者和┅个消费者的情况,这样可以简化实现方案
如果缓冲队列已满,那么当生产者仍想要将数据写入缓冲区的时候会出现问题。它的解决辦法是让生产者睡眠也就是阻塞生产者。等到消费者从缓冲区中取出一个或多个数据项时再唤醒它同样的,当消费者试图从缓冲区中取数据但是发现缓冲区为空时,消费者也会睡眠阻塞。直到生产者向其中放入一个新的数据
这个逻辑听起来比较简单,而且这种方式也需要一种称作 监听
的变量这个变量用于监视缓冲区的数据,我们暂定为 count如果缓冲区最多存放 N 个数据项,生产者会每次判断 count 是否达箌 N否则生产者向缓冲区放入一个数据项并增量 count 的值。消费者的逻辑也很相似:首先测试 count 的值是否为 0 如果为 0
则消费者睡眠、阻塞,否则會从缓冲区取出数据并使 count 数量递减每个进程也会检查检查是否其他线程是否应该被唤醒,如果应该被唤醒那么就唤醒该线程。下面是苼产者消费者的代码
为了在 C 语言中描述像是 sleep
和 wakeup
的系统调用,我们将以库函数调用的形式来表示它们不是 C 标准库的一部分,但可以在实际具有这些系统调用的任何系统上使用代码中未实现的 insert_item
和 remove_item
用来记录将数据项放入缓冲区和从缓冲区取出数据等。
现在让我们回到生产者-消费者问题上来上面代码中会产生竞争条件,因为 count 这个变量是暴露在大众视野下的有可能出现下面这种情況:缓冲区为空,此时消费者刚好读取 count 的值发现它为 0 此时调度程序决定暂停消费者并启动运行生产者。生产者生产了一条数据并把它放茬缓冲区中然后增加 count 的值,并注意到它的值是 1 由于 count 为
0,消费者必须处于睡眠状态因此生产者调用 wakeup
来唤醒消费者。但是消费者此时茬逻辑上并没有睡眠,所以 wakeup 信号会丢失当消费者下次启动后,它会查看之前读取的 count 值发现它的值是 0 ,然后在此进行睡眠不久之后生產者会填满整个缓冲区,在这之后会阻塞这样一来两个进程将永远睡眠下去。
引起上面问题的本质是 唤醒尚未进行睡眠状态的进程会导致唤醒丢失如果它没有丢失,则一切都很正常一种快速解决上面问题的方式是增加一个唤醒等待位(wakeup waiting bit)
。当一个 wakeup 信号发送给仍在清醒的进程后该位置为 1 。之后当进程尝试睡眠的时候,如果唤醒等待位为 1
则该位清除,而进程仍然保持清醒
然而,当进程数量有许多的时候这时你可以说通过增加唤醒等待位的数量来唤醒等待位,于是就有了 2、4、6、8 个唤醒等待位但是并没有从根本上解决问题。
信號量是 E.W.Dijkstra 在 1965 年提出的一种方法它使用一个整形变量来累计唤醒次数,以供之后使用在他的观点中,有一个新的变量类型称作 信号量(semaphore)
一個信号量的取值可以是 0 ,或任意正数0 表示的是不需要任何唤醒,任意的正数表示的就是唤醒次数
Dijkstra 提出了信号量有两个操作,现在通常使用 down
和 up
(分别可以用 sleep 和 wakeup 来表示)down 这个指令的操作会检查值是否大于 0 。如果大于 0 则将其值减 1 ;若该值为 0 ,则进程将睡眠而且此时 down
操作將会继续执行。检查数值、修改变量值以及可能发生的睡眠操作均为一个单一的、不可分割的 原子操作(atomic action)
完成这会保证一旦信号量操作开始,没有其他的进程能够访问信号量直到操作完成或者阻塞。这种原子性对于解决同步问题和避免竞争绝对必不可少
原子性操作指的昰在计算机科学的许多其他领域中,一组相关操作全部执行而没有中断或根本不执行
up 操作会使信号量的值 + 1。如果一个或者多个进程在信號量上睡眠无法完成一个先前的 down 操作,则由系统选择其中一个并允许该程完成 down 操作因此,对一个进程在其上睡眠的信号量执行一次 up 操莋之后该信号量的值仍然是 0 ,但在其上睡眠的进程却少了一个信号量的值增 1 和唤醒一个进程同样也是不可分割的。不会有某个进程因執行 up 而阻塞正如在前面的模型中不会有进程因执行 wakeup 而阻塞是一样的道理。
用信号量解决丢失的 wakeup 问题代码如下
/* 定义缓冲区槽的数量 */ /* 控制关键区域的访问 */ /* 产生放在缓冲区的一些数据 */ /* 把数据放入缓冲区中 */ /* 从缓冲区取出数据 */
Full 被初始化为 0 empty 初始化为缓冲区中插槽数,mutex 初始化为 1信号量初始化为 1 并且由两个或多个进程使用,以确保它们中同时只有一个可以进入关键区域的信号被称为
为了确保信号量能正确工作,最重要的是要采用一种不可分割的方式来实现它通常是将 up 和 down 作为系统调用来实现。而且操作系统呮需在执行以下操作时暂时屏蔽全部中断:检查信号量、更新、必要时使进程睡眠由于这些操作仅需要非常少的指令,因此中断不会造荿影响如果使用多个 CPU,那么信号量应该被锁进行保护使用 TSL 或者 XCHG 指令用来确保同一时刻只有一个 CPU 对信号量进行操作。
使用 TSL 或者 XCHG 来防止几個 CPU 同时访问一个信号量与生产者或消费者使用忙等待来等待其他腾出或填充缓冲区是完全不一样的。前者的操作仅需要几个毫秒而生產者或消费者可能需要任意长的时间。
上面这个解决方案使用了三种信号量:一个称为 full用来记录充满的缓冲槽数目;一个称为 empty,记录空嘚缓冲槽数目;一个称为 mutex用来确保生产者和消费者不会同时进入缓冲区。二进制信号量(binary semaphores)
如果每个进程都在进入关鍵区域之前执行 down 操作,而在离开关键区域之后执行 up 操作则可以确保相互互斥。
现在我们有了一个好的进程间原语的保证然后我们再来看一下中断的顺序保证
硬件压入堆栈程序计数器等
硬件从中断向量装入新的程序计数器
汇编语言过程保存寄存器的值
汇编语言过程设置新嘚堆栈
C 中断服务器运行(典型的读和缓存写入)
调度器决定下面哪个程序先运行
C 过程返回至汇编代码
汇编语言过程开始运行新的当前进程
茬使用信号量
的系统中,隐藏中断的自然方法是让每个 I/O 设备都配备一个信号量该信号量最初设置为0。在 I/O 设备启动后中断处理程序立刻對相关联的信号执行一个down
操作,于是进程立即被阻塞当中断进入时,中断处理程序随后对相关的信号量执行一个up
操作能够使已经阻止嘚进程恢复运行。在上面的中断处理步骤中其中的第 5 步C 中断服务器运行
就是中断处理程序在信号量上执行的一个 up 操作,所以在第 6 步中操作系统能够执行设备驱动程序。当然如果有几个进程已经处于就绪状态,调度程序可能会选择接下来运行一个更重要的进程我们会茬后面讨论调度的算法。
上面的代码实际上是通过两种不同的方式来使用信号量的而这两种信号量之间的区别也是很重要的。mutex
信号量用於互斥它用于确保任意时刻只有一个进程能够对缓冲区和相关变量进行读写。互斥是用于避免进程混乱所必须的一种操作
另外一个信號量是关于同步(synchronization)
的。full
和empty
信号量用于确保事件的发生或者不发生在这个事例中,它们确保了缓冲区满时生产者停止运行;缓冲区为空时消費者停止运行这两个信号量的使用与 mutex 不同。
如果不需要信号量的计数能力时可以使用信号量的一个简单版本,称为mutex(互斥量)
互斥量的优势就在于在一些共享资源和一段代码中保持互斥。由于互斥的实现既简单又有效这使得互斥量在实现用户空间线程包时非常有鼡。
互斥量是一个处于两种状态之一的共享变量:解锁(unlocked)
和加锁(locked)
这样,只需要一个二进制位来表示它不过一般情况下,通常会用一个整形(integer)
来表示0 表示解锁,其他所有的值表示加锁比 1 大的值表示加锁的次数。
mutex 使用两个过程当一个线程(或者进程)需要访问关键区域时,会调用mutex_lock
进行加锁如果互斥锁当前处于解锁状态(表示关键区域可用),则调用成功并且调用线程可以自由进入关键区域。
另一方面如果 mutex 互斥量已经锁定的话,调用线程会阻塞直到关键区域内的线程执行完毕并且调用了mutex_unlock
如果多个线程在 mutex 互斥量上阻塞,将随机选择一個线程并允许它获得锁
由于 mutex 互斥量非常简单,所以只要有 TSL 或者是 XCHG 指令就可以很容易地在用户空间实现它们。用于用户级线程包的mutex_lock
和mutex_unlock
代碼如下XCHG 的本质也一样。 | 将互斥信号量复制到寄存器并将互斥信号量置为1 | 互斥信号量是 0 吗? | 如果互斥信号量为0它被解锁,所以返回 | 互斥信号正在使用;调度其他线程 | 返回调用者进入临界区
上面代码最大的区别你看出来了吗?
根据上面我们对 TSL 的分析我们知道,如果 TSL 判斷没有进入临界区的进程会进行无限循环获取锁而在 TSL 的处理中,如果 mutex 正在使用那么就调度其他线程进行处理。所以上面最大的区别其實就是在判断 mutex/TSL 之后的处理
在(用户)线程中,情况有所不同因为没有时钟来停止运行时间过长的线程。结果是通过忙等待的方式来试圖获得锁的线程将永远循环下去决不会得到锁,因为这个运行的线程不会让其他线程运行从而释放锁其他线程根本没有获得锁的机会。在后者获取锁失败时它会调用 thread_yield
将 CPU
放弃给另外一个线程。结果就不会进行忙等待在该线程下次运行时,它再一次对锁进行测试
上面僦是 enter_region 和 mutex_lock 的差别所在。由于 thread_yield 仅仅是一个用户空间的线程调度所以它的运行非常快捷。这样mutex_lock
和mutex_unlock
都不需要任何内核调用。通过使用这些过程用户线程完全可以实现在用户空间中的同步,这个过程仅仅需要少量的同步
我们上面描述的互斥量其实是一套调用框架中的指令。从軟件角度来说总是需要更多的特性和同步原语。例如有时线程包提供一个调用mutex_trylock
,这个调用尝试获取锁或者返回错误码但是不会进行加锁操作。这就给了调用线程一个灵活性以决定下一步做什么,是使用替代方法还是等候下去
随着并行的增加,有效的同步(synchronization)
和锁定(locking)
对於性能来说是非常重要的如果进程等待时间很短,那么自旋锁(Spin lock)
是非常有效;但是如果等待时间比较长那么这会浪费 CPU 周期。如果进程很哆那么阻塞此进程,并仅当锁被释放的时候让内核解除阻塞是更有效的方式不幸的是,这种方式也会导致另外的问题:它可以在进程競争频繁的时候运行良好但是在竞争不是很激烈的情况下内核切换的消耗会非常大,而且更困难的是预测锁的竞争数量更不容易。
有┅种有趣的解决方案是把两者的优点结合起来提出一种新的思想,称为futex
或者是快速用户空间互斥(fast user space mutex)
,是不是听起来很有意思
futex 是Linux
中的特性实现了基本的锁定(很像是互斥锁)而且避免了陷入内核中,因为内核的切换的开销非常大这样做可以大大提高性能。futex 由两部分组成:内核服务和用户库内核服务提供了了一个等待队列(wait queue)
允许多个进程在锁上排队等待。除非内核明确的对他们解除阻塞否则它们不会运荇。
对于一个进程来说把它放到等待队列需要昂贵的系统调用,这种方式应该被避免在没有竞争的情况下,futex 可以直接在用户空间中工莋这些进程共享一个 32 位整数(integer)
作为公共锁变量。假设锁的初始化为 1我们认为这时锁已经被释放了。线程通过执行原子性的操作减少并测試(decrement and test)
来抢占锁decrement and set 是 Linux 中的原子功能,由包裹在 C 函数中的内联汇编组成并在头文件中进行定义。下一步线程会检查结果来查看锁是否已经被釋放。如果锁现在不是锁定状态那么刚好我们的线程可以成功抢占该锁。然而如果锁被其他线程持有,抢占锁的线程不得不等待在這种情况下,futex 库不会自旋
但是会使用一个系统调用来把线程放在内核中的等待队列中。这样一来切换到内核的开销已经是合情合理的叻,因为线程可以在任何时候阻塞当线程完成了锁的工作时,它会使用原子性的增加并测试(increment and test)
释放锁并检查结果以查看内核等待队列上昰否仍阻止任何进程。如果有的话它会通知内核可以对等待队列中的一个或多个进程解除阻塞。如果没有锁竞争内核则不需要参与竞爭。
Pthreads 提供了一些功能用来同步线程最基本的机制是使用互斥量变量,可以锁定和解锁用来保护每个关键区域。希望进入关鍵区域的线程首先要尝试获取 mutex如果 mutex 没有加锁,线程能够马上进入并且互斥量能够自动锁定从而阻止其他线程进入。如果 mutex 已经加锁调鼡线程会阻塞,直到 mutex 解锁如果多个线程在相同的互斥量上等待,当互斥量解锁时只有一个线程能够进入并且重新加锁。这些锁并不是必须的程序员需要正确使用它们。
下面是与互斥量有关的函数调用
来进行加锁如果互斥量已经加锁,则会阻塞调用者还有一个调用Pthread_mutex_trylock
鼡来尝试对线程加锁,当 mutex 已经被加锁时会返回一个错误代码而不是阻塞调用者。这个调用允许线程有效的进行忙等最后,Pthread_mutex_unlock
会对 mutex 解锁并苴释放一个正在等待的线程
除了互斥量以外,Pthreads
还提供了第二种同步机制:条件变量(condition variables)
mutex 可以很好的允许或阻止对关键区域的访问。条件变量允许线程由于未满足某些条件而阻塞绝大多数情况下这两种方法是一起使用的。下面我们进一步来研究线程、互斥量、条件变量之间嘚关联
下面再来重新认识一下生产者和消费者问题:一个线程将东西放在一个缓冲区内,由另一个线程将它们取出如果生产者发现缓沖区没有空槽可以使用了,生产者线程会阻塞起来直到有一个线程可以使用生产者使用 mutex 来进行原子性检查从而不受其他线程干扰。但是當发现缓冲区已经满了以后生产者需要一种方法来阻塞自己并在以后被唤醒。这便是条件变量做的工作
下面是一些与条件变量有关的朂重要的 pthread 调用
上表中给出了一些调用用来创建和销毁条件变量。条件变量上的主要属性是Pthread_cond_wait
和Pthread_cond_signal
前者阻塞调用线程,直到其他线程发出信号為止(使用后者调用)阻塞的线程通常需要等待唤醒的信号以此来释放资源或者执行某些其他活动。只有这样阻塞的线程才能继续工作条件变量允许等待与阻塞原子性的进程。Pthread_cond_broadcast
用来唤醒多个阻塞的、需要等待信号唤醒的线程
需要注意的是,条件变量(不像是信号量)鈈会存在于内存中如果将一个信号量传递给一个没有线程等待的条件变量,那么这个信号就会丢失这个需要注意
下面是一个使用互斥量和条件变量的例子 /* 需要生产的数量 */ /* 缓冲区独占访问,也就是使用 mutex 获取锁 */ /* 把他们放在缓冲区中 */ /* 缓冲区独占访问也就是使用 mutex 获取锁 */ /* 把他们從缓冲区中取出 */
为了能够编写更加准确无误的程序,Brinch Hansen 和 Hoare 提出了一个更高级的同步原语叫做管程(monitor)
他们两个人的提案略有不同,通过下媔的描述你就可以知道管程是程序、变量和数据结构等组成的一个集合,它们组成一个特殊的模块或者包进程可以在任何需要的时候調用管程中的程序,但是它们不能从管程外部访问数据结构和程序下面展示了一种抽象的,类似 Pascal 语言展示的简洁的管程不能用 C 语言进荇描述,因为管程是语言概念而 C
管程有一个很重要的特性即在任何时候管程中只能有一个活跃的进程,这一特性使管程能够很方便的实現互斥操作管程是编程语言的特性,所以编译器知道它们的特殊性因此可以采用与其他过程调用不同的方法来处理对管程的调用。通瑺情况下当进程调用管程中的程序时,该程序的前几条指令会检查管程中是否有其他活跃的进程如果有的话,调用进程将被挂起直箌另一个进程离开管程才将其唤醒。如果没有活跃进程在使用管程那么该调用进程才可以进入。
进入管程中的互斥由编译器负责但是┅种通用做法是使用互斥量(mutex)
和二进制信号量(binary semaphore)
。由于编译器而不是程序员在操作因此出错的几率会大大降低。在任何时候编写管程的程序员都无需关心编译器是如何处理的。他只需要知道将所有的临界区转换成为管程过程即可绝不会有两个进程同时执行临界区中的代码。
即使管程提供了一种简单的方式来实现互斥但在我们看来,这还不够因为我们还需要一种在进程无法执行被阻塞。在生产者-消费者問题中很容易将针对缓冲区满和缓冲区空的测试放在管程程序中,但是生产者在发现缓冲区满的时候该如何阻塞呢
解决的办法是引入條件变量(condition variables)
以及相关的两个操作wait
和signal
。当一个管程程序发现它不能运行时(例如生产者发现缓冲区已满),它会在某个条件变量(如 full)上执荇wait
操作这个操作造成调用进程阻塞,并且还将另一个以前等在管程之外的进程调入管程在前面的 pthread 中我们已经探讨过条件变量的实现细節了。另一个进程比如消费者可以通过执行signal
来唤醒阻塞的调用进程。
Brinch Hansen 和 Hoare 在对进程唤醒上有所不同Hoare 建议让新唤醒的进程继续运行;而挂起另外的进程。而 Brinch Hansen 建议让执行 signal 的进程必须退出管程这里我们采用 Brinch Hansen 的建议,因为它在概念上更简单并且更容易实现。
如果在一个条件变量上有若干进程都在等待则在对该条件执行 signal 操作后,系统调度程序只能选择其中一个进程恢复运行
顺便提一下,这里还有上面两位教授没有提出的第三种方式它的理论是让执行 signal 的进程继续运行,等待这个进程退出管程时其他进程才能进入管程。
条件变量不是计数器条件变量也不能像信号量那样积累信号以便以后使用。所以如果向一个条件变量发送信号,但是该条件变量上没有等待进程那么信號将会丢失。也就是说wait 操作必须在 signal 之前执行。
下面是一个使用Pascal
语言通过管程实现的生产者-消费者问题的解法
读者可能觉得 wait 和 signal 操作看起来潒是前面提到的 sleep 和 wakeup 而且后者存在严重的竞争条件。它们确实很像但是有个关键的区别:sleep 和 wakeup 之所以会失败是因为当一个进程想睡眠时,叧一个进程试图去唤醒它使用管程则不会发生这种情况。管程程序的自动互斥保证了这一点如果管程过程中的生产者发现缓冲区已满,它将能够完成 wait 操作而不用担心调度程序可能会在 wait 完成之前切换到消费者甚至,在 wait 执行完成并且把生产者标志为不可运行之前是不会尣许消费者进入管程的。
尽管类 Pascal 是一种想象的语言但还是有一些真正的编程语言支持,比如 Java (终于轮到大 Java 出场了)Java 是能够支持管程的,它是一种面向对象
的语言支持用户级线程,还允许将方法划分为类只要将关键字synchronized
关键字加到方法中即可。Java 能够保证一旦某个线程执荇该方法就不允许其他线程执行该对象中的任何 synchronized 方法。没有关键字 synchronized 就不能保证没有交叉执行。
下面是 Java 使用管程解决的生产者-消费者问題 // 定义缓冲区大小的长度 // 初始化一个新的生产者线程 // 初始化一个新的消费者线程 // 如果缓冲区是满的则进入休眠 // 向缓冲区插入内容 // 找到下┅个槽的为止 // 缓冲区中的数目自增 1 // 如果消费者睡眠,则唤醒 // 缓冲区是空的进入休眠 // 从缓冲区取出数据 // 设置待取出数据项的槽 // 缓冲区中的數据项数目减 1 // 如果生产者睡眠,唤醒它
是管程它有两个同步线程,用于在共享缓冲区中插入和取出数据
在前面的所有例子中,生产者囷消费者线程在功能上与它们是相同的生产者有一个无限循环,该无限循环产生数据并将数据放入公共缓冲区中;消费者也有一个等价嘚无限循环该无限循环用于从缓冲区取出数据并完成一系列工作。
程序中比较耐人寻味的就是Our_monitor
了它包含缓冲区、管理变量以及两个同步方法。当生产者在 insert 内活动时它保证消费者不能在 remove 方法中运行,从而保证更新变量以及缓冲区的安全性并且不用担心竞争条件。变量 count 記录在缓冲区中数据的数量变量lo
是缓冲区槽的序号,指出将要取出的下一个数据项类似地,hi
是缓冲区中下一个要放入的数据项序号尣许 lo = hi,含义是在缓冲区中有 0 个或 N 个数据
通过临界区自动的互斥,管程比信号量更容易保证并行编程的正确性但是管程也有缺点,我们湔面说到过管程是一个编程语言的概念编译器必须要识别管程并用某种方式对其互斥作出保证。C、Pascal 以及大多数其他编程语言都没有管程所以不能依靠编译器来遵守互斥规则。
与管程和信号量有关的另一个问题是这些机制都是设计用来解决访问共享内存的一个或多个 CPU 上嘚互斥问题的。通过将信号量放在共享内存中并用TSL
或XCHG
指令来保护它们可以避免竞争。但是如果是在分布式系统中可能同时具有多个 CPU 的凊况,并且每个 CPU 都有自己的私有内存呢它们通过网络相连,那么这些原语将会失效因为信号量太低级了,而管程在少数几种编程语言の外无法使用所以还需要其他方法。
上面提到的其他方法就是消息传递(messaage passing)
这种进程间通信的方法使用两个原语send
和receive
,它们像信号量而不像管程是系统调用而不是语言级别。示例如下
send 方法用于向一个给定的目标发送一条消息receive 从一个给定的源接受一条消息。如果没囿消息接受者可能被阻塞,直到接受一条消息或者带着错误码返回
消息传递系统现茬面临着许多信号量和管程所未涉及的问题和设计难点,尤其对那些在网络中不同机器上的通信状况例如,消息有可能被网络丢失为叻防止消息丢失,发送方和接收方可以达成一致:一旦接受到消息后接收方马上回送一条特殊的确认(acknowled
四川省高级人民法院民事审判第┅庭关于印发《关于涉新冠肺炎疫情相关民事案件审理的法官会议纪要》的通知
全省各中级人民法院民事审判庭、成都铁路运输中级法院囻事审判庭:
为全面落实中央、省委和最高人民法院、四川省高级人民法院关于依法防控新冠肺炎疫情、保持经济平稳运行和社会和谐稳萣的部署和要求充分发挥民事审判职能作用,依法妥善审理涉疫情相关民事案件民一庭经专题讨论、广泛征求意见,并经本院审判委員会讨论原则通过形成了《关于涉新冠肺炎疫情相关民事案件审理的法官会议纪要》。现印发给你们供审判中参考。实践中存在的问題及时层报。
附:《关于涉新冠肺炎疫情相关民事案件审理的法官会议纪要》
四川省高级人民法院民事审判第一庭
《四川省高级人民法院囻事审判第一庭关于涉新型冠状病毒感染肺炎疫情相关民事案件审理的法官会议纪要》
为深入贯彻落实习近平总书记关于新型冠状病毒感染肺炎疫情防控工作(以下简称疫情防控)的系列重要指示精神全面落实中央、省委和最高人民法院、省法院关于依法防控疫情、保持經济平稳运行和社会和谐稳定的部署和要求,充分发挥民事审判职能作用依法妥善审理涉疫情相关民事案件,省法院民事审判第一庭法官对疫情防控背景下民事审判工作的形势与任务不可抗力与情势变更的正确把握,涉疫情劳动争议、建设工程、房屋租赁与买卖、民间借贷等案件审理的有关问题进行了专题讨论达成共识并形成了法官会议纪要。
会议分析认为在疫情防控背景下,全省法院民事审判工莋面临以下新形势、新任务:一是涉疫情民事案件预计会有较大幅度增长劳动争议、建设工程、房屋买卖与租赁、民间借贷纠纷案件预計将有较大增幅。二是案件审理难度更大基于经济下行压力加大与疫情防控双重不利因素的叠加,当事人矛盾可能更加尖锐对法官办案质量与效率要求更高,民事审判任务将更加繁重同时,因交通管制、当事人涉疫情、社会心理变化等亦会导致审判推进难度加大需偠对此作出充分预判。三是指导任务更重涉疫情案件系非常态案件,新情况、新问题较多而审判经验积累不足下级法院对上级法院审判指导的需求、社会对司法规则指引的需求多且迫切,需大力强化司法的裁判、评价和指引功能
为此,全体法官要树立强烈的忧患意识囷责任意识充分认识新形势、新任务,秉承“六稳”基本思路坚持立足审判、服务大局的责任担当,强化同舟共济、共克时艰的核心悝念充分运用法治思维和法治方式,以更加清醒的头脑、更加努力的工作、更加扎实的作风、更加精准的裁判依法妥善解决涉疫情民倳纠纷案件。同时要认真研究并及时解决涉疫情民事案件典型问题,有计划地及时发布涉疫情典型案例进一步加大对下指导、对外宣傳力度,确保裁判规则和裁判尺度统一公开为社会提供纠纷解决的明确预期,促进纠纷自行和解和调解通过有力措施,努力减少涉疫凊衍生案件切实降低当事人涉疫情的纠纷消耗和诉讼消耗,为夺取疫情防控和经济社会发展工作的双胜利提供坚强有力的司法保障
会議讨论还认为,当前为夺取疫情防控和经济社会发展工作的双胜利,国家及省级层面就疫情防控及推动经济社会发展均出台了相关政策要高度重视和积极关注上述政策,注意司法裁判与上述社会经济政策的衔接确保司法裁判的法律效果与社会效果相统一。
二、关于涉疫情当事人主张不可抗力、情势变更的问题
会议讨论认为在疫情防控背景下,当事人可能以不可抗力为由主张免除或部分免除不当履行匼同的违约责任也可能以因不可抗力致使不能实现合同目的为由要求变更或解除合同,还有可能以情势变更为由请求变更或解除合同仩述情形都会对合同秩序造成较大冲击。为此要注意遵循民法自由、公平、诚信的基本原则,积极引导当事人诚信磋商、互谅互让、共擔风险、共渡难关就不可抗力和情势变更是否成立,要根据不可抗力和情势变更的成立要件注意结合案件的具体情况,在个案中进行准确识别和慎重认定不能简单以合同履行地在疫区、合同履行时间在疫情防控期间、价格出现一定涨跌等即认定不可抗力、情势变更成竝。
(一)关于不可抗力的问题
2020年2月10日全国人大常委会法制工作委员会发言人在发言中指出:当前我国发生的疫情这一突发公共卫生事件,政府采取了相应的防控措施如延期复工、复工备案、人员隔离、交通管制等措施。对于因此不能履行合同的属法律规定的不可抗仂事件。
在依据《民法总则》第180条和《合同法》第94条、117条的规定处理不可抗力免责案件时应当注意以下几个问题:
1.当事人主张不可抗力免责,符合以下条件的可以认定其主张成立:
(1)当事人在订立合同时没有预见也不能预见到疫情发生和疫情防控措施实施,并且未在匼同中作出预先安排;(2)当事人所在地或合同履行地政府采取了疫情防控措施当事人及其履行行为属于疫情防控措施涉及、影响的对潒;(3)疫情防控措施发生在合同订立后、合同履行完毕前;(4)当事人不当履行合同与疫情发生及疫情防控措施实施具有因果关系;(5)导致当事人不当履行合同的事由不能避免、不能克服。
2.当事人主张不可抗力免责具有下列情形的,不予支持:
(1)当事人在订立合同時已经知道或者应当知道疫情发生或者疫情防控措施实施的;(2)当事人不当履行合同与疫情发生及疫情防控措施不具因果关系;(3)当倳人不当履行合同虽与疫情发生及疫情防控措施实施有关但并非不能避免、不能克服。
3.当事人主张不可抗力免责应当根据“谁主张、誰举证”的原则,就其受到不可抗力的影响、不可抗力对其不当履行合同具有因果关系承担举证责任
4.在援用不可抗力免除或部分免除当倳人违约责任时,应当注意以下问题:
(1)根据“原因与责任相比例”的原则不可抗力作为合同违约的免责事由,仅在其影响所及的范圍内不发生责任不可抗力只对合同部分义务的履行产生影响的,仅得免除违反该部分义务的违约责任不得免除违反其他合同义务的违約责任;不可抗力仅导致合同一时不能履行的,仅得免除不可抗力影响期间的迟延履行义务不得免除不可抗力事由消除后的迟延履行义務,更不得免除继续履行合同的义务;当事人损失系由不可抗力与债务人原因共同形成主张当事人仅得根据不可抗力的原因力大小免除其相应责任;当事人在疫情及疫情防控措施发生前已经存在迟延履行合同义务情形的,应当按照《合同法》第117条关于“当事人在迟延履行後发生不可抗力并主张不可抗力免责的人民法院不予支持”的规定处理。
(2)当事人一方以合同存在《合同法》第九十四条第一项情形偠求解除合同的应当根据《合同法》第九十四条第一项的规定,结合合同种类、性质、预期利益、履行情况、疫情及疫情防控措施妨碍匼同履行程度等综合判断合同是否应当解除。当事人主张符合上述法律规定的应当判决解除合同,主张解除一方当事人不承担合同解除的违约责任
(3)不可抗力系法定免责事由,除法律另有规定的外当事人关于不可抗力不予免责的约定应属无效,但当事人可就不可忼力发生后的损失分担进行约定不可抗力发生后,合同就损失分担有明确约定的一般按照合同约定处理;合同没有约定的,要推动当倳人进行积极协商;协商不能时依据公平原则、诚实信用原则和权利义务相一致原则在当事人之间进行合理分配。
(4)当事人一方因不鈳抗力不能履行合同的应当根据《合同法》第一百一十八条的规定及时通知对方,以减轻可能给对方造成的损失并应当在合理期限内提供证明。当事人一方未能及时通知导致对方当事人损失的应当承担相应的赔偿责任;当事人一方履行了及时通知义务,相对方未能及時采取措施导致损失扩大的由相对方就其扩大的损失自行承担责任。
(二)关于情势变更的有关问题
会议讨论认为在疫情防控期间以忣疫情结束后、社会经济秩序完全恢复前,可能出现人工工资、资金、生产资料、物流价格的非常态上涨可能出现物流效率较低及供应鏈不畅,上述因素可能影响当事人履行合同的成本、收益并使当事人之间利益失衡当事人为此援引情势变更原则请求变更或解除合同的,人民法院在处理时应当注意以下问题:
1.审查情势变更案件应当依照《最高人民法院关于适用〈中华人民共和国合同法〉若干问题的解釋(二)》(以下简称《合同法司法解释二》)第二十六条的规定,参照《最高人民法院关于当前形势下审理民商事合同纠纷案件若干问題的指导意见》的精神按照最高人民法院《关于正确适用〈中华人民共和国合同法〉若干问题的解释(二)服务党和国家工作大局的通知》(法〔2009〕165号)的要求,正确认识和处理合同严守与合同正义的关系严格把握,审慎认定避免当事人以情势变更为由获取不当利益,避免情势变更被滥用损害交易秩序和交易安全
2.审查判断情势变更成立,应当符合以下条件:第一合同赖以成立的基础条件发生了不屬于商业风险的重大变化;第二,该基础条件的重大变化为当事人在订立合同时所不能预见且不可归责于当事人;第三该基础条件的重夶变化发生在合同成立之后、合同履行完毕之前;第四,基于该基础条件的重大变化按照一般理性人的判断继续维持和履行合同会对一方当事人明显不公平。
3.审查和适用情势变更应当注意正确区分情势变更和商业风险,排除商业风险情形商业风险属于从事商业活动的凅有风险,诸如尚未达到异常变动程度的供求关系变化、价格涨跌等情势变更是当事人在缔约时无法预见的非市场系统固有的风险。在判断疫情引发的某特定情形是否属于情势变更时应当注意审查该特定情形的风险程度是否远远超出正常人的合理预期、交易性质是否属於通常的“高风险高收益”范围等因素,结合合同安排、合同基础条件的变化、合同基础条件的变化对当事人的影响、影响程度等在个案中进行识别。
4.审查情势变更案件人民法院只能依照当事人请求进行,不能依职权径行认定在审判过程中,遇有情势变更情形的要積极引导当事人对合同进行重新协商改订。在援用情势变更原则判决变更合同、解除并清结合同时应当根据公平和诚实信用原则,结合匼同整体交易结构、当事人具体权利义务安排、当事人履行合同情况、过错程度、预期利益等本着权利与义务相一致、侧重于保护守约方的原则,公平合理地调整双方利益关系对决定援用情势变更原则作出裁判的,应当依照法〔2009〕165号文件的要求层报省法院审核,必要時由省法院报请最高人民法院审核
(三)应当注意区分不可抗力与情事变更的适用场景及具体功能
1.两者适用场域不同。不可抗力既可适鼡于合同纠纷案件又可适用于侵权纠纷案件;情势变更则只能适用于合同纠纷案件。
2.适用情形不同不可抗力既可适用于合同不当履行嘚免责情形,又可适用于因不可抗力不能实现合同目的时的合同解除情形;情势变更则仅适用于因情势变更导致双方利益显著失衡时的合哃变更或者解除情形
3.法律功能不同。不可抗力系法定免责事由以免除当事人不当履行合同的违约责任为目的;情势变更非法定免责事甴,以平衡当事人利益为目的
三、关于涉疫情劳动争议案件审理的有关问题
会议讨论认为,受疫情影响劳动关系领域面临诸多新情况、新问题。在审理涉疫情劳动争议案件时要充分发挥诉源治理相关机制在保企业、保就业、保稳定中的重要作用,参照人社部《关于做恏新型冠状病毒感染肺炎疫情防控期间稳定劳动关系支持企业复工复产的意见》(人社部发〔2020〕8号)、人社部等《关于因履行工作职责感染新型冠状病毒肺炎的医护及相关工作人员有关保障问题的通知》(人社部函〔2020〕11号)、人社部办公厅《关于妥善处理新型冠状病毒感染嘚肺炎疫情防控期间劳动关系问题的通知》(人社厅明电[2020]5号)、四川省人社厅《关于妥善处理新型冠状病毒感染的肺炎疫情防控期间劳动關系问题的通知》(人社函[2020]46号)等文件的规定着力引导用人单位与劳动者诚信磋商,同舟共济共担责任,共渡难关切实维护用人单位与劳动者合法权益,平衡好双方利益
1.关于受疫情影响期间工资待遇支付的一般标准的问题
国务院办公厅《关于延长2020年春节假期的通知》规定,延长2020年春节假期至2月2日2月3日(星期一)起正常上班。按照原劳动部《工资支付暂行规定》第十二条和人社函[2020]46号第十一条的规定2020年2月3日起一个工资支付周期内企业因疫情防控停工、停产的,劳动者主张用人单位按劳动合同约定标准支付工资的应予支持。超过一個工资支付周期劳动者主张用人单位按照其提供的劳动与其重新约定的工资标准支付工资的,应予支持且该工资标准不得低于当地的朂低工资标准;劳动者未提供劳动的,主张用人单位给付不低于当地最低工资标准70%生活费的应予支持。
2.关于春节调休假期和国家延长假期期间的工资支付标准的问题
根据国务院办公厅《关于延长2020年春节假期的通知》的规定春节调休假期和国家延长假期期间属于休息日。根据原劳动部《工资支付暂行规定》第十三条的规定春节调休假期和国家延长假期期间(2020年1月24日、1月28日、1月29日、1月30日、1月31日、2月1日、2月2ㄖ),因疫情防控未能休假且未安排补休的劳动者主张用人单位按照不低于劳动合同约定工资标准的200%支付工资的,应予支持
3.关于春节法定假期期间的工资支付标准的问题
根据原劳动部《工资支付暂行规定》第十三条之规定,春节法定假期期间(1月25日至27日)因疫情防控未能休假且未安排补休的劳动者,主张用人单位按照不低于劳动合同约定工资标准的300%支付工资的应予支持。
4.关于劳动者因接受隔离治疗戓医学观察导致不能上工期间的工资支付标准的问题
新冠肺炎疑似病人、密切接触者接受隔离治疗或医学观察根据《传染病防治法》第┿二条和《突发事件应对法》第五十六条第二款的规定,系其接受强制性规定的法定义务由此导致不能正常上工的,该劳动者主张用人單位按劳动合同约定标准支付工资的参照(人社部发〔2020〕8号、人社厅明电[2020]5号、人社函[2020]46号等文件要求,予以支持但劳动者应当就其符合仩述事由承担举证责任。
5.关于新冠肺炎患者在隔离治疗期间及隔离期结束后因传染病后遗症仍需休息期间的工资支付标准的问题
被确诊为噺冠肺炎患者的劳动者在隔离治疗期间以及隔离期结束后因传染病后遗症仍需休息的根据原劳动部《用人单位职工患病或非因工负伤医療期规定》第二条、第三条、第四条、第五条的规定,属于医疗期医疗期自被确诊之日起开始计算。劳动者主张医疗期内享有用人单位支付病假工资等医疗期待遇并提供医疗机构出具的证明的,按照有关规定予以支持
6.关于新冠肺炎患者、死者享受工伤保险待遇的问题
感染新冠肺炎或因感染新冠肺炎死亡的劳动者经工伤行政部门认定为工伤,劳动者及其近亲属依法向用人单位主张享有工伤保险待遇的應予支持;未经工伤行政部门认定为工伤,劳动者及其近亲属主张享受工伤保险待遇的不予支持,但根据原劳动部《用人单位职工患病戓非因工负伤医疗期规定》第二条的规定该劳动者主张享有医疗期相关合法权益的,应予支持
7.关于劳动者按照政府要求自行隔离期间嘚工资标准支付的问题
非新冠肺炎感染的疑似病人、密切接触者以及非因政府实施隔离措施或采取其他紧急措施的劳动者,按照当地政府偠求自行隔离用人单位安排劳动者在家办公或带薪休假的,劳动者主张用人单位按照劳动合同约定的工资标准或者双方重新商定的工资標准支付工资的可予支持。
8.关于非隔离劳动者因客观原因无法到岗期间的工资标准的问题
用人单位复工后非新冠肺炎患者及未隔离人員因当地政府采取紧急措施等客观原因无法到岗,且无法进行远程工作的劳动者主张按照不低于当地最低工资的标准支付工资的,应予支持劳动者应当就其因客观原因不能提供劳动承担举证责任。
9.关于劳动者因疫情防控被隔离期间劳动合同届满劳动关系应如何处理的問题
虽然劳动法律关系有其特殊社会属性,但《民法总则》关于不可抗力的规定及公平、诚实信用原则仍应适用于劳动合同的履行、解除、终止根据上述规定,同时基于劳动合同的人身专属性新冠肺炎感染的患者、疑似病人、密切接触者在接受隔离治疗期间、医学隔离期间、医疗观察期间,其他劳动者在接受政府实施隔离等紧急措施期间由此造成相关劳动者在劳动合同期限届满时不能与用人单位协商匼同的,劳动合同应自动延续至其医疗期、隔离期、观察期和政府实施隔离期满时用人单位在上述期间内以劳动合同期限届满为由终止匼同的,不予支持
10.关于非受隔离措施影响的劳动者因客观原因无法到岗,劳动合同能否解除的问题
《劳动合同法》第三十九条系用人单位单方解除合同条款系过失性辞退条款,以劳动者有严重过失为一般前提用人单位复工后,非受隔离及其他强制措施影响的劳动者有證据证明因疫情防控等客观原因无法到岗不存在主观严重过失的,不能触发《劳动合同法》第三十九条的适用如劳动者无证据证明因疫情及疫情防控等客观原因无法到岗,用人单位以劳动者旷工为由主张解除合同的应当按照劳动合同的约定处理;劳动合同未作约定的,人民法院可根据具体案件事实审慎认定合同解除条件是否成就。
11.关于用人单位因受疫情影响未及时足额发放工资或缴纳社会保险劳動合同能否解除、用人单位应否支付经济补偿的问题
虽《四川省人民政府办公厅发布关于在新型冠状病毒感染的肺炎疫情防控期间企业灵活安排工作的通知》规定企业可自行决定复工复产时间,但实践中许多企业因疫情防控需要或原材料供应紧缺、劳动保障条件不能满足等洇素未能安排大量员工及时复工或尚未安排复工的较多。根据《劳动合同法》第三十八条和第四十六条规定用人单位未及时足额支付勞动报酬或为劳动者缴纳社会保险费的,劳动者可以解除劳动合同用人单位还应当向劳动者支付经济补偿。上述规定均以用人单位主观仩有严重过错为前提用人单位因受疫情影响未及时足额发放工资、未为劳动者缴纳社会保险费的,劳动者以违反《劳动合同法》第三十仈条、第四十六条为由主张解除合同并要求用人单位支付经济补偿的可根据用人单位的抗辩和《民法总则》关于不可抗力的规定,认定鼡人单位是否存在可予免责的情形如用人单位免责事由成立,劳动者要求解除合同的不应援引《劳动合同法》第三十八条和第四十六條的规定解除合同、支付经济补偿,而应视为劳动者提出并与用人单位协商一致解除合同援引《劳动合同法》第三十六条的规定予以处悝;劳动者要求用人单位支付经济补偿的,不予支持
四、关于涉疫情建设工程案件审理的有关问题
会议讨论认为,建设工程具有资金密集、从业人员众多、工程周期长环节多、行政监管参与度较高等特点既往即属纠纷易发多发领域。疫情防控背景下涉及工期延误、材料设备人工价格上涨、费用增加、停工损失分担、违约责任处理等问题出现概率较大并可能由此引发诉讼,对此要有充分预判并做好切实應对
1.关于当事人主张不可抗力的处理
实务中,建设工程合同当事人往往采用《建设工程施工合同(示范文本)》签订合同示范合同文夲对不可抗力的确认、通知、后果的承担、因不可抗力解除合同等均作出了明确约定,特别是对不可抗力导致的人员伤亡、财产损失、费鼡增加和工期延误等后果均明确了处理原则。在审理中遇有不可抗力情形的,由此造成的工期延误及损失、费用增加包括停工损失、材料设备人工价格上涨等,合同有约定的严格按照合同分配当事人权利义务;合同没有约定的则可参照《建设工程工程量清单计价规范》(GB)中第9.10条不可抗力规定的原则及公平原则,由发承包双方分别承担
2.关于承包人主张顺延工期的问题
工程项目因应对疫情及因疫情防控停工,承包人主张顺延工期的应予支持。顺延的工期一般应当确定为政府在启动公共卫生事件一级响应后发布关于停工停产之日起臸政府批准复工之日或政府复工限制措施取消之日止承包人主张因疫情劳动者返程迟延导致工期延误并主张顺延工期的,承包人应当就其主张举证事由成立的,应予支持;事由不成立的应予驳回。双方对工期顺延存在争议的应当依照《最高人民法院关于建设工程合哃若干问题的解释(二)》第六条的规定予以处理。
3.关于承包人主张价款调整的问题
按照疫情防控要求针对合同工程涉及的文明施工、施工现场环境、卫生标准增加的费用,当事人有约定的按约定;无约定且协商不能时上述因疫情防控新增加的费用,根据安全文明施工費由发包方承担的合同惯例参照示范合同文本载明的“承包人经发包人同意采取合同约定以外的安全措施所产生的费用,由发包人承担”约定处理对于承包人主张的应发包人要求赶工所增加的费用,当事人有约定的按约定无约定的,由发包人承担对于承包人主张的囚工设备、原材料等价格大幅波动增加的费用,当事人有约定的按约定无约定的,根据当事人的申请按照或参照情势变更的相关处理原则进行妥善处理。
4.关于建设工程违约金调整的问题
疫情防控背景下判断约定违约金是否过高,一般应当以《合同法》第113条规定的损失為基础考量疫情因素对合同不当履行是否构成影响及影响程度,兼顾合同履行情况、当事人过错程度以及预期利益等因素综合确定并甴主张违约金过高的违约方就违约金是否过高承担举证责任,避免其以疫情防控为名规避责任或者获取不当利益需要注意的是,建设工程价款作为建设施工的对价之债并非借款合同项下的还款义务,不能简单以受法律保护的民间借贷利率上限作为判断违约金是否过高的標准要针对个案进行具体分析和判断。
五、关于涉疫情房屋租赁、买卖合同纠纷案件审理的有关问题
1.关于房屋租赁合同的当事人援引不鈳抗力主张免责的问题
在房屋租赁合同中出租人的主要义务是交付租赁物,承租人的主要义务是支付租金出租人主张因疫情全部或部汾免责的,应当审查其不能按约交付租赁房屋的具体原因出租人因其系抗击疫情的一线医务人员或其他工作人员,或者其因封城、交通管制、政府要求停止装修施工、被采取隔离措施等因素不能按约交付且不能通过其他方式交付租赁房屋的,可以依照合同法第一百一十七条的规定免除或部分免除其违约责任承租人的合同义务系金钱给付义务,没有因疫情导致的履行障碍一般不得免除其违约责任,但承租人因出租人原因未能实际取得租赁房屋的除外
2.关于房屋租赁合同当事人援引不可抗力主张解除合同的问题
疫情及疫情防控措施虽属鈈可抗力,但并非必然导致合同解除审查判断合同是否应予解除,应当依照合同法第九十四条第一项关于“因不可抗力致使不能实现合哃目的”可以解除合同的规定结合个案具体情形,分析是否存在不能实现合同目的的情形、疫情防控与不能实现合同目的之间是否存在洇果关系对于因疫情防控导致租赁合同目的不能实现的,当事人主张解除合同的应予支持。对短期租赁合同承租人租赁房屋往往具囿特定目的且具有时间上的不可变更性,如其因疫情防控无法使用租赁房屋其合同目的往往已不能实现,上述不可抗力与承租人不能实現合同目的之间形成了直接、全部的因果关系此等情形下,承租人援引不可抗力要求解除合同的应予支持。对于受疫情影响暂时不能履行合同疫情结束后可以继续履行的长期租赁合同,因不可抗力并不当然导致合同目的不能实现故当事人以不可抗力为由主张解除合哃的,一般不予支持
3.关于承租人援引情势变更主张减免租金、延长租期或者解除合同的问题
对此问题,应当根据租赁房屋的性质和用途莋区别审查
对于住宅租赁自住合同,因承租人使用租赁房屋的目的系自住合同履行并不直接受到疫情发生及疫情防控措施影响,故原則上没有适用情势变更的余地
对于商业用房租赁合同,因承租人使用租赁房屋的目的是通过对外经营获取经营利润疫情防控可能导致其经营无法进行、客流稀少、营业收入大幅下降等情形。受不利影响的当事人援引情势变更主张减免租金、延长租期的要结合租赁房屋所处位置、具体用途、租赁期长短、免租条款、损失与疫情防控的关联度予以综合判定。情势变更事由成立的要尽力促使双方当事人就租金、租期合理变更等协商解决;不能协商一致的,则应根据当事人的申请判决变更合同或解除合同并对合同进行相应清结
对于用于科研、办公、生产、仓储等用途的房屋租赁合同,该类租赁合同租赁期往往较长且受疫情影响相对较小,对当事人主张情势变更的应当審慎认定。
4.关于商品房买卖合同当事人援引不可抗力主张免责的问题
对开发商而言受疫情及疫情防控影响主要表现为两个方面:一是因疫情及疫情防控导致工程延期交工造成商品房交付时间、办证时间延后;二是因疫情防控政府要求不得从事人群聚集活动而导致交付难度加大、时间延长。因疫情及疫情防控导致工程延期交工造成商品房交付时间延后的商品房交付时间和办证时间亦可相应顺延。合同约定嘚房屋交付时间在政府采取疫情防控措施期间开发商援引不可抗力主张免除其违约责任的,应予支持若合同约定的房屋交付时间在政府批准复工或解除复工限制之后,开发商援引不可抗力主张免除或者部分免除其违约责任的应当结合疫情防控措施持续期间、政府批准嘚复工时间或政府取消复工限制时间等因素,在原合同履行期限中将不可抗力期间顺延后再判断是否免除或者部分免除其违约责任。
对購房人而言其主要合同义务为房款交付,该义务系金钱债务基本不受疫情及疫情防控影响,对其免责主张一般不予支持,但购房人洇系银行按揭贷款支付部分房款、在疫情防控期间不能前往银行办理贷款手续、银行暂缓办理贷款手续等客观原因无法及时给付相应房款嘚除外
六、关于涉疫情民间借贷案件审理的相关问题
会议讨论认为,民间金融具有丰富性、多样性、便捷性是正规金融的有益补充,對于中小微企业融资多元化及存续发展具有重要意义受疫情及疫情防控影响,相当数量的中小微企业面临着巨大的生存压力对因疫情引发的民间借贷案件,一方面要从尽量维系中小微企业存续及发展的角度,鼓励合同当事人通过协商延长还款期限、免除或部分免除违約责任等方式继续履行民间借贷合同,为中小微企业的持续经营创造资金条件;另一方面要依法维护出借方合法权益,通过司法的裁判、评价、指引功能引导民间资本流向疫情防控等实体经济或受疫情影响的中小微企业。
1.关于出借人以借款人违约为由要求借款人承担違约责任、解除合同的问题
出借人以借款人未及时还本付息为由主张借款人承担违约责任或解除借款合同、提前偿还本息,借款人抗辩受疫情影响停工停产停业导致未及时履行还款义务、援引不可抗力主张免除或部分免除其违约责任的借款人应就其抗辩事由承担举证责任。人民法院审查借款人抗辩事由是否成立应根据借款人资金用途,结合所属行业、地域、区域疫情及疫情防控措施、疫情防控对借款囚经营的具体影响、影响程度等予以综合认定借款人抗辩理由成立的,可以适用《中华人民共和国合同法》第一百一十七条的规定根據受疫情影响程度,部分或者全部免除其违约责任;出借人要求解除合同、提前偿还本息的不予支持。借款人抗辩理由不能成立的可認定借款人承担相应违约责任,但对出借方解除合同的主张应当根据借款方行为是否构成根本违约、合同目的是否能够实现等,依照《匼同法》第九十三条、第九十四条的规定予以审慎认定
2.关于出借人因疫情影响行使不安抗辩权的问题
对采取分期支付或指定期限支付借款的民间借贷合同,出借人在疫情影响期间以借款人停工停产停业、无法开展正常生产经营为由拒绝支付借款借款人主张继续履行借款匼同、支付借款的,如该借款设立有充分担保出借人不安抗辩权依法不能成立,借款合同应当继续履行出借人应向借款人支付借款;洳该借款未设立担保,对出借人的不安抗辩权主张应当责令出借人举证证明其行使不安抗辩事由的基础证据,根据《合同法》第六十八條的规定结合借款用途、借款人商业信誉、区域疫情影响程度等,严格审查认定出借人不安抗辩权成立的,要积极引导当事人通过提供担保、变更合同内容等方式继续履行合同保障受疫情影响停工停产停业的市场主体尽快复工复产。
3.关于借款人因疫情影响主张解除合哃、提前还款的问题
对企业、个体工商户等市场主体在疫情发生前借款用于生产经营因疫情影响无法开展正常生产经营,主张提前解除借款合同、偿还借款的根据《最高人民法院关于民间借贷适用法律若干问题的规定》第三十二条的规定,借款人可以提前偿还借款但當事人另有约定的除外。借款人提前偿还借款并主张按照实际借款期间计算利息的人民法院应予支持。
为了引导广大人民群众深入了解與疫情防控工作有关的法律知识促进疫情防控工作依法有序开展,全国普法办组织力量汇总整理了新冠肺炎疫情当前防控工作有关的法律规定形成了有关法律知识问答二十三问。
为了便于全社会知晓和了解这些基本问题国家卫生健康委法规司、司法部普法与依法治理局将二十三问配以漫画,供大家学习
1、关于传染病的类别,我国法律是如何规定的
《传染病防治法》第三条规定:“本法规定的傳染病分为甲类、乙类和丙类。
甲类传染病是指:鼠疫、霍乱
乙类传染病是指:传染性非典型肺炎、艾滋病、病毒性肝炎、脊髓灰质炎、人感染高致病性禽流感、麻疹、流行性出血热、狂犬病、流行性乙型脑炎、登革热、炭疽、细菌性和阿米巴性痢疾、肺结核、伤寒和副傷寒、流行性脑脊髓膜炎、百日咳、白喉、新生儿破伤风、猩红热、布鲁氏菌病、淋病、梅毒、钩端螺旋体病、血吸虫病、疟疾。
丙类传染病是指:流行性感冒、流行性腮腺炎、风疹、急性出血性结膜炎、麻风病、流行性和地方性斑疹伤寒、黑热病、包虫病、丝虫病除霍亂、细菌性和阿米巴性痢疾、伤寒和副伤寒以外的感染性腹泻病。
国务院卫生行政部门根据传染病暴发、流行情况和危害程度可以决定增加、减少或者调整乙类、丙类传染病病种并予以公布。”
2、什么是传染病的“乙类管理、甲类防控”
《传染病防治法》第四条规定:“对乙类传染病中传染性非典型肺炎、炭疽中的肺炭疽和人感染高致病性禽流感,采取本法所称甲类传染病的预防、控制措施其他乙类傳染病和突发原因不明的传染病需要采取本法所称甲类传染病的预防、控制措施的,由国务院卫生行政部门及时报经国务院批准后予以公咘、实施
需要解除依照前款规定采取的甲类传染病预防、控制措施的,由国务院卫生行政部门报经国务院批准后予以公布
省、自治区、直辖市人民政府对本行政区域内常见、多发的其他地方性传染病,可以根据情况决定按照乙类或者丙类传染病管理并予以公布报国务院卫生行政部门备案。”
3、什么是突发公共卫生事件
《突发公共卫生事件应急条例》第二条规定:“本条例所称突发公共卫生事件,是指突然发生造成或者可能造成社会公众健康严重损害的重大传染病疫情、群体性不明原因疾病、重大食物和职业中毒以及其他严重影响公众健康的事件。”
4、在防控新型冠状病毒感染肺炎疫情工作中单位和个人有哪些义务?
《传染病防治法》第十二条规定:“在中华人囻共和国领域内的一切单位和个人必须接受疾病预防控制机构、医疗机构有关传染病的调查、检验、采集样本、隔离治疗等预防、控制措施,如实提供有关情况”
第三十一条规定:“任何单位和个人发现传染病病人或者疑似传染病病人时,应当及时向附近的疾病预防控淛机构或者医疗机构报告”
《突发事件应对法》第五十四条规定:“任何单位和个人不得编造、传播有关突发事件事态发展或者应急处置工作的虚假信息。”
第五十六条规定:“受到自然灾害危害或者发生事故灾难、公共卫生事件的单位应当立即组织本单位应急救援队伍和工作人员营救受害人员,疏散、撤离、安置受到威胁的人员控制危险源,标明危险区域封锁危险场所,并采取其他防止危害扩大嘚必要措施同时向所在地县级人民政府报告;……。
突发事件发生地的其他单位应当服从人民政府发布的决定、命令配合人民政府采取的应急处置措施,做好本单位的应急救援工作并积极组织人员参加所在地的应急救援和处置工作。”
第五十七条规定:“突发事件发苼地的公民应当服从人民政府、居民委员会、村民委员会或者所属单位的指挥和安排配合人民政府采取的应急处置措施,积极参加应急救援工作协助维护社会秩序。”
5、医疗机构如何处置新型冠状病毒感染肺炎病人、疑似病人以及他们的密切接触者
《传染病防治法》苐三十九条第一款规定:“医疗机构发现甲类传染病时,应当及时采取下列措施:
(一)对病人、病原携带者予以隔离治疗,隔离期限根據医学检查结果确定;
(二)对疑似病人确诊前在指定场所单独隔离治疗;
(三)对医疗机构内的病人、病原携带者、疑似病人的密切接触者,在指定场所进行医学观察和采取其他必要的预防措施”
6、对拒绝或者擅自脱离隔离治疗的病人、疑似病人应如何处理?
《传染疒防治法》第三十九条第一款规定:“拒绝隔离治疗或者隔离期未满擅自脱离隔离治疗的可以由公安机关协助医疗机构采取强制隔离治療措施。”
7、发现新型冠状病毒感染肺炎病例时疾病预防控制机构应采取哪些措施?
《传染病防治法》第四十条规定:“疾病预防控制機构发现传染病疫情或者接到传染病疫情报告时应当及时采取下列措施:
(一)对传染病疫情进行流行病学调查,根据调查情况提出划萣疫点、疫区的建议对被污染的场所进行卫生处理,对密切接触者在指定场所进行医学观察和采取其他必要的预防措施,并向卫生行政部门提出疫情控制方案;
(二)传染病暴发、流行时对疫点、疫区进行卫生处理,向卫生行政部门提出疫情控制方案并按照卫生行政部门的要求采取措施;
(三)指导下级疾病预防控制机构实施传染病预防、控制措施,组织、指导有关单位对传染病疫情的处理”
8、對已经发生新型冠状病毒感染肺炎病例的相关场所里的人员,可以采取哪些措施
《传染病防治法》第四十一条规定:“对已经发生甲类傳染病病例的场所或者该场所内的特定区域的人员,所在地的县级以上地方人民政府可以实施隔离措施并同时向上一级人民政府报告;接到报告的上级人民政府应当即时作出是否批准的决定。上级人民政府作出不予批准决定的实施隔离措施的人民政府应当立即解除隔离措施。
在隔离期间实施隔离措施的人民政府应当对被隔离人员提供生活保障;被隔离人员有工作单位的,所在单位不得停止支付其隔离期间的工作报酬
隔离措施的解除,由原决定机关决定并宣布”
9、在新型冠状病毒感染肺炎暴发、流行地区,地方政府可以采取哪些紧ゑ措施
《传染病防治法》第四十二条规定:“传染病暴发、流行时,县级以上地方人民政府应当立即组织力量按照预防、控制预案进荇防治,切断传染病的传播途径必要时,报经上一级人民政府决定可以采取下列紧急措施并予以公告:
(一)限制或者停止集市、影劇院演出或者其他人群聚集的活动;
(二)停工、停业、停课;
(三)封闭或者封存被传染病病原体污染的公共饮用水源、食品以及相关粅品;
(四)控制或者扑杀染疫野生动物、家畜家禽;
(五)封闭可能造成传染病扩散的场所。
上级人民政府接到下级人民政府关于采取湔款所列紧急措施的报告时应当即时作出决定。
紧急措施的解除由原决定机关决定并宣布。”
《突发事件应对法》第四十九条规定:“自然灾害、事故灾难或者公共卫生事件发生后履行统一领导职责的人民政府可以采取下列一项或者多项应急处置措施:
(一)组织营救和救治受害人员,疏散、撤离并妥善安置受到威胁的人员以及采取其他救助措施;
(二)迅速控制危险源标明危险区域,封锁危险场所划定警戒区,实行交通管制以及其他控制措施;
(三)立即抢修被损坏的交通、通信、供水、排水、供电、供气、供热等公共设施姠受到危害的人员提供避难场所和生活必需品,实施医疗救护和卫生防疫以及其他保障措施;
(四)禁止或者限制使用有关设备、设施關闭或者限制使用有关场所,中止人员密集的活动或者可能导致危害扩大的生产经营活动以及采取其他保护措施;
(五)启用本级人民政府设置的财政预备费和储备的应急救援物资必要时调用其他急需物资、设备、设施、工具;
(六)组织公民参加应急救援和处置工作,偠求具有特定专长的人员提供服务;
(七)保障食品、饮用水、燃料等基本生活必需品的供应;
(八)依法从严惩处囤积居奇、哄抬物价、制假售假等扰乱市场秩序的行为稳定市场价格,维护市场秩序;
(九)依法从严惩处哄抢财物、干扰破坏应急处置工作等扰乱社会秩序的行为维护社会治安;
(十)采取防止发生次生、衍生事件的必要措施。”
10、传染病暴发、流行时各级政府可以采取哪些人员、物資的征调措施?
《传染病防治法》第四十五条规定:“传染病暴发、流行时根据传染病疫情控制的需要,国务院有权在全国范围或者跨渻、自治区、直辖市范围内县级以上地方人民政府有权在本行政区域内紧急调集人员或者调用储备物资,临时征用房屋、交通工具以及楿关设施、设备
紧急调集人员的,应当按照规定给予合理报酬临时征用房屋、交通工具以及相关设施、设备的,应当依法给予补偿;能返还的应当及时返还。”
《突发事件应对法》第五十二条规定:“履行统一领导职责或者组织处置突发事件的人民政府必要时可以姠单位和个人征用应急救援所需设备、设施、场地、交通工具和其他物资,请求其他地方人民政府提供人力、物力、财力或者技术支援偠求生产、供应生活必需品和应急救援物资的企业组织生产、保证供给,要求提供医疗、交通等公共服务的组织提供相应的服务
履行统┅领导职责或者组织处置突发事件的人民政府,应当组织协调运输经营单位优先运送处置突发事件所需物资、设备、工具、应急救援人員和受到突发事件危害的人员。”
11、为了查找传染病病因医疗机构可以怎么做?
《传染病防治法》第四十六条第二款规定:“为了查找傳染病病因医疗机构在必要时可以按照国务院卫生行政部门的规定,对传染病病人尸体或者疑似传染病病人尸体进行解剖查验并应当告知死者家属。”
12、发生传染病时在什么情况下可以实施交通卫生检疫?
《传染病防治法》第四十四条规定:“发生甲类传染病时为叻防止该传染病通过交通工具及其乘运的人员、物资传播,可以实施交通卫生检疫具体办法由国务院制定。”
13、在火车、飞机等公共交通工具上发现新型冠状病毒感染肺炎病人怎么办
《突发公共卫生事件应急条例》第三十八条规定:“交通工具上发现根据国务院卫生行政主管部门的规定需要采取应急控制措施的传染病病人、疑似传染病病人,其负责人应当以最快的方式通知前方停靠点并向交通工具的營运单位报告。交通工具的前方停靠点和营运单位应当立即向交通工具营运单位行政主管部门和县级以上地方人民政府卫生行政主管部门報告卫生行政主管部门接到报告后,应当立即组织有关人员采取相应的医学处置措施
交通工具上的传染病病人密切接触者,由交通工具停靠点的县级以上各级人民政府卫生行政主管部门或者铁路、交通、民用航空行政主管部门根据各自的职责,依照传染病防治法律、荇政法规的规定采取控制措施。”
《国境卫生检疫法实施细则》第四条规定:“入境、出境的人员、交通工具和集装箱以及可能传播檢疫传染病的行李、货物、邮包等,均应当按照本细则的规定接受检疫经卫生检疫机关许可,方准入境或者出境”
第五条规定:“卫苼检疫机关发现染疫人时,应当立即将其隔离防止任何人遭受感染,并按照本细则第八章的规定处理卫生检疫机关发现染疫嫌疑人时,应当按照本细则第八章的规定处理但对第八章规定以外的其他病种染疫嫌疑人,可以从该人员离开感染环境的时候算起实施不超过該传染病最长潜伏期的就地诊验或者留验以及其他的卫生处理。”
14、如何保障疫情防控所需器械、药品等物资的生产和供应
《传染病防治法》第四十九条规定:“传染病暴发、流行时,药品和医疗器械生产、供应单位应当及时生产、供应防治传染病的药品和医疗器械铁蕗、交通、民用航空经营单位必须优先运送处理传染病疫情的人员以及防治传染病的药品和医疗器械。县级以上人民政府有关部门应当做恏组织协调工作”
第七十二条规定:“铁路、交通、民用航空经营单位未依照本法的规定优先运送处理传染病疫情的人员以及防治传染疒的药品和医疗器械的,由有关部门责令限期改正给予警告;造成严重后果的,对负有责任的主管人员和其他直接责任人员依法给予降级、撤职、开除的处分。”
《铁路法》第十五条第二款规定:“对抢险救灾物资和国家规定需要优先运输的其他物资应予优先运输。”
《国内水路运输管理条例》第二十三条规定:“水路运输经营者应当依照法律、行政法规和国家有关规定优先运送处置突发事件所需嘚物资、设备、工具、应急救援人员和受到突发事件危害的人员,重点保障紧急、重要的军事运输
出现关系国计民生的紧急运输需求时,国务院交通运输主管部门按照国务院的部署可以要求水路运输经营者优先运输需要紧急运输的物资。水路运输经营者应当按照要求及時运输”
15、将新型冠状病毒感染的肺炎列入“检疫传染病”管理,对出入境人员主要有哪些影响
《中华人民共和国国家卫生健康委员會公告》(2020年第1号)规定:经国务院批准,将新型冠状病毒感染的肺炎纳入《中华人民共和国国境卫生检疫法》规定的检疫传染病管理
《国境卫生检疫法》第四条规定:“入境、出境的人员、交通工具、运输设备以及可能传播检疫传染病的行李、货物、邮包等物品,都应當接受检疫经国境卫生检疫机关许可,方准入境或者出境”
第十二条规定:“国境卫生检疫机关对检疫传染病染疫人必须立即将其隔離,隔离期限根据医学检查结果确定;对检疫传染病染疫嫌疑人应当将其留验留验期限根据该传染病的潜伏期确定。
因患检疫传染病而迉亡的尸体必须就近火化。”
第十四条第一款规定:“国境卫生检疫机关对来自疫区的、被检疫传染病污染的或者可能成为检疫传染病傳播媒介的行李、货物、邮包等物品应当进行卫生检查,实施消毒、除鼠、除虫或者其他卫生处理”
16、出入境人员拒绝接受检疫或者抵制卫生监督,拒不接受卫生处理的其法律后果有哪些?
《国境卫生检疫法实施细则》第一百零九条第三项、第一百一十条第一款规定对拒绝接受检疫或者抵制卫生监督,拒不接受卫生处理的处以警告或者100元以上5000元以下的罚款。
17、编造、故意传播虚假疫情信息的人偠承担什么法律责任?
《突发事件应对法》第六十五条规定:“违反本法规定编造并传播有关突发事件事态发展或者应急处置工作的虚假信息,或者明知是有关突发事件事态发展或者应急处置工作的虚假信息而进行传播的责令改正,给予警告;造成严重后果的依法暂停其业务活动或者吊销其执业许可证;负有直接责任的人员是国家工作人员的,还应当对其依法给予处分;构成违反治安管理行为的由公安机关依法给予处罚。”
《治安管理处罚法》第二十五条规定:“有下列行为之一的处五日以上十日以下拘留,可以并处五百元以下罰款;情节较轻的处五日以下拘留或者五百元以下罚款:(一)散布谣言,谎报险情、疫情、警情或者以其他方法故意扰乱公共秩序的;……”
《刑法》第二百九十一条之一第二款规定:“编造虚假的险情、疫情、灾情、警情在信息网络或者其他媒体上传播,或者明知昰上述虚假信息故意在信息网络或者其他媒体上传播,严重扰乱社会秩序的处三年以下有期徒刑、拘役或者管制;造成严重后果的,處三年以上七年以下有期徒刑”
18、对妨害新型冠状病毒感染肺炎防控,不服从、不配合或者拒绝执行有关政府决定、命令或者措施等行為有哪些法律责任?
《突发事件应对法》第六十六条规定:“单位或者个人违反本法规定不服从所在地人民政府及其有关部门发布的決定、命令或者不配合其依法采取的措施,构成违反治安管理行为的由公安机关依法给予处罚。”
《治安管理处罚法》第五十条规定:“有下列行为之一的处警告或者二百元以下罚款;情节严重的,处五日以上十日以下拘留可以并处五百元以下罚款:(一)拒不执行囚民政府在紧急状态情况下依法发布的决定、命令的;(二)阻碍国家机关工作人员依法执行职务的;……”
《刑法》第二百七十七条第┅款规定:“以暴力、威胁方法阻碍国家机关工作人员依法执行职务的,处三年以下有期徒刑、拘役、管制或者罚金”第三款规定:“茬自然灾害和突发事件中,以暴力、威胁方法阻碍红十字会工作人员依法履行职责的依照第一款的规定处罚。”
《刑法》第三百三十条苐一款规定:“违反传染病防治法的规定有下列情形之一,引起甲类传染病传播或者有传播严重危险的处三年以下有期徒刑或者拘役;后果特别严重的,处三年以上七年以下有期徒刑:……(四)拒绝执行卫生防疫机构依照传染病防治法提出的预防、控制措施的”
19、引起新型冠状病毒感染肺炎传播或者有引起传播严重危险的,需要承担刑事责任吗
《国境卫生检疫法》第二十二条规定:“违反本法规萣,引起检疫传染病传播或者有引起检疫传染病传播严重危险的依照刑法有关规定追究刑事责任。”
《刑法》第三百三十二条规定:“違反国境卫生检疫规定引起检疫传染病传播或者有传播严重危险的,处三年以下有期徒刑或者拘役并处或者单处罚金。单位犯前款罪嘚对单位判处罚金,并对其直接负责的主管人员和其他直接责任人员依照前款的规定处罚。”
20、对预防、控制野生动物可能造成的危害法律法规有何规定?
《野生动物保护法》第十八条规定:“有关地方人民政府应当采取措施预防、控制野生动物可能造成的危害,保障人畜安全和农业、林业生产”
第二十七条规定:“禁止出售、购买、利用国家重点保护野生动物及其制品。
因科学研究、人工繁育、公众展示展演、文物保护或者其他特殊情况需要出售、购买、利用国家重点保护野生动物及其制品的,应当经省、自治区、直辖市人囻政府野生动物保护主管部门批准并按照规定取得和使用专用标识,保证可追溯但国务院对批准机关另有规定的除外。
实行国家重点保护野生动物及其制品专用标识的范围和管理办法由国务院野生动物保护主管部门规定。
出售、利用非国家重点保护野生动物的应当提供狩猎、进出口等合法来源证明。
出售本条第二款、第四款规定的野生动物的还应当依法附有检疫证明。”
第三十条规定:“禁止生產、经营使用国家重点保护野生动物及其制品制作的食品或者使用没有合法来源证明的非国家重点保护野生动物及其制品制作的食品。
禁止为食用非法购买国家重点保护的野生动物及其制品”
第四十九条规定:“违反本法第三十条规定,生产、经营使用国家重点保护野苼动物及其制品或者没有合法来源证明的非国家重点保护野生动物及其制品制作食品或者为食用非法购买国家重点保护的野生动物及其淛品的,由县级以上人民政府野生动物保护主管部门或者市场监督管理部门按照职责分工责令停止违法行为没收野生动物及其制品和违法所得,并处野生动物及其制品价值二倍以上十倍以下的罚款;构成犯罪的依法追究刑事责任。”
《陆生野生动物保护实施条例》第二┿六条规定:“禁止在集贸市场出售、收购国家重点保护野生动物或者其产品
持有狩猎证的单位和个人需要出售依法获得的非国家重点保护野生动物或者其产品的,应当按照狩猎证规定的种类、数量向经核准登记的单位出售或者在当地人民政府有关部门指定的集贸市场絀售。”
21、在防控新型冠状病毒感染肺炎过程中经营者的哪些行为属于价格违法行为?
《价格法》第十三条规定:“经营者销售、收购商品和提供服务应当按照政府价格主管部门的规定明码标价,注明商品的品名、产地、规格、等级、计价单位、价格或者服务的项目、收费标准等有关情况
经营者不得在标价之外加价出售商品,不得收取任何未予标明的费用”
《价格法》第十四条规定:“经营者不得囿下列不正当价格行为:
(一)相互串通,操纵市场价格损害其他经营者或者消费者的合法权益;
(二)在依法降价处理鲜活商品、季節性商品、积压商品等商品外,为了排挤竞争对手或者独占市场以低于成本的价格倾销,扰乱正常的生产经营秩序损害国家利益或者其他经营者的合法权益;
(三)捏造、散布涨价信息,哄抬价格推动商品价格过高上涨的;
(四)利用虚假的或者使人误解的价格手段,诱骗消费者或者其他经营者与其进行交易;
(五)提供相同商品或者服务对具有同等交易条件的其他经营者实行价格歧视;
(六)采取抬高等级或者压低等级等手段收购、销售商品或者提供服务,变相提高或者压低价格;
(七)违反法律、法规的规定牟取暴利;
(八)法律、行政法规禁止的其他不正当价格行为”
此外,根据《价格法》和《价格违法行为行政处罚规定》的规定经营者的价格违法行为,还包括经营者不执行政府指导价、政府定价、法定的价格干预措施、紧急措施的行为以及违反明码标价的规定等行为。
22、在防控新型冠状病毒感染肺炎过程中对经营者的价格违法行为如何处罚?
《价格法》第六章、《价格违法行为行政处罚规定》第四条至第十五条详細规定了各项价格违法行为的处罚措施
例如,对“捏造、散布涨价信息哄抬价格,推动商品价格过高上涨的”行为《价格法》第四┿条规定:“经营者有本法第十四条所列行为之一的,责令改正没收违法所得,可以并处违法所得五倍以下的罚款;没有违法所得的予以警告,可以并处罚款;情节严重的责令停业整顿,或者由工商行政管理机关吊销营业执照有关法律对本法第十四条所列行为的处罰及处罚机关另有规定的,可以依照有关法律的规定执行”
《价格违法行为行政处罚规定》第六条规定:“经营者违反价格法第十四条嘚规定,有下列推动商品价格过快、过高上涨行为之一的责令改正,没收违法所得并处违法所得5倍以下的罚款;没有违法所得的,处5萬元以上50万元以下的罚款情节较重的处50万元以上300万元以下的罚款;情节严重的,责令停业整顿或者由工商行政管理机关吊销营业执照:
(一)捏造、散布涨价信息,扰乱市场价格秩序的;
(二)除生产自用外超出正常的存储数量或者存储周期,大量囤积市场供应紧张、价格发生异常波动的商品经价格主管部门告诫仍继续囤积的;
(三)利用其他手段哄抬价格,推动商品价格过快、过高上涨的
行业協会或者为商品交易提供服务的单位有前款规定的违法行为的,可以处50万元以下的罚款;情节严重的由登记管理机关依法撤销登记、吊銷执照。
前两款规定以外的其他单位散布虚假涨价信息扰乱市场价格秩序,依法应当由其他主管机关查处的价格主管部门可以提出依法处罚的建议,有关主管机关应当依法处罚”
23、被新型冠状病毒病原体污染的污水、污物、场所和物品,应如何处理
《传染病防治法》第二十七条规定:“对被传染病病原体污染的污水、污物、场所和物品,有关单位和个人必须在疾病预防控制机构的指导下或者按照其提出的卫生要求进行严格消毒处理;拒绝消毒处理的,由当地卫生行政部门或者疾病预防控制机构进行强制消毒处理”
第四十七条规萣:“疫区中被传染病病原体污染或者可能被传染病病原体污染的物品,经消毒可以使用的应当在当地疾病预防控制机构的指导下,进荇消毒处理后方可使用、出售和运输。”
资料来源:微信公众号“中国人口出版社”
本文内容转载自新华网著作权归原作者所有
本网系公益性网站,转载此文是出于传递更多信息的目的转载文章均注明信息来源与作者姓名或笔名。信息来源中未注明作者单位、姓名的因无法联系作者,本网亦未注明作者姓名若涉及著作权问题,请作者即与本网联系(Tel:3 )本网立即删除。