《Linux多线程服务端编程-使用muduo C++网络库》学习笔记——第一章

本文目录

  • 第1章 线程安全的对象生命期管理
    • 构造不难
    • 销毁太难
    • 线程安全的Observer的难点
    • 智能指针shared_ptr/weak_ptr
      • shared_ptr
      • weak_ptr
      • 引申:unique_ptr
    • 插曲:系统地避免各种指针错误
    • 论shared_ptr的线程安全
    • shared_ptr的技术陷阱
    • 对象池
      • enable_shared_from_this
      • 弱回调

第1章 线程安全的对象生命期管理

对象在销毁时,出现的竞态条件:

  1. 析构时,对象的函数被其他线程执行;
  2. 正在执行对象的函数,如何保证不被其他线程析构;
  3. 执行对象的函数之前,这个对象是否还活着。

C++标准库的多数类不符合线程安全,比如vector、string等等。

构造不难

对象构造时从以下2点考虑到线程安全:

  1. 不可以在构造函数中注册回调函数,比如加入观察者。。。。
  2. 不可以在构造中,把this传给其他线程。

注册回调的方式应该是二段式构造:构造函数+initalize(),如下:

class Foo : public Observer
{
public:
    Foo();
    void observe(Observer* s)
    {
        s->register_(this);
    }
};

Foo* pFoo = new Foo;
Obervable* s = getSubject();
pFoo->observe(s);//二段式构造

销毁太难

空悬指针(dangling pointer):指向已经销毁的对象。
野指针(wild pointer):未经初始化的指针。

对于普通的成员函数,只需要使用mutex保证临界区不会重叠,就是线程安全的。但是mutex本身不是一直有效,析构会把mutex销毁!对于以下两个函数,使用mutex保护析构:

Foo::~Foo()
{
    Lockguard m(mutex_);
    //...
}
Foo::update()
{
    Lockguard m(mutex_);
    //...
}

//thread A
delete x;
x = nullptr;
//thread B
if (x)
{
    x->update();
}

以上代码,如果线程A首先执行,线程B阻塞在update调用之前,那么将x赋值为空指针的做法,完全无法避免竞争,线程B触发core dump。以上例子,说明对象销毁之后,把指针置为nullptr完全没用。作为数据成员的mutex不能保护析构。对于基类来说,在进入基类的析构函数之前,派生类的析构已经调用了,说明mutex也无法保护整个析构过程。

此外,mutex可能导致死锁,参考以下代码:

void swap(Foo& a, Foo& b)
{
    Lockguard m1(a.mutex_);
    Lockguard m2(b.mutex_);
    //...
}
//thread A
swap(a, b);

//thread B
swap(b, a);

总结:一个函数要锁住多个相同类型的对象,必须始终按照相同的顺序加锁,实际做法可以按照地址大小顺序加锁。

线程安全的Observer的难点

动态创建的对象是否还活着,看指针和引用是看不出来的。面向对象的程序中,对象之间的关系有3种:

  • composition(组合):b是a的成员,不存在竞争关系,是线程安全的;
  • aggregation(关联):a用到了b,a持有b的指针或者引用,但是a不能单独控制b的生命期;
  • association(聚合):不同于关联的“整体-部分”,聚合更加泛化,可以是一对多、多对一。。。

以观察者模式举例

//观察者
class Observer
{
public:
    void update();
    void ob(Observable* s)
    {
        s->register_(this);
        subject_ = s;
    }
    ~Oberver()
    {
        subject_->unregister(this);
    }
    Observable* subject_;
}

//被观察的
class Observable
{
public:
    void register(Observer* s);
    void unregister(Observer* s);
    
    void notify()
    {
        for (Observer* x : observers_)
        {
            x->update();
        }
    }
    
    vector observers_;
}

以上例子中,被观察者在notify时,如何确保每一个指针是可用的?观察者在析构时unregister,也有可能和notify发生竞争。

原始指针是坏的,如果有一种机制能够判断指针指向的对象是否存活,并且判断有多少地方在使用这个对象,就能安全的使用并销毁对象。

智能指针shared_ptr/weak_ptr

shared_ptr

shared_ptr允许多个该智能指针共享第“拥有”同一堆分配对象的内存,这通过引用计数(reference counting)实现,会记录有多少个shared_ptr共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。

    std::shared_ptr sp1(new int(22)); //新建
    std::shared_ptr sp2 = sp1;//拷贝
    std::cout << "cout: " << sp2.use_count() << std::endl; // 打印引用计数
    sp1.reset(); // 让引用计数减一

weak_ptr

weak_ptr是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用计数的增加或减少。没有重载 * 和 -> 但可以使用lock获得一个可用的shared_ptr对象

