如何安全地删除std::互斥体成员

How to safely delete member std::mutex?

本文关键字:std 成员 删除 何安全 安全      更新时间:2023-10-16

我最近一直在使用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::互斥锁。如果std::互斥对象是公平的,那么析构函数应该在其他等待线程处理完之后才被解锁,然后销毁对象。

这不起作用,因为std::mutex在大多数平台上不能保证公平性。

工作解决方案:将对象实现为ref类或其他引用计数对象(智能指针)。然后将并发方法实现为保存对对象的强引用的lambda。一旦持有该对象的所有其他对象被删除,并且所有具有互斥锁的lambda都被处理,该对象将被自动删除。

我不喜欢这个方法,因为它对其余的代码造成了一些限制。对于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 = {};
};

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

代码未测试,但类似的设计以前工作过。