读Muduo源码笔记---1

  1. 对象销毁时出现的竞态条件:
  • 析构对象时,其他线程是否正在执行该对象的成员函数;
  • 在执行成员函数期间,对象不会被其他线程析构;
  • 在调用成员函数之前,如何确定对象还活着。
  1. 线程安全的类:
  • 多线程访问时,变现出正确的行为;
  • 无论操作系统如何调度这些线程,以及线程的执行顺序;
  • 调用端代码不需要额外的同步。
  1. 简单的线程安全类
class Counter

{

  public:

    Counter():value_(0){}

int value() const;

int getAndIncrease();

 private:

    int value_;

    mutable MutexLock mutex_;

};



int Counter::value() const

{

  MutexLockGuard lock(mutex_);

  return value_;

}

int Counter::getAndIncrease()

{

  MutexLockGuard lock(mutex_);

  int ret=value_++;

  return ret;

}

每个对象都有自己的mutex_,因此不同对象之间不构成锁争用。

  1. 存在的问题:销毁太难,析构函数调用的前提是成员变量mutex_被销毁。无法保证析构的线程安全。
  2. C++内存问题:
  • 缓冲区溢出;
  • 空悬指针/野指针
  • 重复释放
  • 内存泄漏
  • 不配对的new/delete
  • 内存碎片

线程同步精要

  1. 线程同步四项原则

  • 尽量最低限度地共享对象,减少需要同步的场合。
  • 使用高级并发编程构件。
  • 最后不得已必须使用底层原语时,只用非递归的互斥器和条件变量,慎用读写锁,不要用信号量。
  1. 互斥器(mutex)使用原则:
  • 用RAII手法封装mutex的创建、销毁、加锁、解锁。
  • 不可重入mutex
  • Lock()和unlock()函数的功能交给Guard对象的构造和析构。
  • 不使用跨进程的mutex。
  • 加解锁在同一个线程。
  • 必要时可考虑用PTHREAD_MUTEX_ERRORCHECK来拍错。
  1. 锁小结

互斥锁保护了一个临界区,在这个临界区中,一次最多只能进入一个线程。如果有多个进程在同一个临界区内活动,就有可能产生竞态条件(race condition)导致错误。

读写锁从广义的逻辑上讲,也可以认为是一种共享版的互斥锁。如果对一个临界区大部分是读操作而只有少量的写操作,读写锁在一定程度上能够降低线程互斥产生的代价。

条件变量允许线程以一种无竞争的方式等待某个条件的发生。当该条件没有发生时,线程会一直处于休眠状态。当被其它线程通知条件已经发生时,线程才会被唤醒从而继续向下执行。

可递归锁与非递归锁:二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。

  1. MutexLock mutex;  
  2.   
  3. void foo()  
  4. {  
  5.     mutex.lock();  
  6.     // do something  
  7.     mutex.unlock();  
  8. }  
  9.   
  10. void bar()  
  11. {  
  12.     mutex.lock();  
  13.     // do something  
  14.     foo();  
  15.     mutex.unlock();   
  16. }  

如果MutexLock锁是个非递归锁,则这个程序会立即死锁。但是这并不意味着应该用递归锁去代替非递归锁。递归锁用起来固然简单,但往往会隐藏某些代码问题。比如调用函数和被调用函数以为自己拿到了锁,都在修改同一个对象,这时就很容易出现问题。因此在能使用非递归锁的情况下,应该尽量使用非递归锁,因为死锁相对来说,更容易通过调试发现。程序设计如果有问题,应该暴露的越早越好。

猜你喜欢

转载自blog.csdn.net/qq_28840229/article/details/83094692