C++:处理线程本地对象销毁



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

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

我拥有的线程本地对象类似于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缓冲区,但它会立即被销毁,因为对象以与创建相反的顺序销毁,这是创建的最后一个静态对象。当下一个全局对象析构函数调用记录器时,它正在有效地访问已销毁的对象,我认为这是问题所在。

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

我创建了一个玩具程序来尝试这个,我看到buffers ?检查确实阻止了访问。但在真正的代码库中,这并没有发生。在崩溃点,向量被访问在它已经是烤面包。

现在,如果有人能告诉我一个神奇的解决方案,它将让我的生活变得轻松:-(。否则知道为什么unique_ptrbool运算符不返回false.是因为访问已销毁的对象是经典的未定义行为。

我在堆栈溢出中读到,如果对象有一个微不足道的析构函数,则可以在销毁后访问它。在这种情况下,如果我在unique_ptr上方创建一个线程本地bool,并在包含unique_ptr的包装类的析构函数中将其设置为 true,我的问题会得到解决吗?

但我

查看了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.
};

Schwarz 计数器或 Nifty Counter Idiom 可以做你想做的事,但它不是"魔法"。 你可以想出一个宏来减少使用起来的麻烦(查看非标准__COUNTER__(,但它的要点是:

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

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

这保证了首次使用前的创建和最后一次使用后的销毁。

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

您可以使用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;
}

最新更新