C ++:处理线程本地对象破坏

时间:2018-10-16 22:42:19

标签: c++ c++14 unique-ptr thread-local

我有一个日志记录系统,该系统基本上使用线程本地缓冲区进行日志记录。这有助于减少锁定。可以将一堆消息写入线程本地缓冲区中,并一口气刷新。而且由于它是线程本地的,所以我们可以避免为每个日志消息分配缓冲区。

无论如何,问题出在流程退出期间。访问线程本地缓冲区时,我们看到崩溃。

我拥有的线程本地对象类似于std::vector<Buffer>。 [{vector,因为有多个缓冲区]。代表性的代码是这样的。

Buffer* getBuffer (int index)
{
    static thread_local auto buffers =    std::make_unique<std::vector<Buffer>>();
    return buffers ? buffers->at(index) : nullptr;
}

现在,当程序退出并调用全局析构函数时,不幸的是其中一些会记录日志。析构函数从主线程调用(否则不执行任何操作)。因此,当第一个全局对象被销毁并调用记录器时,将创建thread_local缓冲区,但该线程将立即被销毁,因为按相反的创建顺序销毁了对象,这是最后创建的静态对象。当下一个全局对象析构函数调用logger时,它正在有效地访问被破坏的对象,我认为这是问题所在。

但是我查看了unique_ptr析构函数,它确实将其内部的指针设置为nullptr [或者至少将其设置为默认构造的指针-我认为值初始化为零?]。因此,我的return buffers ? buffers->at(index) : nullptr;检查应该阻止了对释放对象的访问,不是吗?

我创建了一个玩具程序来尝试该操作,发现buffers ?检查确实阻止了访问。但是在真正的代码库中却没有发生。在崩溃点,访问矢量已经是吐司了。

现在,如果有人可以告诉我一个神奇的解决方案,它将使我的生活变得轻松:-)。否则,任何想法为何bool的{​​{1}}运算符都不会返回unique_ptr。是因为访问被破坏的对象是经典的未定义行为。

我在堆栈溢出中看到,如果对象具有琐碎的析构函数,则销毁后可以访问它。在那种情况下,如果我在false上方创建一个线程局部的bool,并在包含unique_ptr的包装类的析构函数中将其设置为true,我的问题是否可以解决?

3 个答案:

答案 0 :(得分:3)

  

但是我查看了unique_ptr析构函数,它确实将其中的指针设置为nullptr

没关系。一旦对象的寿命结束,以任何方式访问对象就是UB。所以这行不通。

从寿命上看问题

问题。

您的全局日志超出了某些本地缓冲区的范围。

解决方案

全局日志的生存期必须比本地缓冲区的生存期长。

如何实现

如果全局日志的生存期必须长于缓冲区,则必须首先创建它。要强制执行此操作,请确保在构造本地缓冲区时要求对全局缓冲区的引用。这将强制首先创建全局日志,从而在销毁本地缓冲区时仍然有效。

示例解决方案

类似这样的东西:

class Log
{
    public:
        static Log& getLog()
        {
            static Log theOneAndOnlyLog;
            return theOneAndOnlyLog;
        }
    }
};

class BufferFrame
{
    std::vector<Buffer>   buffer;
    BufferFrame()
    {
        Log::getLog();   // Force the log to be created first.
                         // Note: Order of destruction is guranteed
                         //       for static storage duration objects
                         //       to be the exact reverse of the order of
                         //       creation.
                         //
                         // This means if A is created before B
                         // Then B must be destroyed before A
                         //
                         // Here we know that `theOneAndOnlyLog`
                         // has been constructed (fully) thus `this`
                         // object is created after it. Thus this object
                         // will be destroyed before `theOneAndOnlyLog`.
                         //
                         // This means you can safely accesses `theOneAndOnlyLog`
                         // from the destructor of this object.
    }
    ~BufferFrame()
    {
        // We know the log has been created first
        // So we know it is still alive now.
        foreach(Buffer& buf: buffer) {
             Log::getLog() << buf; // Dump Buffer
        }
    }
    Buffer& at(std::size_t index)
    {
        return buffer.at(index);
    }
};
Buffer& getBuffer(int index)
{
    static thread_local BufferFrame buffers;
    return buffers.at(index);  // Note this will throw if index is out of range.
}

