最近在看陈硕大大 的《Linux 多线程服务端编程:使用 muduo C++ 网络库》 ,看到里面用variadic template 和boost智能指针 实现了一个 signal/slot,现在C++11 已经把 boost的智能指针引入到标准库里边了。就想利用纯C++11 实现一遍。
结果发现,只要把原来代码中boost智能指针替换为c++11 的智能指针,把陈大大自己实现的MutexLock替换为std::mutex,
MutexLockGuard 替换为std::lock_guard 就可以了。
看来陈硕大大在很早以前就把握到了 C++ 的发展趋势啊。
在贴代码之前 ,先说几点。
一、
首先说一下shared_ptr<T>的线程安全性,它的线程安全性级别和std::string是一样的。它的计数操作是原子操作,但是多线程对它的并发读写是不安全的,因为他有两个操作,一个是修改地址一个是修改计数。可以想一下,现在有一个智能指针x指向一片内存,先对它读,比如y=x;,读一半(只修改了y的地址,但是计数还是1),此时再进行写操作,比如x=z,全部执行完,那么x指向z的内存,x原来指的内存因为计数减一被释放,这时再进行y=x读操作的另一半(计数加一),但是内存已经释放了。
所以多线程读写shared_ptr<T>需要保护临界区。
二、copyonwrite代替读写锁。
基本思想就是如果此刻有其他线程正在进行读操作,那么写操作需要在新的副本上执行。
实际上是这样的,每当进行读操作,则sp(shared_ptr简写)计数加一(计数至少为2)。这时如果有写操作,它先判断计数是否为1,若为1则没有线程读,可以在原内存上修改,若不为1,则复制内容到一片新内存并进行修改。其中写操作全程加锁保证只有一个线程可以写。
那么我们分析一下,若在写操作时有其他线程要进行读操作会等待锁释放;而没有写操作时可以有很多读操作,在进入和退出读操作的过程中他们的引用计数分别加1、减1,从而保证了读操作时内存的确定以及读操作完成后内存的释放(当然是所有的读操作都完成那么计数为0,自动释放)。又回到开始,有很多读操作时,要执行写操作会开辟新副本。那么读写操作各自管理的两片内存,它们的生命由各自计数管理。
再说一下,读操作的临界区是很小的,只包括了 s_p本身的读保护,只有一个语句,这个临界区是很小的。而写操作是全程保护的。
三、看一下画的图。
sp1就是需要多线程读写保护的智能指针。直接写操作的有clean和add两个函数,直接读操作的只有call函数。wp(wadk_ptr<T>的简称)是槽感知信号生命的指针,在信号中的vector<weak_ptr<slot_imp>>则可以感知每个槽的生命。能感受到对方的生命,就可以执行相应操作。
不说了,上代码。
#include<functional>
#include<memory>
#include<vector>
#include<mutex>
#include<assert.h>
#include<iostream>
template<typename Callback>
struct SlotImpl;
template<typename Callback>
struct SignalImpl
{
typedef std::vector<std::weak_ptr<SlotImpl<Callback> > > SlotList;
SignalImpl()
: slots_(new SlotList)
{
}
void copyOnWrite()
{
if (!slots_.unique())
{
slots_.reset(new SlotList(*slots_));
}
assert(slots_.unique());
}
void clean()
{
std::lock_guard<std::mutex> lock(mutex_);
copyOnWrite();
SlotList& list(*slots_);
typename SlotList::iterator it(list.begin());
while (it != list.end())
{
if (it->expired())
{
it = list.erase(it);
}
else
{
++it;
}
}
}
std::mutex mutex_;
std::shared_ptr<SlotList> slots_;
};
template<typename Callback>
struct SlotImpl
{
typedef SignalImpl<Callback> Data;
SlotImpl(const std::shared_ptr<Data>& data, Callback&& cb)
: data_(data), cb_(cb), tie_(), tied_(false)
{
}
SlotImpl(const std::shared_ptr<Data>& data, Callback&& cb,
const std::shared_ptr<void>& tie)
: data_(data), cb_(cb), tie_(tie), tied_(true)
{
}
~SlotImpl()
{
std::shared_ptr<Data> data(data_.lock());
if (data)
{
data->clean();
}
}
std::weak_ptr<Data> data_;
Callback cb_;
std::weak_ptr<void> tie_;
bool tied_;
};
typedef std::shared_ptr<void> Slot;
template<typename Signature>
class Signal;
template <typename RET, typename... ARGS>
class Signal<RET(ARGS...)>
{
public:
typedef std::function<void (ARGS...)> Callback;
typedef SignalImpl<Callback> SignalImpl_t;
typedef SlotImpl<Callback> SlotImpl_t;
Signal()
: impl_(new SignalImpl_t)
{
}
~Signal()
{
}
Slot connect(Callback&& func)
{
std::shared_ptr<SlotImpl_t> slotImpl(
new SlotImpl_t(impl_, std::forward<Callback>(func)));
add(slotImpl);
return slotImpl;
}
Slot connect(Callback&& func, const std::shared_ptr<void>& tie)
{
std::shared_ptr<SlotImpl_t> slotImpl(new SlotImpl_t(impl_, func, tie));
add(slotImpl);
return slotImpl;
}
void call(ARGS&&... args)
{
SignalImpl_t& impl(*impl_);
std::shared_ptr<typename SignalImpl_t::SlotList> slots;
{
std::lock_guard<std::mutex> lock(impl.mutex_);
slots = impl.slots_;
}
typename SignalImpl_t::SlotList& s(*slots);
for (typename SignalImpl_t::SlotList::const_iterator it = s.begin(); it != s.end(); ++it)
{
std::shared_ptr<SlotImpl_t> slotImpl = it->lock();
if (slotImpl)
{
std::shared_ptr<void> guard;
if (slotImpl->tied_)
{
guard = slotImpl->tie_.lock();
if (guard)
{
slotImpl->cb_(args...);
}
}
else
{
slotImpl->cb_(args...);
}
}
}
}
private:
void add(const std::shared_ptr<SlotImpl_t>& slot)
{
SignalImpl_t& impl(*impl_);
{
std::lock_guard<std::mutex> lock(impl.mutex_);
impl.copyOnWrite();
impl.slots_->push_back(slot);
}
}
const std::shared_ptr<SignalImpl_t> impl_;
};
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<void(void)> sig;
Slot slot1 = sig.connect(&hello);
sig.call();
}
Signal<void(int)> sig1;
Slot slot1 = sig1.connect(&print);
Slot slot2 = sig1.connect(std::bind(&print, std::placeholders::_1));
std::function<void(int)> 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;
}
下面再贴一下Makefile
iCXXFLAGS=-g -Wall -rdynamic -march=native
CXXFLAGS+=-O2
TEST=signal_slot_test
$(TEST):signal_slot_test.cpp
$(TEST):
g++ signal_slot_test.cpp -o signal_slot_test -std=c++11
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。