【问题标题】:Mutex lock in C++ destructor causes exception when exit() called in Python当在 Python 中调用 exit() 时,C++ 析构函数中的互斥锁会导致异常
【发布时间】:2017-11-17 05:26:22
【问题描述】:

我有一个应用程序加载一个带有一个类的 DLL,该类处理队列中的作业。为了保证线程安全,只要队列被修改,互斥锁就会被锁定。当应用程序退出并调用析构函数时,互斥锁被锁定以清除队列。

但是,当我在 Python 中加载此 DLL、创建对象实例并调用 exit()(在 Python 中)时,互斥锁尝试锁定时会引发异常:

Microsoft Visual Studio C 运行时库在 python.exe 中检测到一个致命错误。

我已将析构函数简化为仅在本地创建互斥体并尝试锁定它,并且仍然可以重现该问题:

QueueHandler::~QueueHandler(void)
{
    mutex mut; // in reality, this is a member of the class and there are actual operations between lock and unlock
    mut.lock(); // exception here
    mut.unlock();
}

如果我使用未修改的代码并简单地移除队列操作周围的锁,它就可以正常工作。

这是调用堆栈中看似相关的部分:

KernelBase.dll!RaiseException() Unknown
msvcr120.dll!_CxxThrowException(void * pExceptionObject, const _s__ThrowInfo * pThrowInfo) Line 154 C++
msvcr120.dll!Concurrency::details::SchedulerBase::SchedulerBase(const Concurrency::SchedulerPolicy & policy) Line 149   C++
msvcr120.dll!Concurrency::details:: SchedulerBase::CreateWithoutInitializing(const Concurrency::SchedulerPolicy & policy) Line 285  C++
msvcr120.dll!Concurrency::details:: SchedulerBase::CreateContextFromDefaultScheduler() Line 571 C++
msvcr120.dll!Concurrency::details::SchedulerBase::CurrentContext() Line 404 C++
[Inline Frame] msvcr120.dll!Concurrency::details::LockQueueNode::{ctor (unsigned int) Line 619  C++
msvcr120.dll!Concurrency::critical_section::lock() Line 1031    C++
msvcp120.dll!mtx_do_lock(_Mtx_internal_imp_t * * mtx, const xtime * target) Line 67 C++
--> MyApplication.dll!QueueHandler::~QueueHandler() Line 106    C++
MyApplication.dll!_CRT_INIT(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 416  C
MyApplication.dll!__DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 522    C
ntdll.dll!LdrShutdownProcess()  Unknown
ntdll.dll!RtlExitUserProcess()  Unknown
msvcr100.dll!doexit(int code, int quick, int retcaller) Line 621    C
python27.dll!000000001e13be65() Unknown
...
python27.dll!000000001e043494() Unknown
python.exe!000000001d00119e()   Unknown

问题:

  1. 如果此代码在我正常退出我的应用程序(关闭 GUI)时有效,为什么我在 Pyton 中 exit() 时会有所不同?
  2. 有没有“更正确”的方式退出 Python?
  3. 这可能与所使用的互斥锁/锁的类型有关吗?
  4. 我什至需要在我的析构函数中锁定带有队列操作的部分吗?还是可以不加锁删除队列中的对象?

编辑: MCVE: QueueHandlerApp - 运行应用程序或运行 script.py 来演示问题。

【问题讨论】:

  • 在析构函数中加锁是绝对不行的。
  • @CaptainObvlious,所以您是说互斥对象在被锁定时被销毁?如果我不应该在析构函数中使用锁,那么我应该在没有锁的情况下清理队列吗?还是有其他清理队列的方法?
  • 您在析构函数中创建了一个全新的锁,然后您将其锁定并解锁。这到底应该达到什么目的(这很有用)?
  • @SFBA26 那么也许您应该发布minimal reproducible example 以便我们有机会帮助您解决问题。仅仅发布不做任何事情并且我们无法编译/运行/测试的代码 sn-ps 是相当没用的。
  • @NeilButterworth 锁定析构函数没有错。这是operator delete 已经做的事情。任何第三方分配器也需要这个。这里的问题是 std::mutex 实现损坏,由于 Windows 上的模块取消初始化损坏,情况变得更糟。

标签: python c++ multithreading locking mutex


【解决方案1】:

有些人在遇到问题时会想,“我知道,我会用 延迟初始化。”现在他们有两个问题。

这是std::mutex 的 MSVC 实现中的一个错误。在 MSVC14 之前,std::mutexstd::condition_variable 会懒惰地执行一些内部初始化。仅此一项就糟糕,但由于how modules are deinitialized on Windows而变得更糟。

该错误已在 MSVC14 (Visual Studio 2015) 中修复 - std::mutex 被重写为在内部使用 SRWLockSRWLock 是一个简单的原语,没有额外的依赖。它仅依赖于原子指令和Keyed Events 系统调用。由于内核与用户空间隔离,SRWLock 应该可以无缝地工作,无论它们在哪里使用。

似乎您正在使用 MSVC12 (Visual Studio 2013)。您应该切换到 MSVC14 (Visual Studio 2015) 或改用 Boost.Thread。

在 MSVC12 和更早版本上,std::mutex 实际上存在很多问题。有些与 CRT 中使用的实际实现有关,有些(据我所知)是由 Windows 7 中的错误引起的,并已在 Windows 8 中修复。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-21
    • 2022-08-14
    • 1970-01-01
    • 2016-04-27
    • 2011-03-03
    • 2011-02-09
    • 2012-10-18
    相关资源
    最近更新 更多