class MyObjectThatLogsToBuffer
{
    public:
        MyObjectThatLogsToBuffer()
        {
            getBuffer(0);   // Have created the FramBuffer
                            // It is created first. So it will be
                            // destroyed after me. So it is safe to
                            // access in destructor.
        }
        ~MyObjectThatLogsToBuffer()
        {
            log("I am destroyed");  // assume this calls getBuffer()
        }                           // while logging. Then it will work.
};

答案 1 :(得分:2)

Schwarz计数器或Nifty Counter惯用语可以完成您想要的操作,但这不是“魔术”。您也许可以提出一个宏来减少使用的麻烦(请查看非标准的__COUNTER__),但是其要点是:

在最顶部的每个编译单元(.cpp文件)中,放置一个变量的实例,该实例可递增/递减静态计数器和指向记录器类型的真实对象的指针。

当计数器从0变为1时,将动态创建“目标”对象。当计数器从1变为0时,“目标”对象将被破坏。否则,此管理器对象的构造函数/析构函数将不执行任何操作。

这样可以保证在首次使用前创建,并在最后一次使用后销毁。

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter#Also_Known_As

答案 2 :(得分:-3)

您可以在此处使用std::weak_ptr来跟踪其他线程超出范围的内容。
我没有一个简单的例子。 那里不容易:
https://github.com/alexeyneu/bitcoin/commit/bbd5aa3e36cf303779d888764e1ebb3bd2242a4a

关键行:

    std::weak_ptr<int> com_r;
...
   bhr->SetProgressValue(hwnd , com_r.expired() == 0 ? reserve = *com_r.lock() : reserve, 190);

extern  std::weak_ptr<int> com_r;
...
//inside a class
   std::shared_ptr<int> sp_tray;

   com_r = sp_tray;

  *sp_tray = nVerificationProgress*190;

这是测试用例(已更新)

#include "stdafx.h"
#include "bay.h"
#include <condition_variable>
#include <thread>
#include <atomic>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include <iostream>

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;                                // current instance
wchar_t szTitle[MAX_LOADSTRING];                    // The title bar text
wchar_t szWindowClass[MAX_LOADSTRING];          // the main window class name

// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.
    MSG msg;
    HACCEL hAccelTable;

    // Initialize global strings
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadString(hInstance, IDC_BAY, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // Perform application initialization:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_BAY));

    // Main message loop:
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_BAY));
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCE(IDC_BAY);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassEx(&wcex);
}

HWND hWnd;



BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}
    std::thread u,u2;
    UINT CALLBACK hammer(VOID *c);
    UINT CALLBACK hammersmith(VOID *c);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    HDC hdc;

    switch (message)
    {
    case WM_COMMAND:
        wmId = LOWORD(wParam);
        wmEvent = HIWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_EXIT:
            break;
        case IDM_LETSGO:
            u = std::thread(&hammer,(LPVOID)NULL);
            u2 = std::thread(&hammersmith,(LPVOID)NULL);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;

    case WM_CLOSE:
        DefWindowProc(hWnd, message, wParam, lParam);
        break;          
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
return 0;
}
std::shared_ptr<int> sp_tray;
std::weak_ptr<int> com_r;
std::mutex com_m;   
UINT CALLBACK hammer(VOID *c)
{
    int reserve = 0;
    AllocConsole();
    freopen("CON", "w", stdout);
    while (1)
    {
    std::unique_lock<std::mutex> lb(com_m);
    reserve = com_r.expired() == 0 ? *com_r.lock(): 5;
    lb.unlock();
    std::cout << reserve;   
    }

    return 0;

}
UINT CALLBACK hammersmith(VOID *c)
{   
    while (1)
    {   
        std::unique_lock<std::mutex> lb(com_m);
        sp_tray = std::shared_ptr<int>(new int(7));
        com_r = sp_tray;
        lb.unlock();    
        sp_tray.reset();
    }

    return 0;

}