• muduo第二章死锁问题


    muduo 第二章 多线程死锁相关

    2.1 死锁例子1

    #include "../Mutex.h"
    #include "../Thread.h"
    #include 
    #include 
    
    using namespace muduo;
    
    class Foo
    {
     public:
      void doit() const;
    };
    
    MutexLock mutex;
    std::vector foos;
    
    void post(const Foo& f)
    {
      MutexLockGuard lock(mutex);
      foos.push_back(f);
    }
    
    void traverse()
    {
      MutexLockGuard lock(mutex);
      for (std::vector::const_iterator it = foos.begin();
          it != foos.end(); ++it)
      {
        it->doit();
      }
    }
    
    void Foo::doit() const
    {
      Foo f;
      post(f);
    }
    
    int main()
    {
      Foo f;
      post(f);
      traverse();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    代码分析:上述代码有两个地方有问题

    • mutex 是非递归的:当 traverse() 函数中再次调用 doit 函数时,会造成死锁:traverse 函数中会获得 mutex 锁;doit 函数中会调用 post 函数,post 函数中也会去想获得 mutex 锁,此时,由于 traverse 函数中 未将 mutex 锁释放,post 函数无法获得 mutex 锁,导致死锁。
    • mutex 是递归的:不会因为加锁出问题,但是 traverse 函数中,调用 doit ,会改变 foos 对象,导致遍历 foos 对象的迭代器失效,程序偶尔出现 crash。

    针对上述代码中出现的问题,将上述代码做出如下修改:

    • 对于死锁问题:改变 traverse 函数中 mutex 的临界区,限定临界区的范围在 traverse 函数的 37-39 行。这样当 traverse 函数中调用 doit 函数时,traverse 函数中获得的锁已经被析构了。
    • 对于遍历 foos 对象的同时,改变 foos 对象导致迭代器失效的问题,采用 shared_ptr,post 函数中,使用 shared_ptr 的 unique 方法判断下全局变量的对象 g_foos 是否是唯一的,如果是唯一的就原地修改,否则说明有其他线程正在读取,就不能修改,需要复制一份,修改副本。
    // ./muduo/recipes/thread/test/CopyOnWrite_test.cc
    #include "../Mutex.h"
    #include "../Thread.h"
    #include 
    #include 
    #include 
    
    using namespace muduo;
    
    class Foo
    {
     public:
      void doit() const;
    };
    
    typedef std::vector FooList;
    typedef boost::shared_ptr FooListPtr;
    FooListPtr g_foos;
    MutexLock mutex;
    
    void post(const Foo& f)
    {
      printf("post\n");
      MutexLockGuard lock(mutex);
      if (!g_foos.unique())
      {
        g_foos.reset(new FooList(*g_foos));
        printf("copy the whole list\n");
      }
      assert(g_foos.unique());
      g_foos->push_back(f);
    }
    
    void traverse()
    {
      FooListPtr foos;
      {
        MutexLockGuard lock(mutex);
        foos = g_foos;
        assert(!g_foos.unique());
      }
    
      // assert(!foos.unique()); this may not hold
    
      for (std::vector::const_iterator it = foos->begin();
          it != foos->end(); ++it)
      {
        it->doit();
      }
    }
    
    void Foo::doit() const
    {
      Foo f;
      post(f);
    }
    
    int main()
    {
      g_foos.reset(new FooList);
      Foo f;
      post(f);
      traverse();
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    总结:

    • 避免死锁的方法:缩小临界区的范围
    • 避免便利 vector 对象的同时,改变 vector 导致的临界区失效的方法:使用 shared_ptr 判断要改变的对象是否有线程正在对其进行读操作,如果有,使用 reset 方法复制一份,改变其副本。保证在对这个对象进行写操作时,该对象是 unique

    2.2 死锁例子2

    #include "../Mutex.h"
    #include "../Thread.h"
    #include 
    #include 
    
    class Request;
    
    class Inventory
    {
     public:
      void add(Request* req)
      {
        muduo::MutexLockGuard lock(mutex_);
        requests_.insert(req);
      }
    
      void remove(Request* req) __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        requests_.erase(req);
      }
    
      void printAll() const;
    
     private:
      mutable muduo::MutexLock mutex_;
      std::set requests_;
    };
    
    Inventory g_inventory;
    
    class Request
    {
     public:
      void process() // __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        g_inventory.add(this);
        // ...
      }
    
      ~Request() __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        sleep(1);
        g_inventory.remove(this);
      }
    
      void print() const __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        // ...
      }
    
     private:
      mutable muduo::MutexLock mutex_;
    };
    
    void Inventory::printAll() const
    {
      muduo::MutexLockGuard lock(mutex_);
      sleep(1);
      for (std::set::const_iterator it = requests_.begin();
          it != requests_.end(); 
          ++it)
      {
        (*it)->print();
      }
      printf("Inventory::printAll() unlocked\n");
    }
    
    /*
    void Inventory::printAll() const
    {
      std::set requests
      {
        muduo::MutexLockGuard lock(mutex_);
        requests = requests_;
      }
      for (std::set::const_iterator it = requests.begin();
          it != requests.end();
          ++it)
      {
        (*it)->print();
      }
    }
    */
    
    void threadFunc()
    {
      Request* req = new Request;
      req->process();
      delete req;
    }
    
    int main()
    {
      muduo::Thread thread(threadFunc);
      thread.start();
      usleep(500 * 1000);
      g_inventory.printAll();
      thread.join();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    上述代码存在的问题:

    • 死锁问题:
      • 程序开始运行后,会有两个线程,一个是 threadFunc 线程 thread.start() 后执行 threadFunc 函数,一个是 main 线程 执行 g_inventory.printAll()
      • thread.start() 开始执行后,main 函数会 sleep 一小会儿,当 main 函数执行到 g_inventory.printAll(); 这里的时候,thread 线程很可能已经开始执行 delete req; 此时可能会持有 Inventory 中的 mutex_ 锁并已经持有 Request 中的锁 mutex_;main 线程 执行 g_inventory.printAll(); 时,会先持有 Inventory 中的锁,此时会造成死锁。(简言之,加锁顺序相反,造成死锁。)在这里插入图片描述
    • 析构问题:一个线程正在析构对象,另一个线程在调用它的成员函数
      • 当 threadFunc 线程要析构 requests_ 对象的同时,main 线程可能正在遍历

    改进版:

    // ./muduo/recipes/thread/test/RequestInventory_test.cc
    #include "../Mutex.h"
    #include "../Thread.h"
    #include 
    #include 
    #include 
    
    class Request;
    
    class Inventory
    {
     public:
      Inventory()
        : requests_(new RequestList)
      {
      }
    
      void add(Request* req)
      {
        muduo::MutexLockGuard lock(mutex_);
        if (!requests_.unique())
        {
          requests_.reset(new RequestList(*requests_));
          printf("Inventory::add() copy the whole list\n");
        }
        assert(requests_.unique());
        requests_->insert(req);
      }
    
      void remove(Request* req) // __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        if (!requests_.unique())
        {
          requests_.reset(new RequestList(*requests_));
          printf("Inventory::remove() copy the whole list\n");
        }
        assert(requests_.unique());
        requests_->erase(req);
      }
    
      void printAll() const;
    
     private:
      typedef std::set RequestList;
      typedef boost::shared_ptr RequestListPtr;
    
      RequestListPtr getData() const
      {
        muduo::MutexLockGuard lock(mutex_);
        return requests_;
      }
    
      mutable muduo::MutexLock mutex_;
      RequestListPtr requests_;
    };
    
    Inventory g_inventory;
    
    class Request
    {
     public:
      Request()
        : x_(0)
      {
      }
    
      ~Request() __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        x_ = -1;
        sleep(1);
        g_inventory.remove(this);
      }
    
      void process() // __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        g_inventory.add(this);
        // ...
      }
    
      void print() const __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        // ...
        printf("print Request %p x=%d\n", this, x_);
      }
    
     private:
      mutable muduo::MutexLock mutex_;
      int x_;
    };
    
    void Inventory::printAll() const
    {
      RequestListPtr requests = getData();
      sleep(1);
      for (std::set::const_iterator it = requests->begin();
          it != requests->end();
          ++it)
      {
        (*it)->print();
      }
    }
    
    void threadFunc()
    {
      Request* req = new Request;
      req->process();
      delete req;
    }
    
    int main()
    {
      muduo::Thread thread(threadFunc);
      thread.start();
      usleep(500*1000);
      g_inventory.printAll();
      thread.join();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 上述代码依然存在死锁以及 Request 对象析构时 的竞态条件
      • 死锁问题
      • 对象析构时的竞态条件:printAll() 函数中,使用 getData() 来获取 requests_ 对象,相当于这个对象的引用计数 +1,本质上还是操作这个对象,所以依然存在一个线程析构这个对象的同时另一个线程调用这对象的函数(???是因为这个原因嘛)

    如下代码改进的点:

    • g_inventory对象remove Request 对象的时候,不使用 Request 类的裸指针 this(指向这个对象本身的指针),使用裸指针会导致同一个对象释放两次的问题。而是使用 shared_from_this()
    #include "../Mutex.h"
    #include "../Thread.h"
    #include 
    #include 
    #include 
    #include 
    
    class Request;
    typedef boost::shared_ptr RequestPtr;
    
    class Inventory
    {
     public:
      Inventory()
        : requests_(new RequestList)
      {
      }
    
      void add(const RequestPtr& req)
      {
        muduo::MutexLockGuard lock(mutex_);
        if (!requests_.unique())
        {
          requests_.reset(new RequestList(*requests_));
          printf("Inventory::add() copy the whole list\n");
        }
        assert(requests_.unique());
        requests_->insert(req);
      }
    
      void remove(const RequestPtr& req) // __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        if (!requests_.unique())
        {
          requests_.reset(new RequestList(*requests_));
          printf("Inventory::remove() copy the whole list\n");
        }
        assert(requests_.unique());
        requests_->erase(req);
      }
    
      void printAll() const;
    
     private:
      typedef std::set RequestList;
      typedef boost::shared_ptr RequestListPtr;
    
      RequestListPtr getData() const
      {
        muduo::MutexLockGuard lock(mutex_);
        return requests_;
      }
    
      mutable muduo::MutexLock mutex_;
      RequestListPtr requests_;
    };
    
    Inventory g_inventory;
    
    class Request : public boost::enable_shared_from_this
    {
     public:
      Request()
        : x_(0)
      {
      }
    
      ~Request()
      {
        x_ = -1;
      }
    
      void cancel() __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        x_ = 1;
        sleep(1);
        printf("cancel()\n");
        g_inventory.remove(shared_from_this());
      }
    
      void process() // __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        g_inventory.add(shared_from_this());
        // ...
      }
    
      void print() const __attribute__ ((noinline))
      {
        muduo::MutexLockGuard lock(mutex_);
        // ...
        printf("print Request %p x=%d\n", this, x_);
      }
    
     private:
      mutable muduo::MutexLock mutex_;
      int x_;
    };
    
    void Inventory::printAll() const
    {
      RequestListPtr requests = getData();
      printf("printAll()\n");
      sleep(1);
      for (std::set::const_iterator it = requests->begin();
          it != requests->end();
          ++it)
      {
        (*it)->print();
      }
    }
    
    void threadFunc()
    {
      RequestPtr req(new Request);
      req->process();
      req->cancel();
    }
    
    int main()
    {
      muduo::Thread thread(threadFunc);
      thread.start();
      usleep(500*1000);
      g_inventory.printAll();
      thread.join();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
  • 相关阅读:
    MySQL下载步骤详解
    react面试题
    C++经典面试题:内存泄露是什么?如何排查?
    pdfh5在线预览pdf文件
    复习三:线性表
    考研是为了逃避找工作的压力吗?
    小小Javaer没用过AtomicInteger?
    【ISP】噪声--sensor(2)
    国内首档智能汽车综艺《X相对论》定档,众多专家云集,但最懂车主的是她?
    性能优化-如何爽玩多线程来开发
  • 原文地址:https://blog.csdn.net/qq_31672701/article/details/126519322