C++11 信号槽 signal/slot

实现了一个线程安全的信号槽,还是利用智能指针的特性,一方面信号槽的多线程的读写是安全的,另一方面采用了弱回调技术(基于weak_ptr,槽的析构)。随手画了一张结构图,。此处应该@陈硕大大,《Linux多线程服务端编程:使用muduo C++网络库》。

一、

首先说一下shared_ptr的线程安全性,它的线程安全性级别和std::string是一样的。它的计数操作是原子操作,但是多线程对它的并发读写是不安全的,因为他有两个操作,一个是修改地址一个是修改计数。可以想一下,现在有一个智能指针x指向一片内存,先对它读,比如y=x;,读一半(只修改了y的地址,但是计数还是1),此时再进行写操作,比如x=z,全部执行完,那么x指向z的内存,x原来指的内存因为计数减一被释放,这时再进行y=x读操作的另一半(计数加一),但是内存已经释放了。

所以多线程读写shared_ptr需要保护临界区。

二、copyonwrite代替读写锁。

基本思想就是如果此刻有其他线程正在进行读操作,那么写操作需要在新的副本上执行。

实际上是这样的,每当进行读操作,则sp(shared_ptr简写)计数加一(计数至少为2)。这时如果有写操作,它先判断计数是否为1,若为1则没有线程读,可以在原内存上修改,若不为1,则复制内容到一片新内存并进行修改。其中写操作全程加锁保证只有一个线程可以写。

那么我们分析一下,若在写操作时有其他线程要进行读操作会等待锁释放;而没有写操作时可以有很多读操作,在进入和退出读操作的过程中他们的引用计数分别加1、减1,从而保证了读操作时内存的确定以及读操作完成后内存的释放(当然是所有的读操作都完成那么计数为0,自动释放)。又回到开始,有很多读操作时,要执行写操作会开辟新副本。那么读写操作各自管理的两片内存,它们的生命由各自计数管理。

再说一下,读操作的临界区是很小的,只包括了 s_p本身的读保护,只有一个语句,这个临界区是很小的。而写操作是全程保护的。

三、看一下画的图。

sp1就是需要多线程读写保护的智能指针。直接写操作的有clean和add两个函数,直接读操作的只有call函数。wp(wadk_ptr的简称)是槽感知信号生命的指针,在信号中的vector>则可以感知每个槽的生命。能感受到对方的生命,就可以执行相应操作。

C++11 信号槽 signal/slot_第1张图片


不说了,上代码。

#include
#include
#include
#include
#include
#include


template
class slot_imp;

template
class signal_imp
{
public:
	typedef std::vector>> slot_list;

	std::shared_ptr slots_;
	std::mutex mutex_;
public:
	signal_imp() :slots_(new slot_list) {}
	void copyonwrite()//写操作会调用他,前提已经加锁。
	{
		if (!slots_.unique())
		{
			slots_.reset(new slot_list(*slots_));
		}
		assert(slots_.unique());
	}
	void clean()
	{
		std::lock_guard lock(mutex_);
		copyonwrite();
		slot_list& slots_ref(*slots_);
		typename slot_list::iterator iter = slots_ref.begin();
		while (iter != slots_ref.end())
		{
			if (iter->expired())
				iter = slots_ref.erase(iter);
			else
				iter++;
		}
	}
};

template
class slot_imp
{
public:
	std::weak_ptr> signal_;
	callback cb_;
	std::weak_ptr tie_;
	bool tied_;
public:
	slot_imp(const std::shared_ptr>& signal_args_, callback&& cb_arg_)
		:signal_(signal_args_), cb_(cb_arg_), tie_(), tied_(false)
	{

	}
	slot_imp(const std::shared_ptr>& signal_args_, callback&& cb_arg_, const std::shared_ptr tie_arg)
		:signal_(signal_args_), cb_(cb_arg_), tie_(tie_arg), tied_(true)
	{

	}
	~slot_imp()
	{
		std::shared_ptr> sp_signal_imp_(signal_.lock());
		if (sp_signal_imp_)
			sp_signal_imp_->clean();
	}
};

typedef std::shared_ptr slot;

template
class signal;

template
class signal
{
public:
	typedef std::function callback;
	typedef signal_imp signal_imp;
	typedef slot_imp slot_imp;
private:
	std::shared_ptr signal_imp_;
	void add(const std::shared_ptr& slot)//写操作
	{
		signal_imp&  signal_imp_ref_(*signal_imp_);
		{
			std::lock_guard lock(signal_imp_ref_.mutex_);
			signal_imp_ref_.copyonwrite();
			signal_imp_ref_.slots_->push_back(slot);
		}
	}
public:
	signal() :signal_imp_(new signal_imp) {}
	~signal() {}
	slot connect(callback&& cb_)
	{
		std::shared_ptr ret(new slot_imp(signal_imp_, std::forward(cb_)));
		add(ret);
		return ret;
	}
	slot connect(callback&& cb_, const std::shared_ptr& tie)
	{
		std::shared_ptr ret(new slot_imp(signal_imp_, std::forward(cb_), tie));
		add(ret);
		return ret;
	}
	void call(ARGS... args)
	{
		signal_imp&  signal_imp_ref_(*signal_imp_);
		std::shared_ptr sp_slots_;//为了让引用计数加1。保护sp的读,临界区一行
		{
			std::lock_guard lock(signal_imp_ref_.mutex_);
			sp_slots_ = signal_imp_ref_.slots_;
		}

		signal_imp::slot_list&  slots_ref_(*sp_slots_);//这时就可以大胆的取 槽数组 了
		for (typename signal_imp::slot_list::iterator iter = slots_ref_.begin(); iter != slots_ref_.end(); iter++)
		{
			std::shared_ptr sp_slot_imp = iter->lock();
			if (sp_slot_imp)//感知生命,对应操作
			{
				std::shared_ptr guard;
				if (sp_slot_imp->tied_)
				{
					guard = sp_slot_imp->tie_.lock();
					if (guard)
						sp_slot_imp->cb_(args...);
				}
				else
					sp_slot_imp->cb_(args...);
			}
		}
	}
};

using namespace std;
void hello()
{
	int a = 0;
	int b = 1;
	cout << "hello" << endl;
}
void print(int i)
{
	cout << "print " << i << endl;
}
void test()
{
	{
		signal sig;
		slot slot1 = sig.connect(&hello);
		sig.call();
	}
	signal sig1;
	slot slot1 = sig1.connect(&print);
	slot slot2 = sig1.connect(std::bind(&print, std::placeholders::_1));
	std::function func1(std::bind(&print, std::placeholders::_1));
	slot slot3 = sig1.connect(std::move(func1));
	{
		slot slot4 = sig1.connect(std::bind(&print, 666));
		sig1.call(4);
	}
	sig1.call(4);
}

int main()
{
	test();
	char c; cin >> c;
}


你可能感兴趣的:(C++,多线程)