使用 MSVC 编译的多线程应用程序在运行时失败

Posted

技术标签:

【中文标题】使用 MSVC 编译的多线程应用程序在运行时失败【英文标题】:A multithreading app compiled with MSVC fails at runtime 【发布时间】:2015-02-25 11:51:10 【问题描述】:

我已经实现了一个循环运行提供的函数的类。

//Timer.h
#include <chrono>
#include <mutex>
#include <thread>

class Timer 
public:
    Timer(const std::chrono::milliseconds period, const std::function<void()>& handler);
    ~Timer();
    void Start();
    void Stop();
    bool IsRunning() const;

private:
    const std::function<void()>& handler;
    const std::chrono::milliseconds period;
    bool isRunning = false;
    mutable std::recursive_mutex lock;
    int counter = 0;

    void DoLoop(int id);
;

//Timer.cpp
#include "Timer.h"

Timer::Timer(const std::chrono::milliseconds period, const std::function<void()>& handler) :handler(handler), period(period), lock()

Timer::~Timer() 
    Stop();


void Timer::Stop() 
    lock.lock();
    isRunning = false;  
    lock.unlock();


void Timer::Start() 
    lock.lock();
    if (!isRunning) 
        isRunning = true;
        counter++;
        std::thread(&Timer::DoLoop, this, counter).detach();
    
    lock.unlock();


void Timer::DoLoop(int id) 
    while (true)
        std::this_thread::sleep_for(period);
        lock.lock();
        bool goOn = isRunning && counter==id;
        if (goOn) std::thread(handler).detach();
        lock.unlock();

        if (!goOn)
            break;
    


bool Timer::IsRunning() const 
    lock.lock();
    bool isRunning = this->isRunning;
    lock.unlock();
    return isRunning;

这是一个简单的程序,看看它是否有效:

void Tick() cout << "TICK" << endl; 

int main() 
    Timer timer(milliseconds(1000), Tick);
    timer.Start();
    cin.get();

当我使用 g++ 构建应用程序时,程序可以毫无问题地构建和运行。 但是,当我使用 Microsoft 的编译器 (v18) 时,程序也可以编译,但在运行时会失败。

当我使用发布配置时,我从其中一个线程中得到以下异常:

Program.exe 中 0x000007F8D8E14A30 (msvcr120.dll) 处的未处理异常:请求致命程序退出。

当我使用调试配置时,每秒都会弹出一个 Microsoft Visual C++ Runtime Library 错误:

调试错误!

程序:...\path\Program.exe

R6010 - abort() 已被调用

在两种配置中:

在计时器循环的第二次迭代中抛出异常/错误开始弹出。

即使调用了thread(handler),程序也不会进入Tick函数。

虽然这两种配置中出错时的堆栈跟踪不同,但它们都不包含我的代码中的任何内容。两者都以ntdll.dll!UserThreadStart()开头;调试以msvcr123d.dll!_NMSG_WRITE() 结束,发布以msvcr120.dll!abort() 结束。

为什么会出现问题?为什么只有在使用 MSVC 编译应用程序时才会出现这些问题?它是某种 MSVC 的错误吗?或者我应该改变编译器的配置吗?

【问题讨论】:

你需要睡一觉,你应该加入 Stop()。这只是一次迭代! 我不明白你在说什么。有一个无限循环正在进行。在每次迭代中,都会产生一个新线程。我应该加入哪个线程,为什么? 当你使用 stop(); “无限循环”结束。你可以等待加入他:) 我知道这一点,但这不是我写这门课时的意图。 Stop() 应该阻止计时器进行新的调用,而不是停止计时器启动的所有活动。此外,如果我想等待调用结束,我应该累积对所有调用线程的引用,因为之前调用的线程可能仍在工作。 请不要手动调用lock()unlock(),而是使用std::lock_guard。现在,如果有任何东西在你的锁中引发异常,你就会死锁。 【参考方案1】:

您的线程正在抛出std::bad_function_call,未处理异常,因此运行时正在调用abort()

变化:

const std::function<void()>& handler;

const std::function<void()> handler;

解决问题。我猜这是因为您在线程之间共享它?

如果您创建一个本地并传递对它的引用,也可以:

  const std::function<void()> f = Tick;
  Timer timer(std::chrono::milliseconds(1000), f);

所以它一定是超出了范围。

编辑:确实,函数对象在 ctor 调用后被破坏。不知道为什么会这样。

【讨论】:

确实,这解决了问题。我认为有一些隐式转换,其中指向函数的指针被转换为function 对象。然后该对象在 ctor 中使用并在之后销毁,因为不再需要它。但是为什么这段代码用 g++ 编译能正常工作呢? 一定是函数对象还在g++的作用域内。虽然我认为如果一个临时对象绑定到一个引用,那么它的生命周期应该延长吗? It seems 在这种情况下编译器的行为是未定义的。所以我很幸运 g++ 没有在 function 的地方放任何东西。 这可能只是次差的普通未定义行为——它似乎工作正常。暂时的,尽管不再在范围内,只是还没有被覆盖。如果某个其他值在内存中重用该位置,并且将“用户”提供的值写入其中,则存在安全漏洞。 @tearvisus 不,你很不幸,它没有在你负担不起的情况下严重破坏某些东西之前指出你的错误。另见“快速失败”。

以上是关于使用 MSVC 编译的多线程应用程序在运行时失败的主要内容,如果未能解决你的问题,请参考以下文章

QT程序在发布的时候应注意的地方

在 MSVC 的发布模式下初始化 QApplication 失败

MSVC 导致静态 const 模板成员初始化失败

Python中的多处理:处理多个工作线程

dll 运行时错误(C/C++/GCC/MSVC)

如何使用 msvc 2019 编译 QT 4 dll?