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

How to safely delete member std::mutex?

我最近一直在使用 std::mutex,现在正在寻找有关删除成员为 std::mutex 的对象的 pattern/design 指南。当对象作为 public 函数使用互斥锁(监视函数、临界区等)并且删除该对象时,它可能有线程仍在等待互斥锁,就会出现问题。 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 class 或其他引用计数对象(智能指针)。然后将并发方法实现为一个拥有对该对象的强引用的 lamda。一旦删除了持有该对象的所有其他对象并且处理了所有具有互斥锁的lamda,该对象将被自动删除。

我不喜欢这种方法,因为它对其余代码造成了一些限制。对于 WinRT,此方法无法控制哪个线程删除此对象,因此,可能会发生 UI 线程错误。

您无法通过对象中的数据来保护对象的生命周期。

您可以使用外部互斥体保护对象。

所以从这个开始:

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

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

代码未经过测试,但类似的设计以前曾有效。