C++多线程Linux多线程服务端编程使用muduo C++网络库:线程同步精要

本文是 C++多线程Linux多线程服务端编程:使用muduo C++网络库的学习笔记

线程同步的四项原则
  1. 首要的原则是最低限度地共享对象,减少需要同步的场合
  2. 其次是使用高级的并发编程构建,如TasjQueue、Producer-Consumer Queue、CountDownLatch 等等
  3. 最后不得已使用同步原语时,只用非递归的互斥器和条件变量,慎用读写锁,不要用信号量
  4. 除了使用 atomic 整数之外,不自己编写 lock-free 代码,也不要用内核级同步原语
互斥器

主要原则:

  • 使用 RAII 手法封装 mutex 的创建、销毁、加锁、解锁这四个操作
  • 只用不可重入的 mutex
  • 不手动调用 lock() 和 unlock() 函数,一切交给栈上的 Gurad 对象的构造和析构函数负责。
  • 每次构造 Guard 对象的时候,思考调用栈上已经持有的锁,防止因加锁顺序不同导致的死锁。

次要原则

  • 不使用跨进程的 mutex ,进程间通信只用 TCP sockets
  • 加锁、解锁在同一个线程,线程 a 不能去 unlock 线程 b 已经锁住的 mutex ( RAII 自动保证)
  • 别忘解锁( RAII 自动保证)
  • 不重复解锁( RAII 自动保证)
  • 必要的时候可以考虑用 PTHREAD_MUTEX_ERRORCHECK 来排错
条件变量

wait 端
wait() 前需要用 while() 判断条件,为了防止虚假唤醒
signal 端

  1. 不一定要在 mutex 已经上锁的情况下调用 signal
  2. 在 signal 之前一般要修改布尔表达式
  3. 修改布尔表达式通常要用 mutex 保护
  4. 注意区分 signal 与 broadcast :“broadcast 通常用于表明状态变化,signal 通常用于表示资源可用”
    不要使用读写锁和信号量
  • 从正确性方面来说,一种典型的易犯错误是在持有 read lock 的时候修改了共享数据。
  • 从性能方面来说,读写锁不见得比普通 mutex 更高效。无论如何 reader lock加锁的开销不会比 mutex lock 小,因为它要更新当前 reader 的树木,如果临界区很小,锁竞争不激烈,那么 mutex 往往更快
  • reader lock 可能允许提升为 writer lock,也可能不允许提升
  • 通常 reader lock是可重入的,writer lock是不可重入的,但是为了防止 writer 饥饿,writer lock通常会阻塞后来的reader lock,因此 reader lock在重入的时候可能死锁;另外在追求低延迟读取的场合也不适用读写锁
利用 shared_ptr 实现 copy-on-write

原理:

  • shared_ptr 是引用计数型智能指针,如果当前只有一个观察者,那么引用计数的值为1
  • 对于 write 端,如果发现引用计数为1,这时可以安全地修改共享对象,不用担心有人正在读它
  • 对于 read 端,在读之前把引用计数+1,读完之后-1,这保证在读的期间其引用计数大于1,防止并发写
  • 在 writer 端,引用计数大于1,可以使用 copy-on-wirte 处理

post 和 traverse 修改后分析
读:之前在读的整个期间都要持有锁(害怕写改变了读的数据),利用智能指针,在读期间 copy 后即可释放锁,之后如果再读,直接读 copy后的指针就可以了(通过智能指针的引用计数保证了,资源不会被释放与修改)
写:之前要等待整个读进行完毕才能写,修改后利用智能指针的引用计数判断是否有在读,没有直接改,有的话,复制后再改
修改之后,即使doit里重入了 traverse 也不会发生死锁
练习
C++多线程Linux多线程服务端编程使用muduo C++网络库:线程同步精要_第1张图片
附上traverse代码
C++多线程Linux多线程服务端编程使用muduo C++网络库:线程同步精要_第2张图片
错误一:由于tarverse中锁已经修改成提前释放,所以后半段代码不受锁的控制,导致可以读到锁内的数据
错误二:和错误三类似
错误三:假设有a,b两个线程分别并发执行post(f1),post(f2),由于不用的时序,结果集会出现 {f1} ,{f2} , { f1 , f2 }三种情况,显然我们想要的是第三种

你可能感兴趣的:(乱七八糟)