问题
In a RPC communication protocol, after the invocation of a method I'm sending "done" messages back to the caller. Since the methods are invoked in a concurrent fashion, the buffer containing the response (a std::string
) needs to be protected by a mutex. What I'm trying to achieve is the following:
void connection::send_response()
{
// block until previous response is sent
std::unique_lock<std::mutex> locker(response_mutex_);
// prepare response
response_ = "foo";
// send response back to caller. move the unique_lock into the binder
// to keep the mutex locked until asio is done sending.
asio::async_write(stream_,
asio::const_buffers_1(response_.data(), response_.size()),
std::bind(&connection::response_sent, shared_from_this(),
_1, _2, std::move(locker))
);
}
void connection::response_sent(const boost::system::error_code& err, std::size_t len)
{
if (err) handle_error(err);
// the mutex is unlocked when the binder is destroyed
}
However, this fails to compile, since boost::asio
requires handlers to be CopyConstructible.
The problem can be worked around (albeit not very elegantly) by using the following shared locker class instead of unique_lock
:
template <typename Mutex>
class shared_lock
{
public:
shared_lock(Mutex& m)
: p_(&m, std::mem_fn(&Mutex::unlock))
{ m.lock(); }
private:
std::shared_ptr<Mutex> p_;
};
What is the reasoning behind boost::asio
not allowing move-only handlers?
回答1:
Until Chris Kohlhoff responds to the bug I've filed, here's a simple workaround:
template <typename F>
struct move_wrapper : F
{
move_wrapper(F&& f) : F(std::move(f)) {}
move_wrapper(move_wrapper&&) = default;
move_wrapper& operator=(move_wrapper&&) = default;
move_wrapper(const move_wrapper&);
move_wrapper& operator=(const move_wrapper&);
};
template <typename T>
auto move_handler(T&& t) -> move_wrapper<typename std::decay<T>::type>
{
return std::move(t);
}
The wrapper declares a copy constructor, tricking asio's machinery into submission, but never defines it, so that copying would result in a linking error.
Now one can finally do this:
std::packaged_task<int()> pt([] {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
});
std::future<int> fu = pt.get_future();
boost::asio::io_service io;
io.post(move_handler(pt));
std::thread(&boost::asio::io_service::run, &io).detach();
int result = fu.get();
assert(result == 42);
回答2:
Here's a simpler workaround:
shared_ptr<mutex> lock(mutex & m)
{
m.lock();
return shared_ptr<mutex>(&m, mem_fn(&mutex::unlock));
}
No need to write custom wrappers.
Referring to Smart Pointer Programming Techniques you can even use:
class shared_lock {
private:
shared_ptr<void> pv;
public:
template<class Mutex> explicit shared_lock(Mutex & m): pv((m.lock(), &m), mem_fn(&Mutex::unlock)) {}
};
shared_lock
can now be used as:
shared_lock lock(m);
Note that shared_lock
is not templated on the mutex
type, thanks to shared_ptr<void>
's ability to hide type information.
It potentially costs more, but it has some thing going for it too (the receiver can take shared_lock
and you could pass it an upgradable, shared, unique lock, scope_guard, of basically any BasicLockable or "better"
来源:https://stackoverflow.com/questions/17211263/how-to-trick-boostasio-to-allow-move-only-handlers