如何安全删除成员std :: mutex?

时间:2016-09-02 01:30:14

标签: c++ multithreading c++11 windows-runtime delete-operator

我最近一直在使用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线程错误。

1 个答案:

答案 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 = {};
};

锁定在指向窗口小部件的指针周围,而不是在窗口小部件周围。指向窗口小部件指针的指针是共享的。

代码未经过测试,但类似的设计之前已经过。