C ++: обрабатывать уничтожение локального объекта потока - PullRequest
0 голосов
/ 17 октября 2018

У меня есть система регистрации, которая в основном использует локальный буфер потока для регистрации.Это помогает в уменьшении блокировки.Связка сообщений может быть записана в локальный буфер потока и очищена за один раз.А также, поскольку он является локальным для потока, мы можем избежать выделения буфера для каждого сообщения журнала.

В любом случае проблема возникает при выходе из процесса.Мы видим сбой при доступе к локальному буферу потока.

У меня есть локальный объект потока, похожий на 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 для unique_ptr не возвращает false.Это потому, что это классическое неопределенное поведение при доступе к разрушенному объекту.

Я прочитал в переполнении стека, что если у объекта есть тривиальный деструктор, то можно получить к нему доступ после уничтожения.В этом случае моя проблема будет решена, если я создам локальный поток bool чуть выше unique_ptr и установлю его в true в деструкторе класса-оболочки, содержащего unique_ptr?

Ответы [ 3 ]

0 голосов
/ 17 октября 2018

Но я посмотрел деструктор 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.
};
0 голосов
/ 17 октября 2018

вы можете использовать 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;

}
0 голосов
/ 17 октября 2018

Счетчик Schwarz или Nifty Counter Idiom может делать то, что вы хотите, но это не «магия».Возможно, вам удастся придумать макрос, чтобы сделать его менее громоздким (обратите внимание на нестандартный __COUNTER__), но суть его такова:

В каждом модуле компиляции (файл .cpp) вВ самом верху вы помещаете экземпляр переменной, которая увеличивает / уменьшает статический счетчик и указатель на реальный объект типа регистратора.

Когда счетчик изменяется от 0 до 1, объект "target" имеет виддинамически создан.Когда счетчик становится от 1 до 0, объект «цель» уничтожается.В противном случае конструктор / деструктор этого объекта менеджера ничего не делает.

Это гарантирует создание перед первым использованием и уничтожение после последнего использования.

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

...