std :: mutex锁在覆盖新运算符时挂起 [英] std::mutex lock hangs when overriding the new operator

查看:283
本文介绍了std :: mutex锁在覆盖新运算符时挂起的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有一个内部内存管理器,我们使用我们的一个产品。内存管理器覆盖 new delete 运算符,并且在单线程应用程序中正常工作。然而,我现在的任务是使它与多线程应用程序一起工作。对我的理解下面的伪代码应该工作,但它挂在旋转,即使使用 try_lock()。任何想法?



更新#1



导致访问违规 / p>

  #include< mutex> 

std :: mutex g_mutex;

/ *!
\brief覆盖标准C ++新操作符
\param size [in]分配的字节数
* /
void * operator new(size_t size)
{
g_mutex.lock(); //访问冲突异常
...
}

永远在旋转:

  #include< mutex> 

std :: mutex g_mutex;
bool g_systemInitiated = false;


/ *!
\brief覆盖标准C ++新操作符
\param size [in]分配的字节数
* /
void * operator new(size_t size)
{
if(g_systemInitiated == false)return malloc(size);
g_mutex.lock(); //线程永远挂在这里。 g_mutex.try_lock()也挂起
...
}

int main(int argc,const char * argv [])
{
/ /告诉new()操作符系统已经启动
g_systemInitiated = true;
...
}

更新#2



递归互斥也会导致线程永久挂起:

  #include< mutex> 

std :: recursive_mutex g_mutex;
bool g_systemInitiated = false;


/ *!
\brief覆盖标准C ++新操作符
\param size [in]分配的字节数
* /
void * operator new(size_t size)
{
if(g_systemInitiated == false)return malloc(size);
g_mutex.lock(); //线程永远挂在这里。 g_mutex.try_lock()也挂起
...
}

int main(int argc,const char * argv [])
{
/ /告诉new()操作符系统已经启动
g_systemInitiated = true;
...
}

更新#3



Jonathan Wakely建议我尝试 unique_lock 和/或 lock_guard ,但是锁仍然挂起。



unique_lock
$ b

  #include< mutex> 

std :: mutex g_mutex;
std :: unique_lock< std :: mutex> g_lock1(g_mutex,std :: defer_lock);
bool g_systemInitiated = false;

/ *!
\brief覆盖标准C ++新操作符
\param size [in]分配的字节数
* /
void * operator new(size_t size)
{
if(g_systemInitiated == false)return malloc(size);
g_lock1.lock(); //线程永远在这里第一次被称为
...
}

int main(int argc,const char * argv [])
{
//告诉new()操作符,系统已经启动
g_systemInitiated = true;
...
}

lock_guard test:

  #include< mutex> 

std :: recursive_mutex g_mutex;
bool g_systemInitiated = false;


/ *!
\brief覆盖标准C ++新操作符
\param size [in]分配的字节数
* /
void * operator new(size_t size)
{
if(g_systemInitiated == false)return malloc(size);
std :: lock_guard< std :: mutex> g_lock_guard1(g_mutex); //线程永远在这里第一次被称为
...
}

int main(int argc,const char * argv [])
{
//告诉new()操作符,系统已经启动
g_systemInitiated = true;
...
}

我认为我的问题是 delete 在锁定时由C ++ 11互斥库调用。 delete 也被覆盖,如下:

  / *! 
\brief覆盖标准C ++新操作符
\param p [in]指向内存的指针以释放
* /
void operator delete(void * p)
{
if(g_systemInitiated == false)
{
free(p);
}
else
{
std :: lock_guard< std :: mutex> g_lock_guard1(g_mutex);
...
}
}

这会导致死锁情况我不能看到任何好的解决方案,除了使自己的锁,不产生任何调用删除

更新#4 实现我自己的自定义递归互斥体,它们没有调用 new delete ,也允许相同的线程进入

  #include< thread> 

std :: thread :: id g_lockedByThread;
bool g_isLocked = false;
bool g_systemInitiated = false;

/ *!
\brief覆盖标准C ++新操作符
\param size [in]分配的字节数
* /
void * operator new(size_t size)
{
if(g_systemInitiated == false)return malloc(size);

while(g_isLocked& g_lockedByThread!= std :: this_thread :: get_id());
g_isLocked = true; //原子操作
g_lockedByThread = std :: this_thread :: get_id();
...
g_isLocked = false;
}

/ *!
\brief覆盖标准C ++新操作符
\param p [in]指向内存的指针以释放
* /
void operator delete(void * p)
{
if(g_systemInitiated == false)
{
free(p);
}
else
{
while(g_isLocked& g_lockedByThread!= std :: this_thread :: get_id());
g_isLocked = true; //原子操作
g_lockedByThread = std :: this_thread :: get_id();
...
g_isLocked = false;
}
}

int main(int argc,const char * argv [])
{
//告诉new系统已启动
g_systemInitiated = true;
...
}

