--------------------------------------------------------------------------------------------
阻塞型同步(Blocking Synchronization)
Mutex、Semaphore
死锁(Deadlock)
• 两个迎面走来的人,都不愿给别人让路,都在等对方让路,导致谁也过不
去。
• 活锁(Livelock)
• 两个迎面走来的人,靠近的时候需要相互让路,一个人向自己的左边移动,另
外一个人向自己的右边移动这时候就会相撞。
• 饿死(Starvation)
• A站着等B开仓放粮,但B一直未去开仓,导致A饿晕了
• 一个线程经常长时间占有某个资源,其他线程就会饿死
• 优先级反转(Priority Inversion)
• 共享资源被较低优先级的进程/线程所拥有,较高优先级的进程/线程竞争该同
步资源未获得该资源,而使得较高优先级进程/线程反而推迟被调度执行的现
象。
• 解决办法
• 懒惰法:优先级继承(Priority Inheritance)
• 积极法:优先级顶置(Priority Overhead)
------------------------------------------------------------------------------------------------------
Lock-free应用
• Spinlock(自旋锁)
CAS
不会产生上下文切换(Context Switching)
pthread_spin_lock
使用准则:临界区尽量简短,不要有显式或者隐式的系统调用
• Seqlock(顺序锁)
• 写优先
• 实现原理
• 依赖一个序列计数器,当写者写入数据时,会得到一把锁,并且将序列值加 1。当
读者读取数据之前和之后,该序列号都会被读取,如果读取的序列号值都相同,则
表明写没有发生。反之,表明发生过写事件,则放弃已进行的操作,重新循环一
次,直至成功。
• RCU(Read-Copy-Update)
• 写操作分为写和更新两步,允许读操作在任何时刻无阻塞的运行
• WRRM(Write-Rarely-Read-Many)
--------------------------------------------------------------------------------------------------------
Spinlock vs Mutex 锁的比较(NGINX锁属于自旋锁 )
优点 缺点
Spinlock
1.线程状态不会切换 1.一直占用CPU
2.一直处于用户态,没有昂贵的系统调用 2.会锁住数据总线
Mutex
1.不会死等,得不到锁的时候会sleep 1.sleep时会陷入内核,发生状态切会sleep 换
2.需要昂贵的系统调用
总结:nginx的锁能实现线程切换,固然非常优秀
读写锁的使用
--------------------------------------------------------------------------------------------------------------
1.如果系统中的插入/删除操作很少,主要活动是搜索,那么基于单一锁的方法性能会很差。在这种情况下,应该考虑使用读写锁,即 pthread_rwlock_t。
这里使用两个锁定函数调用(pthread_rwlock_rdlock 和 pthread_rwlock_wrlock)管理同步,使用 pthread_setschedprio 调用设置写线程的优先级。如果没有写线程在这个锁上阻塞(换句话说,没有插入/删除请求),那么多个请求链表搜索的读线程可以同时操作,因为在这种情况下 一个读线程不会阻塞另一个读线程。如果有写线程等待这个锁,当然不允许新的读线程获得锁,写线程等待,到现有的读线程完成操作时写线程开始操作。如果不按 这种方式使用 pthread_setschedprio 设置写线程的优先级,根据读写锁的性质,很容易看出写线程可能会饿死。
总结:插入和删除次数少时,读用读锁,写和删除用写锁设置优先级。
应该了解的最后一个方法是 insert_after。同样,预期的使用模式要求调整数据结构的设计。如果一个应用程序使用前面提供的链表,它执行的插入和搜索操作数量差不多相同,但是删除操作很少,那么在插入期间锁住整个链表是不合适的。在这种情况下,最好允许在链表中的分离点(disjoint point)上执行并发插入,同样使用基于读写锁的方式。下面是构造链表的方法:
- 在两个级别上执行锁定:链表有一个读写锁,各个节点包含一个互斥锁。如果想节省空间,可以考虑共享互斥锁 — 可以维护节点与互斥锁的映射。
- 在插入期间,写线程在链表上建立读锁,然后继续处理。在插入数据之前,锁住要在其后添加新数据的节点,插入之后释放此节点,然后释放读写锁。
- 删除操作在链表上建立写锁。不需要获得与节点相关的锁。
- 与前面一样,可以并发地执行搜索。
进行测试比较,nginx的锁在不同情况下都有很大的优势,特别是并发上。
以上总结:如果非考虑到插入和删除次数小的情况使用读写锁外;锁住CPU和占用总线的问题不是很大,因为大
并发的nginx对这个问题都放到一边,可以说问题不大;那么所有的服务器锁定都使用nginx的汇编完全替代了!