weak_ptr的使用更为复杂一点,它可以指向shared_ptr指针指向的对象内存,却并不拥有该内存,而使用weak_ptr成员lock,则可返回其指向内存的一个share_ptr对象,且在所指对象内存已经无效时,返回指针空值nullptr。

    std::shared_ptr sp1(new int(22));
    std::shared_ptr sp2 = sp1;
    std::weak_ptr wp = sp1; // 指向shared_ptr所指对象

    std::cout << "count: " << wp.use_count() << std::endl; // count: 2
    
    sp1.reset();
    std::cout << "count: " << wp.use_count() << std::endl; // count: 1

    sp2.reset();
    std::cout << "count: " << wp.use_count() << std::endl; // count: 0, pointer is invalid
    
    std::shared_ptr sp = wp.lock(); // 转换为shared_ptr
    if (sp != nullptr)
    {
        std::cout << "still: " << *sp << std::endl;
    } 
    else
    {
        std::cout << "still: " << "pointer is invalid" << std::endl;
    }

引申:unique_ptr

unique_ptr,是用于取代c++98的auto_ptr的产物,在c++98的时候还没有移动语义(move semantics)的支持,有了移动语义,使用move()把unique_ptr传入函数,这样你就知道原先的unique_ptr已经失效了.移动语义本身就说明了这样的问题。

unique_ptr 是一个独享所有权的智能指针,它提供了严格意义上的所有权,包括:

  1. 拥有它指向的对象
  2. 无法进行复制构造,无法进行复制赋值操作。即无法使两个unique_ptr指向同一个对象。但是可以进行移动构造和移动赋值操作
  3. 保存指向某个对象的指针,当它本身被删除释放的时候,会使用给定的删除器释放它指向的对象

unique_ptr 可以实现如下功能:

  1. 为动态申请的内存提供异常安全
  2. 讲动态申请的内存所有权传递给某函数
  3. 从某个函数返回动态申请内存的所有权
  4. 在容器中保存指针
  5. auto_ptr 应该具有的功能
unique_ptr fun()
{
    return unique_ptr(new Test("789"));
}
int main()
{
    unique_ptr ptest(new Test("123"));
    unique_ptr ptest2(new Test("456"));
    ptest->print();
    ptest2 = std::move(ptest);//不能直接ptest2 = ptest
    if(ptest == NULL) //直接==和get()居然都可以。。。
        cout<<"ptest = NULL\n";
    Test* p = ptest2.release();
    p->print();
    ptest.reset(p);
    ptest->print();
    ptest2 = fun(); //这里可以用=,因为使用了移动构造函数
    ptest2->print();
    return 0;
}

插曲:系统地避免各种指针错误

作者陈硕说他的C++程序从来没有出过内存问题。

C++的内存问题有如下几个方面,智能指针可以解决前5个。

  • 缓冲区溢出(buffer overrun):vector, string
  • 空悬指针 | 野指针
  • 重复释放(double delete)
  • 内存泄漏(memory leak)
  • 不配对的new[]/delete
  • 内存碎片(memory fragmentation)

作者认为,现代C++程序不应该出现delete语句,所有资源通过容器或者智能指针管理。

修改:使用weak_ptr实现Observer,即在Observable中使用weak_ptr探测Observer对象的生死。

class Observable
{
public:
	void register(weak_ptr x);
	void unregister(weak_ptr x);
	void notify()
	{
		lock_guard lg(mutex_);
		for (auto it = observers_.begin(); it != observers_.end(); )
		{
			shared_ptr pObserver(it->lock());
			if (pObserver)
			{
				pObserver->update();
				++it;
			}
			else
			{
				it = observers_.erase(it);
			}
		}
	}
private:
	list > observers_;
	mutable mutex mutex_;
};

class Observer
{
public:
	void update();
	void ob(weak_ptr x)
	{
		shared_ptr pObservable(x.lock());
		if (pObservable)
		{
			subject_ = pObservable;
		}
	}
	~Observer()
	{
		shared_ptr pObservable(subject_.lock());
		if (pObservable)
		{
			shared_ptr x(this);
			pObservable->unregister(shared_ptr x(this));
		}
	}
private:
	weak_ptr subject_;
};

以上代码存在的问题:

  1. Observable的三个函数全部要加锁,可是notify的执行时间无上限,因为update是用户提供的。
  2. ~Observer中要调用unregister,如果Observer对象在栈上,怎么传递weak_ptr

论shared_ptr的线程安全

shared_ptr的引用计数是原子的,但是shared_ptr并非线程安全的,多个线程读写时需要加锁。