更新#5



尝试Jonathan Wakely的建议,发现它似乎肯定是微软的C ++ 11互斥体的实现有问题;如果使用 / MTd (多线程调试)编译器标志编译,则它的示例挂起,但如果使用 / MDd (多线程调试DLL)编译器标志。正如Jonathan指出, std :: mutex 实现应该是 constexpr 的。下面是我用来测试实现问题的VS 2012 C ++代码:

  #includestdafx.h

#include< mutex>
#include< iostream>

bool g_systemInitiated = false;
std :: mutex g_mutex;

void * operator new(size_t size)
{
if(g_systemInitiated == false)return malloc(size);
std :: lock_guard< std :: mutex> lock(g_mutex);
std :: cout<< Inside new()critical section< std :: endl;
//< - 内存管理器将在这里调用,哑调用malloc()代替
return malloc(size);
}

void operator delete(void * p)
{
if(g_systemInitiated == false)free(p);
else
{
std :: lock_guard< std :: mutex> lock(g_mutex);
std :: cout<< Inside delete()critical section< std :: endl;
//< - 内存管理器将在这里调用,虚拟调用free()代替
free(p);
}
}

int _tmain(int argc,_TCHAR * argv [])
{
g_systemInitiated = true;

char * test = new char [100];
std :: cout<< Allocated<< std :: endl;
delete test;
std :: cout<< 已删除<< std :: endl;

return 0;
}

更新#6
$ b

向Microsoft提交错误报告:
https://connect.microsoft.com/VisualStudio/feedback/details/776596/std-mutex-not-a-constexpr-with-mtd -compiler-flag#details

解决方案

互斥库使用 new ,而std :: mutex是不是递归(即可重入)。一个鸡和鸡蛋的问题。



UPDATE 正如在下面的注释中指出,使用std :: recursive_mutex可能工作。但是,对于全局变量的静态初始化顺序的经典C ++问题仍然存在,以及外部访问全局互斥体的危险(最好将其置于匿名命名空间中)。



UPDATE 2 您可能会将g_systemInitiated切换为true太早,即在互斥体已有机会完成其初始化之前,因此第一次通过调用 malloc()永远不会发生。要强制这样做,您可以尝试用对allocator模块中的初始化函数的调用替换main()中的赋值:

  namespace {
std :: recursive_mutex g_mutex;
bool g_initialized = false;
}
void initialize()
{
g_mutex.lock();
g_initialized = true;
g_mutex.unlock();
}


We have an internal memory manager that we use with one of our products. The memory manager overrides the new and delete operators, and works fine in single-threaded appliations. However, I'm now tasked to make it work with multi-threaded applications too. To my understanding the following pseudo code should work, but it hangs on a spin, even with try_lock(). Any ideas?

Update #1

Causes "Access Violation":

#include <mutex>

std::mutex g_mutex;

/*!
\brief Overrides the Standard C++ new operator
\param size [in] Number of bytes to allocate
*/
void *operator new(size_t size)
{
   g_mutex.lock(); // Access violation exception
   ...   
}

Causes thread to hang forever in a spin:

#include <mutex>

std::mutex g_mutex;
bool g_systemInitiated = false;


/*!
\brief Overrides the Standard C++ new operator
\param size [in] Number of bytes to allocate
*/
void *operator new(size_t size)
{
   if (g_systemInitiated == false) return malloc(size);
   g_mutex.lock(); // Thread hangs forever here. g_mutex.try_lock() also hangs
   ...   
}

int main(int argc, const char* argv[])
{
   // Tell the new() operator that the system has initiated
   g_systemInitiated = true;
   ...
}

Update #2

A recursive mutex also causes the thread to hang forever in a spin:

#include <mutex>

std::recursive_mutex g_mutex;
bool g_systemInitiated = false;


/*!
\brief Overrides the Standard C++ new operator
\param size [in] Number of bytes to allocate
*/
void *operator new(size_t size)
{
   if (g_systemInitiated == false) return malloc(size);
   g_mutex.lock(); // Thread hangs forever here. g_mutex.try_lock() also hangs
   ...   
}

int main(int argc, const char* argv[])
{
   // Tell the new() operator that the system has initiated
   g_systemInitiated = true;
   ...
}

Update #3

Jonathan Wakely suggested I should try unique_lock and/or lock_guard, but the lock still hangs in a spin.

unique_lock test:

#include <mutex>

std::mutex g_mutex;
std::unique_lock<std::mutex> g_lock1(g_mutex, std::defer_lock);
bool g_systemInitiated = false;

/*!
\brief Overrides the Standard C++ new operator
\param size [in] Number of bytes to allocate
*/
void *operator new(size_t size)
{
   if (g_systemInitiated == false) return malloc(size);
   g_lock1.lock(); // Thread hangs forever here the first time it is called
   ...   
}

