5

当社の製品の 1 つで使用する内部メモリ マネージャーがあります。メモリ マネージャーはnewanddelete演算子をオーバーライドし、シングル スレッド アプリケーションで正常に動作します。ただし、マルチスレッド アプリケーションでも動作するようにする必要があります。私の理解では、次の疑似コードは機能するはずですが、try_lock(). 何か案は?

更新 #1

「アクセス違反」の原因:

#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
   ...   
}

スピン中にスレッドを永久にハングさせる:

#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;
   ...
}

アップデート #2

再帰的ミューテックスも、スレッドをスピンで永久にハングさせます。

#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;
   ...
}

アップデート #3

unique_lockJonathan Wakely は、 and/orを試してみるべきだと提案しlock_guardましたが、ロックはまだスピンでハングしています。

unique_lockテスト:

#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テスト:

#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;
   ...
}

私の問題は、deleteロック時に C++ 11 ミューテックス ライブラリによって呼び出されることだと思います。deleteも次のようにオーバーライドされます。

/*!
\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);
       ...
    }
}

newこれにより、ロック中またはロック解除中の呼び出しを生成しない独自のロックを作成する以外に、適切な解決策が見当たらないデッドロック状況が発生しますdelete

アップデート #4

neworへの呼び出しがない独自のカスタム再帰ミューテックスを実装しましたdelete。また、同じスレッドがロックされたブロックに入ることができます。

#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;
   ...
}

アップデート #5

Jonathan Wakely の提案を試してみたところ、Microsoft の C++ 11 Mutex の実装に問題があるように思われることがわかりました。この例は、(マルチスレッド デバッグ) コンパイラ フラグを使用してコンパイルするとハングしますが、 (マルチスレッド デバッグ DLL) コンパイラ フラグ/MTdを使用してコンパイルすると正常に動作します。/MDdジョナサンが正しく指摘したようstd::mutexに、実装はconstexpr. 実装の問題をテストするために使用した VS 2012 C++ コードを次に示します。

#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;
}

更新 #6

Microsoft にバグ レポートを提出しました: https://connect.microsoft.com/VisualStudio/feedback/details/776596/std-mutex-not-a-constexpr-with-mtd-compiler-flag#details

4

2 に答える 2

1

デフォルトでは、 mutex ライブラリは を使用しnew、std::mutex は再帰的 (つまり再入可能) ではありません。ニワトリが先か卵が先かの問題。

更新以下のコメントで指摘されているように、 std::recursive_mutex を使用するとうまくいく場合があります。しかし、グローバル ミューテックスへの外部アクセスの危険性と同様に、グローバルの静的初期化の順序が適切に定義されていないという古典的な C++ の問題が残っています (匿名名前空間内に配置するのが最善です)。

更新 2 g_systemInitiated を true に切り替えるのが早すぎる可能性があります。つまり、ミューテックスが初期化を完了する前に、「初回スルー」呼び出しがmalloc()発生しません。これを強制するには、main() の代入をアロケーター モジュールの初期化関数の呼び出しに置き換えてみてください。

namespace {
    std::recursive_mutex    g_mutex;
    bool                    g_initialized = false;
}
void initialize()
{
    g_mutex.lock();
    g_initialized = true;
    g_mutex.unlock();
}
于 2013-01-14T14:08:10.367 に答える
1

最初の例を除いて、すべての例が壊れています。これは、スコープ付きロックタイプを使用しないための非常に悪い習慣です。

コンパイラが機能しない場合、または標準ライブラリが壊れている場合、これは機能するはずです。

#include <mutex>

std::mutex g_mutex;

void *operator new(size_t size)
{
   std::lock_guard<std::mutex> lock(g_mutex);
   ...   
}
于 2013-01-15T13:41:17.903 に答える