//globalPtr全局shared_ptr变量
void read()
{
	shared_ptr localPtr;
	{
		lock_guard lg(mutex_);
		localPtr = globalPtr;
	}
	// use localPtr
}
void write()
{
	shared_ptr newPtr(new Foo);
	{
		lock_guard lg(mutex_);
		globalPtr = newPtr;
	}
	// use newPtr
}

shared_ptr的技术陷阱

shared_ptr是强引用,只要还有shared_ptr存活,对象就不会析构。例如:

vector > x;
//bind会拷贝实参,使得对象生命期不短于std::function
std::bind();

对象池

Stock是股票类,实现根据股票名字key,返回股票ID的对象池StockFactory

class StockFactory
{
public:
	shared_ptr get(const string& key)
	{
		shared_ptr pStock;
		lock_guard lg(mutex_);
		weak_ptr& wStock = stocks_[key];
		pStock = wStock.lock();
		if (!pStock)
		{
			pStock.reset(new Stock(key));
			wStock = pStock;
		}
		return pStock;
	}

private:
	mutable mutex mutex_;
	map> stocks_;
};

以上代码中,map结构只增不减,解决办法:shared_ptr调用析构时,可以定制析构,即传入一个bind,析构时调用。

class Stock
{
public:
	string key;
	string id;
	Stock(string k) : key(k) {};
	~Stock()
	{
		cout << "~Stock called" << endl;
	}
};

class StockFactory
{
public:
	boost::shared_ptr get(const string& key)
	{
        boost::shared_ptr pStock;
		lock_guard lg(mutex_);
        boost::weak_ptr& wStock = stocks_[key];
		pStock = wStock.lock();
		if (!pStock)
		{
			//bind绑定非静态成员函数时,要传入函数的隐含的第一个参数this
			pStock.reset(pTemp, boost::bind(&StockFactory::deleteStock, this, _1));
			wStock = pStock;
		}
		return pStock;
	}

private:
	void deleteStock(Stock* s)
	{
		if (s)
		{
			lock_guard lg(mutex_);
			stocks_.erase(s->key);
			delete s;
		}
		cout << "deleteStock called" << endl;
	}
	mutable std::mutex mutex_;
	map> stocks_;
};

enable_shared_from_this

上面代码设置shared_ptr的deleter时,使用了裸指针。如果执行deleter时的StcokFactory已经死亡,就会发生错误。使用enable_shared_from_this可以在成员函数中得到shared_ptr,要求实例必须在堆上。

	boost::shared_ptr get(const string& key)
	{
        boost::shared_ptr pStock;
		lock_guard lg(mutex_);
        boost::weak_ptr& wStock = stocks_[key];
		pStock = wStock.lock();
		if (!pStock)
		{
			//bind绑定非静态成员函数时,要传入函数的隐含的第一个参数this
			//pStock.reset(pTemp, boost::bind(&StockFactory::deleteStock, this, _1));
			pStock.reset(pTemp, boost::bind(&StockFactory::deleteStock, enable_shared_from_this(), _1));
			wStock = pStock;
		}
		return pStock;
	}

弱回调

上面代码延长了StockFactory的生命期,只要不发生回调,就不会销毁。使用weak_ptr产生弱回调效果。

	boost::shared_ptr get(const string& key)
	{
        boost::shared_ptr pStock;
		lock_guard lg(mutex_);
        boost::weak_ptr& wStock = stocks_[key];
		pStock = wStock.lock();
		if (!pStock)
		{
			//bind绑定非静态成员函数时,要传入函数的隐含的第一个参数this
			pStock.reset(pTemp, boost::bind(&StockFactory::deleteStock, boost::weak_ptr(shared_from_this()), _1));
			wStock = pStock;
		}
		return pStock;
	}

	void deleteStock(const boost::weak_ptr& sf, Stock* s)
	{
	    shared_ptr f(sf.lock());
		if (f && s)
		{
			lock_guard lg(mutex_);
			stocks_.erase(s->key);
			delete s;
		}
		cout << "deleteStock called" << endl;
	}

经典的弱回调实现

template
class WeakCallback
{
 public:

  WeakCallback(const std::weak_ptr& object,
               const std::function& function)
    : object_(object), function_(function)
  {
  }

  // Default dtor, copy ctor and assignment are okay

  void operator()(ARGS&&... args) const
  {
    std::shared_ptr ptr(object_.lock());
    if (ptr)
    {
      function_(ptr.get(), std::forward(args)...);
    }
  }

 private:

  std::weak_ptr object_;
  std::function function_;
};

你可能感兴趣的:(学习笔记,C++,C++,muduo,多线程,网络)