乐趣区

关于c++:Linux多线程服务器编程笔记1

C++ 中实现线程平安的对象创立、回调与析构

  • 写出线程平安的 并不难,应用同步原语爱护外部状态即可
  • STL 大多类 都不是线程平安的,须要在内部加锁保障多个线程同时拜访

平安的对象创立

惟一的要求就是不要在结构期间泄露 this 指针,即

- 不要在构造函数中注册任何回调
- 不要把 this 指针传给跨线程的对象
- 即便在最初一行也不能够,因为这个类可能是基类,它的构造函数最初一行不等于结构实现

起因:在执行构造函数期间对象没有实现初始化,如果 this 指针被写了泄露给了其余对象,那么别的线程可能会拜访这个半成品

平安的对象销毁

对象析构函数的竞态条件

1. 析构某个对象时,如何得悉是否有他人在用它?2. 析构某个对象时,它是否可能正在被另一个线程析构?3. 调用某个对象的成员函数时,如何保障它仍旧活着?析构函数会不会碰巧执行到一半?

对象析构函数的设计难点

1.mutex 不是方法,因为锁只是用来 爱护对象外部数据 的,不能来爱护对象自身,不能解决任何问题

2. 面向对象中宽泛应用的三种对象关系,都依赖于对象晓得“其余对象是否还活着”的能力

a. composition
b. aggregation
c. association

其中,composition 在多线程中不会有什么麻烦,因为对象 x 的生命周期由其 owner 惟一管制,但后两者,对象的分割是借由 指针或援用 实现的,因而可能会呈现下面提到的竞态条件。

解决办法:shared_ptr / weak_ptr

即在对象间中引入一层中间层,不再应用裸指针,而是智能指针,作为一个辅助的管理者(援用计数)。

  • shared_ptr管制对象的生命周期,是强援用,只有有一个指向 x 对象的 shared_ptr 存在,x就不会析构,当援用计数降为 0 时或 reset() 时,x保障会被销毁。
  • weak_ptr不管制对象生命周期,但晓得对象是否或者,如果对象没死,那么能够晋升为无效的 shared_ptr, 从而援用对象,并保障在这个期间对象不析构,如果对象死了,那么会返回空的shared_ptr 晋升 行为是线程平安的。
  • shared_ptrweak_ptr 的计数是原子操作,性能不俗

然而

  • shared_ptrweak_ptr 自身的线程安全级别与 std::string 和 STL 的容器一样,见上面的探讨

对于 shared_ptr 的探讨

  • shared_ptr并不线程平安:能够多个线程同时读,不能多个线程同时写(析构算写)
  • shared_ptr的线程平安和它指向对象的线程平安是两回事
  • 访问共享的shared_ptr,须要加上互斥锁

    void read(){
      shared_ptr<Foo> localPtr;
      {MutexLockGuard lock(mutex);
          localPtr = globalPtr; // read globalPtr
      }
      // use localPtr since here,读写 localPtr 无需加锁,因为是栈上对象,只有以后线程可见
      do_it(localPtr); // 这里函数的参数应是 reference to const,防止复制
    }
  • weak_ptrshared_ptr 用错,可能会导致一些对象永远不被析构,通常做法是 owner 领有指向 child 的shared_ptr,child 持有 owner 的weak_ptr
  • 拷贝开销:因为拷贝会创立一份正本,须要批改援用计数,开销较大,但须要拷贝的中央不多,通常 shared_ptr 作为参数时都是const reference

RAII 资源获取即初始化

  • C++ 区别于其余编程语言的最重要的个性
  • 每一个明确的资源配置动作(如 new)都应该在繁多语句中执行,并在该寄居中立即将配置取得的资源交给 handle 对象(如shared_ptr), 程序中个别不呈现delete

线程平安的对象池

实现一个StcokFactory, 依据 key 返回 Stock 对象

版本 1 :利用 weak_ptrshared_ptr治理

class StockFactory : boost::noncopyable
{
 public:
  boost::shared_ptr<Stock> get(const string& key)
  {
    boost::shared_ptr<Stock> pStock;
    muduo::MutexLockGuard lock(mutex_);
    boost::weak_ptr<Stock>& wkStock = stocks_[key]; // 如果这里是 shared_ptr,则对象永远不会被析构
    pStock = wkStock.lock();
    if (!pStock)
    {pStock.reset(new Stock(key));
      wkStock = pStock; // wkStock is a reference 
    }
    return pStock;
  }

 private:
  mutable muduo::MutexLock mutex_;
  std::map<string, boost::weak_ptr<Stock> > stocks_;
};

问题 : 存在轻微的内存透露,map 中的 key:weak_ptr 会始终存在,在 key 无限时没什么问题,在 key 范畴很大时会造成内存透露

版本 2 : 利用 shared_ptr 的定制析构性能,在析构对象的同时析构

class StockFactory : boost::noncopyable
{
 public:

  boost::shared_ptr<Stock> get(const string& key)
  {
    boost::shared_ptr<Stock> pStock;
    muduo::MutexLockGuard lock(mutex_);
    boost::weak_ptr<Stock>& wkStock = stocks_[key];
    pStock = wkStock.lock();
    if (!pStock)
    {pStock.reset(new Stock(key),
                   boost::bind(&StockFactory::deleteStock, this, _1)); // 定制析构性能,绑定了一个对象的成员函数
      wkStock = pStock;
    }
    return pStock;
  }

