在使用共享内存的应用程序中,程序员必须特别留意保护共享资源,防止共享资源并发访问。
一、临界区和竞争条件
1.1 临界区和竞争条件
所谓临界区就是访问和操作共享数据代码段。多个执行线程并发访问同一个资源通常是不安全的,为了避免在临界区中并发访问,编程者必须保证这些代码原子地执行。
如果两个执行线程有可能处于同一个临界区中同时执行,如果这种情况确实发生了,我们就称它是竞争条件。因为竞争引起的错误非常不易重现,所以调试这种错误才会非常困难。
避免并发和防止竞争条件称为同步。
为什么要保护?
有些事务必须完整地发生,要么干脆不发生,但是绝不能打断。
二、加锁
锁提供的就是这种机制:它就如同一把门锁,门后的房间可想象成要给临界区。在一个指定的时间内,房间里只能有一个执行线程存在,当一个线程进入房间后,它会锁住身后的房门。当它结束对共享数据的操作后,就会走出房间,打开门锁。如果另一个线程在房门上锁时来了,那么它就必须等待房间内的线程出来并发开门锁后,才能进入房间。
线程持有锁,锁保护了数据。
2.1 造成并发执行的原因
内核中有类似可能造成并发执行的原因,它们是:
- 中断----中断几乎可以在任何时刻异步发生,也就可能随时打断当前正在执行的代码
- 软中断和tasklet----内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在执行的代码
- 内核抢占----因为内核具有抢占型,所以内核中的任务可能会被另一任务抢占
- 睡眠及与用户空间的同步----在内核执行的进程可能会睡眠,这就会唤醒调度程序,从而导致调度一个新的用户进程执行
- 对称多处理----两个或多个处理器可以同时执行代码
真正困难的就是发现上述的潜在并发执行的可能,并有意识地采取某些措施来防止并发执行。
其实,真正用锁来保护共享资源并不困难,尤其是在设计代码的早期就这么做了,事情就更简单了。
辨认出真正需要共享的数据和响应的临界区,才是真正有挑战性的地方。
2.2 了解要保护些什么
找出哪些数据需要保护是关键所在。
线程中的局部数据仅仅被它访问,显然不需要保护。
三、死锁
四、争用和扩展性