锁
锁
锁,从概念上可以分为悲观锁与乐观锁两大类,悲观锁譬如互斥量(Mutex
锁必须是原子性操作实现,决不能中途打断,由处理器原语支持。锁的意义在于将操作做为一个执行单元以一种原子方式执行而不被打断,多线程下也不会互相干扰。但是锁会影响性能,这是因为一个加锁的临界资源 在被访问前必须获取对应的锁,获取该锁的线程将以独占的方式访问临界区。如果此时有其他线程同时访问临界区,则会因为无法获取这个锁而阻塞,显然,在临界区强行通过加锁使线程执行串行化是需要牺牲一定的性能的。
互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
如果共享和可变都无法避免,那么只有使用互斥
if(!occipied){ // 检查
occupied = true; // 占锁
critical_region(); // 临界区
occupied = false; // 释放锁
}
最朴素的互斥手段,就是进入临界区之前,用
管程即是从编译器的层面保证了临界区的互斥,譬如在
不使用wait()
,notifyAll()
等条件变量都是基于此。
// 线程 A
while(true){
while(turn != 0){} // 锁被占用,循环忙等
critical_region();
turn = 1; // 释放锁
noncritical_region();
}
// 线程 B
while(true){
while(turn != 1){} // 锁被占,循环忙等
critical_region();
turn = 0; // 释放锁
noncritical_region();
}
理解了count==1
的
简单的说,互斥锁保护了一个临界区,在这个临界区中,一次最多只能进入一个线程。如果有多个进程在同一个临界区内活动,就有可能产生竞态条件
读写锁从广义的逻辑上讲,也可以认为是一种共享版的互斥锁。如果对一个临界区大部分是读操作而只有少量的写操作,读写锁在一定程度上能够降低线程互斥产生的代价。
条 件变量允许线程以一种无竞争的方式等待某个条件的发生。当该条件没有发生时,线程会一直处于休眠状态。当被其它线程通知条件已经发生时,线程才会被唤醒从 而继续向下执行。条件变量是比较底层的同步原语,直接使用的情况不多,往往用于实现高层之间的线程同步。使用条件变量的一个经典的例子就是线程池
保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进 入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共 享资源的目的。
在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程 度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起 其他线程的长时间等待。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
自旋锁对信号量
需求 建议的加锁方法
低开销加锁 优先使用自旋锁 短期锁定 优先使用自旋锁 长期加锁 优先使用信号量 中断上下文中加锁 使用自旋锁 持有锁是需要睡眠、调度 使用信号量