一、原子操作
就是不能被更高等级中断抢夺优先的单指令操作。
二、同步与锁
1、二元信号量(Binary Semaphore)(任何进程中的任何线程可访问)
是最简单的一种锁,它只有两种状态:占用 与 非占用。它适合只能被唯一一个线程独占访问
的资源。当二元信号量处于非占用状态下时,第一个试图取得该二元信号量的线程会获得该
锁,并将二元信号量置为占 用状态,此后其他的所有试图获取该二元信号量的线程将会等待,
直到该锁被释放。
2、多元信号量简称信号量(Semaphore)(任何进程中的任何线程可访问)
允许多个线程并发访问的资源。它是一个很好的选择。一个初始值为N的信号量允许N个线程
并发访问。线程访问资源的时候首先获取信号量,进行如下操作:
-- 将信号量的值减1
-- 如果信号量的值小于0,则进入等待状态,否则继续执行。
访问完资源后,线程释放该信号量,进行如下操作:
-- 将信号量的值加1.
-- 如果信号量的值小于1,唤醒一个等待的线程。( 如果信号量的值大于0,则唤醒等待的线程 )
3、互斥量(Mutex)(任何进程中的任何线程可访问,但必须获得互斥量的线程自己释放)
和二元信号量很像,资源仅同时允许一个线程访问,但和信号量不同的是,信号量在整个系
统内可以被任意线程获取并释放,也就是说,同一个信号量可以在系统内 的一个线程获取之后
由另一个线程释放。而互斥体则要求哪个线程获取了互斥体,哪个线程就要负责释放这个
锁,其他线程越俎代庖去释放互斥体是无效的。
4、临界区(Critical Section)(本进程中的任何线程可访问)
是比互斥体更加严格的同步手段,在术语中,把临界区的锁的获得称为进入临界区,而把锁
的释放称为离开临界区。临界区和互斥体与信号量的区别在于,互斥体和信号量在系统内的任
何线程里都是可见的,也就是说,一个进程创建了互斥体或信号量,另一个进程试图获取该锁
是合法的。然而,临界区的作用范围仅限于本进程,其他的进程无法获取该锁。
除此之外,临界区具有和互斥体相同的性质。
5、读写锁(Read-Write Lock)
致力于一种更加特定的场合的同步。对于一段数据,多个线程同时读取总是没有问题的,但
假设操作不是原子的,只要任何一个线程试图对这个数据进行修改,就必须使用同步手段来避
免出错。如果我们使用上述信号量、互斥量或临界区中任何一种来进行同步,尽管可以保证程
序正确,但对于读取频繁,而仅仅偶尔写入情况会显得非常低效。读写锁可以避免这个问题。
对于同一个锁,读写锁有两种获取方式,共享的(Shared)或独占的(Exclusive)。当锁处于
自由的状态时,试图以任何一种方式获取锁都能成功,并将锁至于对应的状态。如果
锁处于自由的状态时,试图以任何一种方式取得锁仍然会成功,此时这个锁分配给多个线
程。然而,如果其他线程试图以独占的方式获取已经处于共享状态的锁,那么它将必须等待被
所
有的线程释放。相应地,处于独占状态的锁将阻止任何其他线程获取该锁,不论它们试图以
哪种方式获取。
6、条件变量(Condition Variable)
作为一种同步手段,作用类似于一个栅栏。对于条件变量,线程可以有两种操作,首先线程
可以等待条件变量,一个条件变量可以被多个线程等待。其次,线程可以唤醒条件变量,此时
某个或所有等待此条件变量的线程都会被唤醒并继续支持。也就是说,使用条件变量可以让许
多线程一起等待某个事件发生,当事件发生时(条件变量被唤醒),所有的线程可以一起恢复
执行。
三、可重入(Reentrant)
一个函数被重入,表示这个函数没有执行完成,由于外部因素或内部调用,又一次进入该函
数执行。一个函数要被重入,只有两种情况:
(1)多个线程同时执行这个函数。
(2)函数自身(可能是经过多层调用之后)调用自身
一个函数被称为可重入的,表明该函数被重入之后不会产生任何不良后果。一个函数要成为
可重入的,必须满足如下几个特点:
- 不使用任何(局部)静态或全局的非const变量
- 不返回任何(局部)静态或全局的非const变量指针
- 仅依赖于任何单个资源的锁(mutex等)
- 不调用任何不可重入的函数
四、过度优化
我们可以使用volatile关键字试图阻止过度优化,volatile基本可以做到两件事:
(1)阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回
(2)阻止编译器调整操作volatile变量的指令顺序