【问题标题】:C++: std::async and std::mutex leads to deadlock on Linux but runs on Windows?C++:std::async 和 std::mutex 在 Linux 上导致死锁但在 Windows 上运行?
【发布时间】:2015-03-15 12:32:21
【问题描述】:

我刚刚编译了一个我一直在 Windows for Linux 下工作的项目,发现它在某个时刻挂起。由于我使用的是 std::async 和 std::mutex 我的第一个假设是,这可能是一个死锁问题。但是,我想知道为什么它在 Windows 上运行良好。代码如下:

void BorderExtractor::preprocessImageAsync(const PreprocessingSettings& settings) {
    _preprocessingMutex.lock();
    if (!_preprocessingActive) {
        _preprocessingActive = true;
        std::async(std::launch::async, &BorderExtractor::preprocessImage, this, settings);
        //this point is never reached on linux
        _preprocessingUpToDate = true;
    } else {
        _cachedSettings = settings;
        _preprocessingUpToDate = false;
    }
    _preprocessingMutex.unlock();
}

这是在 Linux 下永远不会返回的函数。它一直运行到异步调用,然后才停止。看起来好像函数没有异步启动,程序等待它返回,这是行不通的,因为另一个函数会尝试锁定同一个互斥体。

这是异步调用的函数:

void BorderExtractor::preprocessImage(PreprocessingSettings settings) {

    //here some image processing stuff is done

    _preprocessingMutex.lock();
    //this point is never reached on linux
    if (!_preprocessingUpToDate) {
        _preprocessingUpToDate = true;
        _preprocessingMutex.unlock();
        std::async(std::launch::async, &BorderExtractor::preprocessImage, this, _cachedSettings);
    } else {
        _preprocessingUpToDate = true;
        _preprocessingActive = false;
        _preprocessingMutex.unlock();
    }
}

在 linux 下永远不会达到它尝试锁定互斥锁之后的点。

现在,问题是什么?是我的代码有问题,还是在 Linux 上有什么特别需要注意的地方? (编译器标志等)对我来说,这看起来好像异步调用是同步的,因此会导致死锁。但是为什么会这样

【问题讨论】:

  • 您使用的是哪个编译器?也许不相关,但std::async() 返回的未来会在销毁时加入,因此如果您的库实现符合要求,则此调用将同步执行:std::async(std::launch::async, &BorderExtractor::preprocessImage, this, settings)。 AFAIK 在 MS 的实现中,未来的析构函数不会加入。
  • 我正在使用 GCC。好的,我看到了问题。除了创建一个成员变量来拯救未来,还有什么办法吗?
  • 你也在 Windows 上使用 GCC 吗?
  • 不,我在那里使用 VS2013 编译器。
  • 好的,那么情况就清楚了。详情见我的回答。

标签: c++ linux asynchronous mutex deadlock


【解决方案1】:

这个电话:

async(std::launch::async, &BorderExtractor::preprocessImage, this, _cachedSettings);

有效地同步运行。这是因为 std::async() 返回的 std::future 的析构函数最终加入了异步计算 - 请注意,如果您以其他方式获得未来,行为会有所不同。

由于您没有保持std::async 返回的未来对象处于活动状态,因此它的生命周期在函数调用返回后立即结束,并且其析构函数阻塞直到异步计算终止——这是永远的,因为这似乎会导致死锁。

这在 Windows 上工作的原因可能是因为您使用的是标准库的不兼容实现(例如 Microsoft 的 VS2013 附带的实现),其中未来的析构函数不加入异步计算 - MS 故意这样做,遵循this (rejected) proposal by Herb Sutter 中说明的基本原理。

如果您正在寻找一种即发即弃的方法,请考虑 this alternative implementation of std::async(),它不会导致返回的 future 在销毁时阻塞(由 bamboon 提供):

template<class Function, class... Args>
std::future<typename std::result_of<Function(Args...)>::type> async( 
    Function&& f, 
    Args&&... args) 
{
    using R = typename std::result_of<Function(Args...)>::type;
    auto bound_task = std::bind(std::forward<Function>(f), std::forward<Args>(args)...);
    auto task = std::packaged_task<R()>{std::move(bound_task)};
    auto ret = task.get_future();
    auto t = std::thread{std::move(task)};
    t.detach();
    return ret;   
}

附带说明,请避免显式锁定/解锁互斥锁。相反,使用 RAII 包装器,如 std::lock_guard 或(如有必要)std::unique_lock 以确保即使抛出异常或提前返回,您的互斥锁也将被解锁:

// The mutex will be unlocked automatically when the function returns.
std::lock_guard<std::mutex> lock{_preprocessingMutex};

if (!_preprocessingUpToDate) {
    _preprocessingUpToDate = true;
    async(std::launch::async, &BorderExtractor::preprocessImage, this, _cachedSettings);
    // No need to call unlock() on the mutex!
} else {
    _preprocessingUpToDate = true;
    _preprocessingActive = false;
    // No need to call unlock() on the mutex!
}

【讨论】:

  • “请注意,如果你以其他方式获得未来,行为会有所不同。”是什么意思?
  • @user1488118:只有std::async() 返回的期货加入销毁。对于未来不会阻塞的替代方案,请参阅我链接的答案。
  • 好的,我尝试了两件事。首先,将 async 更改为 std::thread(&amp;BorderExtractor::preprocessImage, this, settings).detach();,这导致在使用程序 (Windows) 时出现错误“Abort() was called”而崩溃。其次,我尝试了另一个线程中建议的模板函数,但它无法编译,给出编译器错误“术语不评估为采用 3 个参数的函数”。问题可能是我试图在一个对象上调用一个函数(所以我必须传递一个指向该对象的指针)。不幸的是,我对模板编程知之甚少。
  • @user1488118:它无法在 Windows 上编译的原因可能是 MS 的 std::packaged_task&lt;&gt; 实现中的一个错误(仅供参考,here 你可以找到一个补丁),但这不相关,因为 MS 对 std::async() 的实现已经做了你想要的。您可以做的是编写一个包装器async() 函数,根据编译常量(例如#ifdef _WINDOWS)在Windows 上调用std::async(),以及我在Linux 上的答案中发布的async() 的修改版本。跨度>
  • @user1488118:附注Live demo 替代 async() 在 Linux 上使用 GCC 4.9 编译。
猜你喜欢
  • 1970-01-01
  • 2015-11-21
  • 1970-01-01
  • 1970-01-01
  • 2013-07-24
  • 1970-01-01
  • 1970-01-01
  • 2013-01-20
  • 1970-01-01
相关资源
最近更新 更多