一. linux为何须要临界段,信号量,互斥锁,自旋锁,原子操做?html
1.1. linux内核后期版本是支持多核CPU以及抢占式调度。这里就存在一个并发,竞争状态(简称竟态)。linux
1.2. 竞态条件 发生在两个或更多线程操纵一个共享数据项时,在多处理器(MP)计算机中也存在并发,其中每一个处理器中共享相同数据的线程同时执行数据库
1.3. 临界段,信号量,互斥锁,自旋锁,原子操做能够从不一样情形解决上述问题安全
二. 临界区(Critical Section)数据结构
2.1. 保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只容许一个线程对共享资源进行访问。若是有多个线程试图同时访问临界区,那么在有一个线 程进入后其余全部试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其余线程能够继续抢占,并以此达到用原子方式操 做共享资源的目的。 多线程
2.2. 临界区包含两个操做原语: 并发
EnterCriticalSection() 进入临界区 函数
LeaveCriticalSection() 离开临界区 atom
EnterCriticalSection()语句执行后代码将进入临界区之后不管发生什么,必须确保与之匹配的 spa
LeaveCriticalSection()都可以被执行到。不然临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本 进程内的线程,而不可用来同步多个进程中的线程。
2.3. MFC提供了不少功能完备的类,我用MFC实现了临界区。MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是 很是简单的。只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片断便可。Lock()后代 码用到的资源自动被视为临界区内的资源被保护。UnLock后别的线程才能访问这些资源。
三. 互斥量(Mutex)
3.1. 互斥量跟临界区很类似,只有拥有互斥对象的线程才具备访问资源的权限,因为互斥对象只有一个,所以就决定了任何状况下此共享资源都不会同时被多个线程 所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其余线程在得到后得以访问资源。互斥量比临界区复杂。由于使用互斥不只仅可以在同 一应用程序不一样线程中实现资源的安全共享,并且能够在不一样应用程序的线程之间实现对资源的安全共享。
3.2. 互斥量包含的几个操做原语:
CreateMutex() 建立一个互斥量
OpenMutex() 打开一个互斥量
ReleaseMutex() 释放互斥量
WaitForMultipleObjects() 等待互斥量对象
四. 信号量(Semaphores)
4.1. 信号量对象对线程的同步方式与前面几种方法不一样,信号容许多个线程同时使用共享资源,这与操做系统中的PV操做相同。它指出了同时访问共享资源的线程 最大数目。它容许多个线程在同一时刻访问同一资源,可是须要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()建立信号量 时即要同时指出容许的最大资源计数和当前可用资源计数。通常是将当前可用资源计数设置为最大资源计数,每增长一个线程对共享资源的访问,当前可用资源计数 就会减1,只要当前可用资源计数是大于0的,就能够发出信号量信号。可是当前可用计数减少到0时则说明当前占用资源的线程数已经达到了所容许的最大数目, 不能在容许其余线程的进入,此时的信号量信号将没法发出。线程在处理完共享资源后,应在离开的同时经过ReleaseSemaphore()函数将当前可 用资源计数加1。在任什么时候候当前可用资源计数决不可能大于最大资源计数。 PV操做及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时表明可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数
P操做 申请资源:
(1)S减1;
(2)若S减1后仍大于等于零,则进程继续执行;
(3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,而后转入进程调度。
V操做 释放资源:
(1)S加1;
(2)若相加结果大于零,则进程继续执行;
(3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,而后再返回原进程继续执行或转入进程调度。
4.2. 信号量包含的几个操做原语:
CreateSemaphore() 建立一个信号量
OpenSemaphore() 打开一个信号量
ReleaseSemaphore() 释放信号量
WaitForSingleObject() 等待信号量
五. 自旋锁(spin_lock)
5.1. 是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较相似,它们都是为了解决对某项资源的互斥使用。不管是互斥锁,仍是自旋锁,在任什么时候刻,最多只能有一个保持者,也就说,在任什么时候刻最多只能有一个执行单元得到锁。可是二者在调度机制上略有不一样。对于互斥锁,若是资源已经被占用,资源申请者只能进入睡眠状态。可是自旋锁不会引发调用者睡眠,若是自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是所以而得名。
5.2. 自旋锁和信号量的使用要点
(1)自旋锁不能递归
(2)自旋锁能够用在中断上下文(信号量不能够,由于可能睡眠),可是在中断上下文中获取自旋锁以前要先禁用本地中断,中断是不参与系统调度的
(3)自旋锁的核心要求是:拥有自旋锁的代码必须不能睡眠,要一直持有CPU直到释放自旋锁
(4)信号量和读写信号量适合于保持时间较长的状况,它们会致使调用者睡眠,所以只能在进程上下文使用,而自旋锁适合于保持时间很是短的状况,它能够在任何上下文使用。若是被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源很是合适,若是对共享资源的访问时间很是短,自旋锁也能够。可是若是被保护的共享资源须要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是能够被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的状况下才真正须要,在单CPU且不可抢占的内核下,自旋锁的全部操做都是空操做。
六. 原子操做
6.1. Linux原子概念
6.1.1. 所谓原子操做,就是“不可中断的一个或一系列操做”。
6.1.2. 原子操做,就是不能被更高等级中断抢夺优先的操做。你既然提这个问题,我就说深一点。因为操做系统大部分时间处于开中断状态,因此,一个程序在执行的时候可能被优先级更高的线程中断。而有些操做是不能被中断的,否则会出现没法还原的后果,这时候,这些操做就须要原子操做。就是不能被中断的操做。
6.1.3. 硬件级的原子操做:在单处理器系统(UniProcessor)中,可以在单条指令中完成的操做均可以认为是“原子操做”,由于中断只发生在指令边缘。在多处理器结构中(Symmetric Multi-Processor)就不一样了,因为系统中有多个处理器独立运行,即便能在单条指令中完成的操做也有可能受到干扰。在X86平台生,CPU提供了在指令执行期间对总线加锁的手段。CPU上有一根引线#HLOCK pin连到北桥,若是汇编语言的程序中在一条指令前面加上前缀"LOCK",通过汇编之后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能经过总线访问内存了,保证了这条指令在多处理器环境中的原子性。对于其余平台的CPU,实现各不相同,有的是经过关中断来实现原子操做(sparc),有的经过CMPXCHG系列的指令来实现原子操做(IA64)。本文主要探讨X86平台下原子操做的实现。
6.2. Linux内核两组原子操做接口:
6.2.1. 原子整数操做
原子操做一般针对int或bit类型的数据,可是Linux并不能直接对int进行原子操做,而只能经过atomic_t的数据结构来进行。定义于#include<asm/atomic.h>
6.2.2. 内核中提供的一些主要位原子操做函数
同时内核还提供了一组与上述操做对应的非原子位操做函数,名字前多两下划线。因为不保证原子性,所以速度可能执行更快。
七. 总结:
7.1. 互斥量与临界区的做用很是类似,但互斥量是能够命名的,也就是说它能够跨越进程使用。因此建立互斥量须要的资源更多,因此若是只为了在进程内部是用的话使 用临界区会带来速度上的优点并可以减小资源占用量。由于互斥量是跨进程的互斥量一旦被建立,就能够经过名字打开它。
7.2. 经过互斥量能够指定资源被独占的方式使用,但若是有下面一种状况经过互斥量就没法处理,好比如今一位用户购买了一份三个并发访问许可的数据库系统,能够根 据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操做,这时候若是利用互斥量就没有办法完成这个要求,信号灯对象能够说是一种资源计数 器。
7.3. 互斥锁与信号量的区别:
7.3.一、信号量通常以同步的方式对共享资源进行控制,而互斥锁经过互斥的方式对共享资源对其进行控制;
7.3.二、信号量能够对进程的共享资源进行控制,而互斥锁不行;
7.3.三、信号量的值为非负整数,而互斥锁的值只能为0或1;
7.3.四、互斥量的加锁和解锁必须由同一线程分别对应使用,信号量能够由一个线程释放,另外一个线程获得;mutex和二值信号量的区别在于mutex必须是同一个进程来释放
7.4. 自旋锁与互斥锁的区别:
7.4.一、由于自旋锁不会引发调用者睡眠,因此效率比较高
7.4.二、自旋锁比较适用于锁使用者保持锁时间比较短的状况。
7.4.三、自旋锁容易形成死锁,因此须要安全使用它;