我有一个日志记录系统,该系统基本上使用线程本地缓冲区进行日志记录。这有助于减少锁定。可以将一堆消息写入线程本地缓冲区中,并一口气刷新。而且由于它是线程本地的,所以我们可以避免为每个日志消息分配缓冲区。
无论如何,问题出在流程退出期间。访问线程本地缓冲区时,我们看到崩溃。
我拥有的线程本地对象类似于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,我的问题是否可以解决?>
答案 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;
}