int main(int argc, const char* argv[])
{
   // Tell the new() operator that the system has initiated
   g_systemInitiated = true;
   ...
}

lock_guard test:

#include <mutex>

std::recursive_mutex g_mutex;
bool g_systemInitiated = false;


/*!
\brief Overrides the Standard C++ new operator
\param size [in] Number of bytes to allocate
*/
void *operator new(size_t size)
{
   if (g_systemInitiated == false) return malloc(size);
   std::lock_guard<std::mutex> g_lock_guard1(g_mutex); // Thread hangs forever here the first time it is called
   ...   
}

int main(int argc, const char* argv[])
{
   // Tell the new() operator that the system has initiated
   g_systemInitiated = true;
   ...
}

I think my problem is that delete is called by the C++ 11 mutex library when locking. delete is also overridden like so:

/*!
\brief Overrides the Standard C++ new operator
\param p [in] The pointer to memory to free
*/
void operator delete(void *p)
{
    if (g_systemInitiated == false)
    {
       free(p); 
    }
    else
    {
       std::lock_guard<std::mutex> g_lock_guard1(g_mutex);
       ...
    }
}

This causes deadlocking situation that I can't see any good solution to except for making my own locking that does not spawn any calls to new or delete while locking or unlocking.

Update #4

I've implemented my own custom recursive mutex that have no calls to new or delete, also, it allows the same thread to enter a locked block.

#include <thread>

std::thread::id g_lockedByThread;
bool g_isLocked = false;
bool g_systemInitiated = false;

/*!
\brief Overrides the Standard C++ new operator
\param size [in] Number of bytes to allocate
*/
void *operator new(size_t size)
{
   if (g_systemInitiated == false) return malloc(size);

   while (g_isLocked && g_lockedByThread != std::this_thread::get_id());
   g_isLocked = true; // Atomic operation
   g_lockedByThread = std::this_thread::get_id();
   ...   
   g_isLocked = false;
}

/*!
\brief Overrides the Standard C++ new operator
\param p [in] The pointer to memory to free
*/
void operator delete(void *p)
{
    if (g_systemInitiated == false)
    {
       free(p); 
    }
    else
    {
       while (g_isLocked && g_lockedByThread != std::this_thread::get_id());
       g_isLocked = true; // Atomic operation
       g_lockedByThread = std::this_thread::get_id();
       ...   
       g_isLocked = false;
    }
}

int main(int argc, const char* argv[])
{
   // Tell the new() operator that the system has initiated
   g_systemInitiated = true;
   ...
}

Update #5

Tried Jonathan Wakely suggestion, and found that it definitely seems that there is something wrong with Microsoft's implementation of C++ 11 Mutexes; his example hangs if compiled with the /MTd (Multi-threaded Debug) compiler flag, but works fine if compiled with the /MDd (Multi-threaded Debug DLL) compiler flag. As Jonathan rightly pointed out std::mutex implementations are supposed to be constexpr's. Here is the VS 2012 C++ code I used to test the implementation issue:

#include "stdafx.h"

#include <mutex>
#include <iostream>

bool g_systemInitiated = false;
std::mutex g_mutex;

void *operator new(size_t size)
{
    if (g_systemInitiated == false) return malloc(size);
    std::lock_guard<std::mutex> lock(g_mutex);
    std::cout << "Inside new() critical section" << std::endl;
    // <-- Memory manager would be called here, dummy call to malloc() in stead
    return malloc(size);
}

void operator delete(void *p)
{
    if (g_systemInitiated == false) free(p); 
    else
    {
        std::lock_guard<std::mutex> lock(g_mutex);
        std::cout << "Inside delete() critical section" << std::endl;
        // <-- Memory manager would be called here, dummy call to free() in stead
        free(p);
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    g_systemInitiated = true;

    char *test = new char[100];
    std::cout << "Allocated" << std::endl;
    delete test;
    std::cout << "Deleted" << std::endl;

    return 0;
}

Update #6

Submitted a bug report to Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/776596/std-mutex-not-a-constexpr-with-mtd-compiler-flag#details

解决方案

The mutex library uses new, and std::mutex is not recursive (i.e. reentrant) by default. A chicken-and-egg problem.

UPDATE As has been pointed out in the comments below, using std::recursive_mutex may work. But the classic C++ problem of the order of static initialization of globals being not well defined remains, as does the danger of outside access to the global mutex (best to put it inside an anonymous namespace.)

UPDATE 2 You may be switching g_systemInitiated to true too early, i.e. before the mutex has had a chance to complete its initialization, and so the "first-time-through" call to malloc() never happens. To force this, you could try replacing the assignment in main() with a call to an initialization function in the allocator module:

namespace {
    std::recursive_mutex    g_mutex;
    bool                    g_initialized = false;
}
void initialize()
{
    g_mutex.lock();
    g_initialized = true;
    g_mutex.unlock();
}

这篇关于std :: mutex锁在覆盖新运算符时挂起的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