当操作系统的两个或以上线程访问和修改同一地址的内存空间时(在并发语境下被称为临界区),就会面临并发问题,这时候就要用到锁。
锁的实现
最简单的实现方法是,利用CPU的允许中断和禁止中断(disableInterupt)。
这样可以保证单个CPU上的临界区代码原子性。
但这会带来三个问题:
不支持多CPU(多处理器)。因为只禁止了单个CPU的中断操作,另一CPU上的线程还是有可能访问临界区的
二是关闭中断会使CPU的其它功能受到影响,这是很严重的问题;
三是无法防止恶意代码的运行。
一种改进方案是,使用CPU的硬件支持。使用一个互斥信号量,在一条线程进入临界区后,将互斥量设置为1,如果其它线程进入临界区时,发现信号量为1,那么线程会进入一个while循环等待。这种方法,被称作“自旋锁”。
另外一种锁的实现,明天更新。
并发数据结构
并发计数器
并发列表
并发散列表
并发队列
并发程序设计所需的原语
1.锁
锁的意义在上面已经说过,在不同线程访问临界区时,要对临界区做lock和unlock的操作,避免数据冲突
2.条件变量
条件变量的意义在于,可以让线程间可以协作。比如父线程创建了一个子线程,然后父线程需要等子线程执行完毕后再继续运行。
如果仅仅使用自旋的方式,那么CPU要不断切换到不工作的线程,这样会导致CPU的性能大大降低。另外一种方式是,使用等待队列。当线程需要等待时,使用wait()方法,并需要一个条件变量作为唤醒时的手段。当激活条件满足时,使用条件变量调用signal()方法,重启线程。
NOTE:发信号时总是持有锁
因为wait和signal总是会在默认线程在操作锁的情况下进行方法调用,所以在调用这些方法(wait和signal)的时候,要确保线程持有锁。
网友评论