我最近一直在使用std :: mutex,现在正在寻找关于删除std :: mutex作为成员的对象的模式/设计指南。当一个对象作为使用互斥锁(监视器功能,关键部分等)的公共函数时,当该对象被删除时,它可能还有线程仍在等待互斥锁。
:std :: mutex在被其他线程锁定时删除了未定义的行为,因此出现了问题。我想知道在这种情况下使用的普遍可接受的模式是什么。或者如果这被认为是一种糟糕的编码风格,那么一种避免这种设计的方法,即不删除仍在等待互斥方法的对象。
示例:
//a public method that uses mutex.
IAsyncAction^ XInputBase::flushTask()
{
return create_async([this](){
_monitorMutex.lock();
if (_readyToFlush && !_noMoreFlush) {
//should flush only once, block additional flush signals.
_noMoreFlush = true;
_monitorMutex.unlock();
//actually flush
concurrency::task<void> UITask = concurrency::create_task(Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal,
ref new Windows::UI::Core::DispatchedHandler([=]()
{
onFlush();
})));
}
else {
_needToFlush = true;
_monitorMutex.unlock();
}
});
}
尝试过的解决方案: 在处理完所有等待的线程之后,析构函数等待互斥锁完全解锁。我通过在互斥锁之外设置一个标志来实现它,以便方法中的所有线程都退出或等待互斥锁。然后我在析构函数中最后一次锁定了std :: mutex。鉴于std :: mutex是公平的,析构函数应该在处理完其他等待线程后解锁,然后销毁该对象。
这不起作用,因为std :: mutex并不能保证大多数平台的公平性。
工作解决方案的种类: 将对象实现为ref类或其他引用计数对象(智能指针)。然后将并发方法实现为包含对象的强引用的lamda。删除保存此对象的所有其他对象并处理所有带有互斥锁的lamdas后,将自动删除此对象。
我不喜欢这种方法,因为它对其余代码构成了一些限制。对于WinRT,此方法不控制哪个线程删除此对象,因此可能发生UI线程错误。
答案 0 :(得分:1)
您无法使用对象内的数据保护对象的生命周期。
您可以使用外部互斥锁保护对象。
首先从这开始:
template<class T>
struct locked_view {
template<class F>
auto operator->*(F&& f) const
-> std::result_of_t< F(T const&) >
{
auto l = lock();
return std::forward<F>(f)(*t);
}
locked_view(locked_view const&) = default;
locked_view& operator=(locked_view const&) = default;
locked_view()=default;
explicit operator bool() const { return m&&t; }
locked_view( std::mutex* min, T* tin ):m(min), t(tin) {}
private:
std::unique_lock<std::mutex> lock() const {
return std::unique_lock<std::mutex>(*m);
}
std::mutex* m;
T* t;
};
template<class T>
struct locked {
locked()=default;
locked(locked&& o):
t( o.move_from() )
{}
locked(locked const& o):
t( o.copy_from() )
{}
locked& operator=(locked&& o) {
auto tin = o.move_from();
assign_to(std::move(tin));
return *this;
}
locked& operator=(locked const& o) {
auto tin = o.copy_from();
assign_to(std::move(tin));
return *this;
}
template<class U,
std::enable_if_t<!std::is_same<std::decay_t<U>, locked>{}, int> =0
>
locked( U&& u ):
t( std::forward<U>(u) )
{}
// stars of show:
locked_view<T const> read() const
{
return {&m, std::addressof(t)};
}
locked_view<T> write()
{
return {&m, std::addressof(t)};
}
T move_from() {
return write()->*[](T& tin){return std::move(tin);};
}
T copy_from() const {
return read()->*[](T const& tin){return tin;};
}
template<class U>
void assign_to( U&& u ) {
write()->*[&](T& t){ t = std::forward<U>(u); };
}
private:
mutable std::mutex m;
T t;
};
使用类似于:
locked<int> my_int = 7;
my_int.read()->*[](int x){ std::cout << x << '\n'; };
接下来,将std::unique_ptr<YourClass>
填入其中。这将允许人们删除它。
最后,与其分享std::shared_ptr<>
。
所以
template<class T>
using shared_locked = std::shared_ptr< locked< T > >;
template<class T>
using shared_locked_ptr = shared_locked< std::unique_ptr<T> >;
是你的类型。
要使用,他们会这样做:
shared_locked_ptr<widget> w;
w->read()->*[&]( auto& ptr ) {
if (ptr) ptr->do_something();
};
您可以检查锁定区块内的生命周期。
以线程安全的方式删除对象:
w->write()->*[&]( auto& ptr ) {
ptr = {};
};
锁定在指向窗口小部件的指针周围,而不是在窗口小部件周围。指向窗口小部件指针的指针是共享的。
代码未经过测试,但类似的设计之前已经过。