 private:

  void deleteStock(Stock* stock)
  {printf("deleteStock[%p]\n", stock);
    if (stock)
    {muduo::MutexLockGuard lock(mutex_);
      stocks_.erase(stock->key());  // 如果 stocks_先于 stock 死亡,这里会 core dump
    }
    delete stock;  // 因为本人定制析构性能,须要手动写 delete
  }
  mutable muduo::MutexLock mutex_;
  std::map<string, boost::weak_ptr<Stock> > stocks_;
};

问题 : 如果stocks_ 先于 stock 死亡,stock析构,回调 stocks_erase函数时会报错,这个问题的实质和下面探讨的问题是一样的,即对象的 this 指针 是不足够判断对象生命周期的 ,下面的办法里,改用weak_ptr + shared_ptr 解决了问题,这里也一样

版本 3 : 利用 enable_shared_from_this 应用 shared_ptr 代替 this 指针

class StockFactory : public boost::enable_shared_from_this<StockFactory>,
                     boost::noncopyable
{
 public:

  boost::shared_ptr<Stock> get(const string& key)
  {
    boost::shared_ptr<Stock> pStock;
    muduo::MutexLockGuard lock(mutex_);
    boost::weak_ptr<Stock>& wkStock = stocks_[key];
    pStock = wkStock.lock();
    if (!pStock)
    {pStock.reset(new Stock(key),
                   boost::bind(&StockFactory::deleteStock,
                               shared_from_this(),
                               _1));
      wkStock = pStock;
    }
    return pStock;
  }

 private:

  void deleteStock(Stock* stock)
  {printf("deleteStock[%p]\n", stock);
    if (stock)
    {muduo::MutexLockGuard lock(mutex_);
      stocks_.erase(stock->key());  // This is wrong, see removeStock below for correct implementation.
    }
    delete stock; 
  }
  mutable muduo::MutexLock mutex_;
  std::map<string, boost::weak_ptr<Stock> > stocks_;
};

问题 : 因为stock 注册了 StockFactoryshared_ptr(bind 的时候会产生指针的复制),导 StockFactory 会等到所有 stock 都析构之后才析构,意外地缩短了生命。

版本 4 : 利用 enable_shared_from_this 应用weak_ptr 代替 this 指针,实现弱回调 : “ 如果对象还活着,就调用它的成员函数,否则疏忽之 ”

class StockFactory : public boost::enable_shared_from_this<StockFactory>,
                     boost::noncopyable
{
 public:
  boost::shared_ptr<Stock> get(const string& key)
  {
    boost::shared_ptr<Stock> pStock;
    muduo::MutexLockGuard lock(mutex_);
    boost::weak_ptr<Stock>& wkStock = stocks_[key];
    pStock = wkStock.lock();
    if (!pStock)
    {pStock.reset(new Stock(key),
                   boost::bind(&StockFactory::weakDeleteCallback,
                               boost::weak_ptr<StockFactory>(shared_from_this()),
                                // 必须有这步转型,才不会缩短 StockFactory 的生命周期
                                // boost:bind 拷贝的是实参类型不是形参类型
                               _1));
      wkStock = pStock;
    }
    return pStock;
  }

 private:
  static void weakDeleteCallback(const boost::weak_ptr<StockFactory>& wkFactory,
                                 Stock* stock)
  {printf("weakDeleteStock[%p]\n", stock);
    boost::shared_ptr<StockFactory> factory(wkFactory.lock());
    if (factory)
    {factory->removeStock(stock);
    }
    else
    {printf("factory died.\n");
    }
    delete stock; 
  }

  void removeStock(Stock* stock)
  {if (stock)
    {muduo::MutexLockGuard lock(mutex_);
      auto it = stocks_.find(stock->key());
      if (it != stocks_.end() && it->second.expired())
      {stocks_.erase(stock->key());
      }
    }
  }

 private:
  mutable muduo::MutexLock mutex_;
  std::map<string, boost::weak_ptr<Stock> > stocks_;
};

这下无论 Stock 还是 StockFactory 谁先析构都不会影响程序失常运行,利用智能指针解决了两个对象互相援用的问题。
当然,通常 Factory 对象是一个 singleton,并且失常运行期间不会销毁,这里只是为了展现弱回调计数。

多线程编程的 Tips

1. 包装底层的同步原语,借助对象的结构和析构加锁和解锁

int getvalue() const{MutexLockGuard lock(mutex_); // 对象的结构即加锁
    return value_; // 读取数据
} // 作用域完结,Guard 主动析构,解锁

2. 如果一个函数要所著雷同类型的两个对象,那么 为了保障始终按雷同的程序加锁,能够比拟 mutex 对象的地址始终加锁地址较小的那个,比方:

void swap(counter& a, counter& b){MutexLockGuard aLock(a.mutex_);
    MutexLockGuard bLock(b.mutex_);
    // 替换 a,b 的数据
}

如果线程 A 执行 swap(a,b) 而线程 B 执行 swap(b,a) 将会产生死锁。

3. 尽管本章讲如何平安应用、析构跨线程的对象,然而尽量不要用跨线程的对象,用流水线,生产者消费者,工作队列等有法则的机制,最低限度的共享数据,是比拟好的多线程编程办法

退出移动版