Qt源码分析: QEventLoop实现原理
QEventLoop屏蔽了底层消息循环实现细节,向上提供了与平台无关的消息/事件循环。
本文拟对Windows系统下QEventLoop的实现原理予以分析。
注1:限于研究水平,分析难免不当,欢迎批评指正。
注2:文章内容会不定期更新。
一、研究素材:Win32应用程序框架
在Win32应用程序中,wWinMain是整个程序的入口点,整个代码段主要包括窗口类注册、创建窗口、消息循环等。
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPWSTR lpCmdLine,_In_ int nCmdShow)
{UNREFERENCED_PARAMETER(hPrevInstance);UNREFERENCED_PARAMETER(lpCmdLine);// TODO: Place code here.// Initialize global stringsLoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);MyRegisterClass(hInstance);// Perform application initialization:if (!InitInstance (hInstance, nCmdShow)){return FALSE;}HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));MSG msg;// Main message loop:while (GetMessage(&msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}return (int) msg.wParam;
}
MyRegisterClass用于注册创库类,可以指定窗口样式、窗口过程函数等。
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{WNDCLASSEXW 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_WINDOWSPROJECT1));wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);wcex.lpszClassName = szWindowClass;wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));return RegisterClassExW(&wcex);
}
完成窗口类注册之后,可以依据窗口类创建窗口实例,
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{hInst = hInstance; // Store instance handle in our global variableHWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);if (!hWnd){return FALSE;}ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return TRUE;
}
当完成窗口类注册之后,便可以依据窗口类名称来创建窗口。在Windows系统下,消息队列用于管理线程内窗口相关的消息,同一线程内的窗口对象共享同一各消息队列。
The Message Loop
For each thread that creates a window, the operating system creates a queue for window messages. This queue holds messages for all the windows that are created on that thread.
MSG msg;// Main message loop:while (GetMessage(&msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}
当消息被投递到窗口时,便会调用对应的窗口过程函数,
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_COMMAND:{int wmId = LOWORD(wParam);// Parse the menu selections:switch (wmId){case IDM_ABOUT:DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}}break;case WM_PAINT:{PAINTSTRUCT ps;HDC hdc = BeginPaint(hWnd, &ps);// TODO: Add any drawing code that uses hdc here...EndPaint(hWnd, &ps);}break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}
二、QEventLoop实现原理
QEventLoop实际上是通过QAbstractEventDispatcher子类来屏蔽了底层窗口系统的消息循环。
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{Q_D(QEventLoop);if (!d->threadData->hasEventDispatcher())return false;return d->threadData->eventDispatcher.loadRelaxed()->processEvents(flags);
}
从中可以看出,QAbstractEventDispatcher是线程级别的存在,每个线程有唯一的QAbstractEventDispatcher实现,线程内的所有QEventLoop共享同一个QAbstractEventDispatcher实现。
Ref. from QAbstractEventDispatcher
An event dispatcher receives events from the window system and other sources. It then sends them to the QCoreApplication or QApplication instance for processing and delivery. QAbstractEventDispatcher provides fine-grained control over event delivery.
2.1 QEventDispatcherWin32的创建
实际上,在Windows系统下,当创建QCoreApplication时,便会创建主线程相关的QEventDispatcherWin32,而QEventDispatcherWin32正是QAbstractEventDispatcher的Windows系统实现。
void QCoreApplicationPrivate::init()
{// ...
#ifndef QT_NO_QOBJECT// use the event dispatcher created by the app programmer (if any)Q_ASSERT(!eventDispatcher);eventDispatcher = threadData->eventDispatcher.loadRelaxed();// otherwise we create oneif (!eventDispatcher)createEventDispatcher();Q_ASSERT(eventDispatcher);if (!eventDispatcher->parent()) {eventDispatcher->moveToThread(threadData->thread.loadAcquire());eventDispatcher->setParent(q);}threadData->eventDispatcher = eventDispatcher;eventDispatcherReady();
#endif// ...
}
void QCoreApplicationPrivate::createEventDispatcher()
{Q_Q(QCoreApplication);QThreadData *data = QThreadData::current();Q_ASSERT(!data->hasEventDispatcher());eventDispatcher = data->createEventDispatcher();eventDispatcher->setParent(q);
}
QAbstractEventDispatcher *QThreadData::createEventDispatcher()
{QAbstractEventDispatcher *ed = QThreadPrivate::createEventDispatcher(this);eventDispatcher.storeRelease(ed);ed->startingUp();return ed;
}
QAbstractEventDispatcher *QThreadPrivate::createEventDispatcher(QThreadData *data)
{Q_UNUSED(data);
#ifndef Q_OS_WINRTreturn new QEventDispatcherWin32;
#elsereturn new QEventDispatcherWinRT;
#endif
}
2.2 QEventDispatcherWin32的实现
由前面的分析可知,在Windows系统下,QEventLoop实际上是使用QEventDispatcherWin32来完成消息循环。那QEventDispatcherWin32又是如何实现对Windows消息循环的封装呢?
在QEventDispatcherWin32中,注册了一个"QEventDispatcherWin32_Internal_Widget"窗口类,而对应的窗口过程函数主要是提供了对定时器、套接字等消息处理。
QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext(): atom(0), className(0)
{// make sure that multiple Qt's can coexist in the same processconst QString qClassName = QStringLiteral("QEventDispatcherWin32_Internal_Widget")+ QString::number(quintptr(qt_internal_proc));className = new wchar_t[qClassName.size() + 1];qClassName.toWCharArray(className);className[qClassName.size()] = 0;WNDCLASS wc;wc.style = 0;wc.lpfnWndProc = qt_internal_proc;wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hInstance = GetModuleHandle(0);wc.hIcon = 0;wc.hCursor = 0;wc.hbrBackground = 0;wc.lpszMenuName = NULL;wc.lpszClassName = className;atom = RegisterClass(&wc);if (!atom) {qErrnoWarning("%ls RegisterClass() failed", qUtf16Printable(qClassName));delete [] className;className = 0;}
}
同时,依据该窗口类,创建了一个内部窗口,
void QEventDispatcherWin32::createInternalHwnd()
{Q_D(QEventDispatcherWin32);if (d->internalHwnd)return;d->internalHwnd = qt_create_internal_window(this);// setup GetMessage hook needed to drive our posted eventsd->getMessageHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC) qt_GetMessageHook, NULL, GetCurrentThreadId());if (Q_UNLIKELY(!d->getMessageHook)) {int errorCode = GetLastError();qFatal("Qt: INTERNAL ERROR: failed to install GetMessage hook: %d, %ls",errorCode, qUtf16Printable(qt_error_string(errorCode)));}// start all normal timersfor (int i = 0; i < d->timerVec.count(); ++i)d->registerTimer(d->timerVec.at(i));
}
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
{QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext();if (!ctx->atom)return 0;HWND wnd = CreateWindow(ctx->className, // classnamectx->className, // window name0, // style0, 0, 0, 0, // geometryHWND_MESSAGE, // parent0, // menu handleGetModuleHandle(0), // application0); // windows creation data.if (!wnd) {qErrnoWarning("CreateWindow() for QEventDispatcherWin32 internal window failed");return 0;}#ifdef GWLP_USERDATASetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(eventDispatcher));
#elseSetWindowLong(wnd, GWL_USERDATA, reinterpret_cast<LONG>(eventDispatcher));
#endifreturn wnd;
}
在QEventDispatcherWin32::processEvents中,会不断的调用PeekMessage检查消息队列,然后调用TranslateMessage与DispatchMessage进行消息转发。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{Q_D(QEventDispatcherWin32);if (!d->internalHwnd) {createInternalHwnd();wakeUp(); // trigger a call to sendPostedEvents()}d->interrupt.storeRelaxed(false);emit awake();// To prevent livelocks, send posted events once per iteration.// QCoreApplication::sendPostedEvents() takes care about recursions.sendPostedEvents();bool canWait;bool retVal = false;do {DWORD waitRet = 0;DWORD nCount = 0;HANDLE *pHandles = nullptr;if (d->winEventNotifierActivatedEvent) {nCount = 1;pHandles = &d->winEventNotifierActivatedEvent;}QVarLengthArray<MSG> processedTimers;while (!d->interrupt.loadRelaxed()) {MSG msg;bool haveMessage;if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {// process queued user input eventshaveMessage = true;msg = d->queuedUserInputEvents.takeFirst();} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {// process queued socket eventshaveMessage = true;msg = d->queuedSocketEvents.takeFirst();} else {haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);if (haveMessage) {if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)&& isUserInputMessage(msg.message)) {// queue user input events for later processingd->queuedUserInputEvents.append(msg);continue;}if ((flags & QEventLoop::ExcludeSocketNotifiers)&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {// queue socket events for later processingd->queuedSocketEvents.append(msg);continue;}}}if (!haveMessage) {// no message - check for signalled objectswaitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) {// a new message has arrived, process itcontinue;}}if (haveMessage) {if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {// Set result to 'true', if the message was sent by wakeUp().if (msg.wParam == WMWP_QT_FROMWAKEUP)retVal = true;continue;}if (msg.message == WM_TIMER) {// avoid live-lock by keeping track of the timers we've already sentbool found = false;for (int i = 0; !found && i < processedTimers.count(); ++i) {const MSG processed = processedTimers.constData()[i];found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);}if (found)continue;processedTimers.append(msg);} else if (msg.message == WM_QUIT) {if (QCoreApplication::instance())QCoreApplication::instance()->quit();return false;}if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}} else if (waitRet - WAIT_OBJECT_0 < nCount) {activateEventNotifiers();} else {// nothing todo so breakbreak;}retVal = true;}// still nothing - wait for message or signalled objectscanWait = (!retVal&& !d->interrupt.loadRelaxed()&& (flags & QEventLoop::WaitForMoreEvents));if (canWait) {emit aboutToBlock();waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);emit awake();if (waitRet - WAIT_OBJECT_0 < nCount) {activateEventNotifiers();retVal = true;}}} while (canWait);return retVal;
}
2.3 小结
依据上述对QEventLoop的实现分析,大体上可有以下结论:
1. QEventLoop内部实际上是通过QAbstractEventDispatcher子类来完成了消息(事件)分发,这实际上就是GoF's Bridge Pattern。
2. QAbstractEventDispatcher定义了事件分发的接口,而针对具体窗口系统的实现则有QEventDispatcherWin32、QEventDispatcherWinRT、QEventDispatcherUNIX、QEventDispatcherGlib等负责。
3. 每个线程都有唯一的同一事件分发器,线程内的窗体共用同一事件分发器。
4. 在Windows系统下,QEventLoop::processEvents函数大体等价于PeekMessage/TranslateMessage/DispatchMessage。
三、Qt中的事件路由
3.1 消息转换
在Qt中,当窗体接受到底层窗口系统消息之后,又是如何将这些平台相关的消息转换成Qt事件呢?
Ref. from QEvent
Qt's main event loop (QCoreApplication::exec()) fetches native window system events from the event queue, translates them into QEvents, and sends the translated events to QObjects.
In general, events come from the underlying window system (spontaneous() returns true), but it is also possible to manually send events using QCoreApplication::sendEvent() and QCoreApplication::postEvent() (spontaneous() returns false).
实际上,对于QGuiApplication,会基于QPA (Qt Platform Abstraction)来创建QPlatformIntegration,进而创建特定的QAbstractEventDispatcher。
Ref. from Qt Platform Abstraction
The Qt Platform Abstraction (QPA) is the platform abstraction layer for Qt 5 and replaces Qt for Embedded Linux and the platform ports from Qt 4.
QPA plugins are implemented by subclassing various QPlatform* classes. There are several root classes, such as QPlatformIntegration and QPlatformWindow for window system integration and QPlatformTheme for deeper platform theming and integration. QStyle is not a part of QPA.
void QGuiApplicationPrivate::createEventDispatcher()
{Q_ASSERT(!eventDispatcher);if (platform_integration == 0)createPlatformIntegration();// The platform integration should not mess with the event dispatcherQ_ASSERT(!eventDispatcher);eventDispatcher = platform_integration->createEventDispatcher();
}
对于Windows系统,QGuiApplication会通过加载qwindows.dll插件来创建QWindowsIntegration对象。
QAbstractEventDispatcher * QWindowsIntegration::createEventDispatcher() const
{return new QWindowsGuiEventDispatcher;
}
也就是说,在Windows系统下,QGuiApplication实际上是通过QWindowsGuiEventDispatcher来实现对底层消息的调度。
void QWindowsGuiEventDispatcher::sendPostedEvents()
{QEventDispatcherWin32::sendPostedEvents();QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}
bool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{int nevents = 0;while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) {QWindowSystemInterfacePrivate::WindowSystemEvent *event = nullptr;if (QWindowSystemInterfacePrivate::platformFiltersEvents) {event = QWindowSystemInterfacePrivate::getWindowSystemEvent();} else {event = flags & QEventLoop::ExcludeUserInputEvents ?QWindowSystemInterfacePrivate::getNonUserInputWindowSystemEvent() :QWindowSystemInterfacePrivate::getWindowSystemEvent();}if (!event)break;if (QWindowSystemInterfacePrivate::eventHandler) {if (QWindowSystemInterfacePrivate::eventHandler->sendEvent(event))nevents++;} else {nevents++;QGuiApplicationPrivate::processWindowSystemEvent(event);}// Record the accepted state for the processed event// (excluding flush events). This state can then be// returned by flushWindowSystemEvents().if (event->type != QWindowSystemInterfacePrivate::FlushEvents)QWindowSystemInterfacePrivate::eventAccepted.storeRelaxed(event->eventAccepted);delete event;}return (nevents > 0);
}
对于来自底层窗口系统的鼠标消息,可以看到QWindowsGuiEventDispatcher最终是通过调用QGuiApplicationPrivate::processMouseEvent,
void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e)
{Q_TRACE_SCOPE(QGuiApplicationPrivate_processWindowSystemEvent, e->type);switch(e->type) {case QWindowSystemInterfacePrivate::Mouse:QGuiApplicationPrivate::processMouseEvent(static_cast<QWindowSystemInterfacePrivate::MouseEvent *>(e));break;// ...default:qWarning() << "Unknown user input event type:" << e->type;break;}
}
而正是在这个函数中,实现了底层窗口消息到Qt事件的转换。
void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent *e)
{// ...QMouseEvent ev(type, localPoint, localPoint, globalPoint, button, e->buttons, e->modifiers, e->source);ev.setTimestamp(e->timestamp);if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) {// a modal window is blocking this window, don't allow mouse events throughreturn;}if (doubleClick && (ev.type() == QEvent::MouseButtonPress)) {// QtBUG-25831, used to suppress delivery in qwidgetwindow.cppsetMouseEventFlags(&ev, ev.flags() | Qt::MouseEventCreatedDoubleClick);}QGuiApplication::sendSpontaneousEvent(window, &ev);// ...
}
以上代码分析,也可通过在Qt应用程序设置断点,观察函数堆栈调用来进一步佐证。
3.2 事件处理
当底层窗口消息转换成Qt事件之后,最终会由QCoreApplication进行事件路由。
bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{// no events are delivered after ~QCoreApplication() has startedif (QCoreApplicationPrivate::is_app_closing)return true;return doNotify(receiver, event);
}static bool doNotify(QObject *receiver, QEvent *event)
{if (receiver == 0) { // serious errorqWarning("QCoreApplication::notify: Unexpected null receiver");return true;}#ifndef QT_NO_DEBUGQCoreApplicationPrivate::checkReceiverThread(receiver);
#endifreturn receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{// Note: when adjusting the tracepoints in here// consider adjusting QApplicationPrivate::notify_helper too.Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type());bool consumed = false;bool filtered = false;Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);// send to all application event filters (only does anything in the main thread)if (QCoreApplication::self&& receiver->d_func()->threadData->thread.loadAcquire() == mainThread()&& QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {filtered = true;return filtered;}// send to all receiver event filtersif (sendThroughObjectEventFilters(receiver, event)) {filtered = true;return filtered;}// deliver the eventconsumed = receiver->event(event);return consumed;
}
从中可以看出,Qt事件首先分别让应用程序过滤器、对象过滤器进行处理,最后再交由虚函数QObject::event(QEvent *e)进行处理。
对于QWidget,QWidget::event(QEvent *e)实际上就是通过调用mousePressEvent(QMouseEvent *event) 、mouseReleaseEvent(QMouseEvent *event)等虚函数来处理各种具体的事件。
3.3 小结
基于上述分析,可有以下结论:
1. Qt基于QPA实现了跨窗口系统。QPA实际上是一种插件系统。
2. 在Windows系统下,QGuiApplication创建QWindowsIntegration。
3. QCoreApplication使用QEventDispatcherWin32进行消息调度;
4. QWindowsIntegration使用QWindowsGuiEventDispatcher进行消息调度。
5. QEvent先有系统级的filter处理,再有对象级的filter处理,最后交由目标对象处理。
四、扩展:Qt插件系统
4.1 动态插件
4.2 静态插件
参考文献
Erich Gamma. Design Patterns:elements of reusable object-oriented software. Addison Wesley, 1994.
Joseph Ingeno. Handbook of Software Architecture.
参考资料
QEventLoop
Get Started with Win32 and C++
QAbstractEventDispatcher
QThread
QCoreApplication
相关文章:
Qt源码分析: QEventLoop实现原理
QEventLoop屏蔽了底层消息循环实现细节,向上提供了与平台无关的消息/事件循环。 本文拟对Windows系统下QEventLoop的实现原理予以分析。 注1:限于研究水平,分析难免不当,欢迎批评指正。 注2:文章内容会不定期更新。 …...
痛失offer的八股
java面试八股 mysql篇: 事物的性质: 事物的性质有acid四特性。 a:automic,原子性,要么全部成功,要么全部失败,mysql的undolog,事物在执行的时候,mysql会进行一个快照读…...
【Git】第一课:Git的介绍
简介 什么是Git? Git是一个开源的分布式版本控制系统,用于跟踪代码的改变和协同开发。它最初由Linus Torvalds为了管理Linux内核开发而创建,现已成为开源软件开发中最流行的版本控制系统,没有之一。Git允许多人同时在不同的分支上工作&…...
知识蒸馏——深度学习的简化之道 !!
文章目录 前言 1、什么是知识蒸馏 2、知识蒸馏的原理 3、知识蒸馏的架构 4、应用 结论 前言 在深度学习的世界里,大型神经网络因其出色的性能和准确性而备受青睐。然而,这些网络通常包含数百万甚至数十亿个参数,使得它们在资源受限的环境下&…...
【爬虫】Selenium打开新tab页截图并关闭
如果说 你曾苦过我的甜 我愿活成你的愿 愿不枉啊 愿勇往啊 这盛世每一天 山河无恙 烟火寻常 可是你如愿的眺望 孩子们啊 安睡梦乡 像你深爱的那样 🎵 王菲《如愿》 在自动化测试和网页抓取中,Selenium WebDriver 是一个强大的工具&…...
09 事务和连接池
文章目录 properties文件连接池service层实现类dao层实现类dao层实现类 连接池类: 创建线程池静态常量,用于放连接。 创建Properties静态常量,用于解析properties文件 静态代码块中,解析properties文件,将解析结果用于创建连接池 …...
P4344 [SHOI2015] 脑洞治疗仪 线段树+二分
主要是维护一个连续区间,比较经典的题目,还要考虑一下二分的情况,否则很难处理,比较有难度。这里和序列操作一题的区别是不需要考虑1的个数,因为不需要取反。传送门https://www.luogu.com.cn/problem/P4344 #include&…...
解决大型语言模型中的幻觉问题:前沿技术的综述
大型语言模型中的幻觉问题及其解决技术综述 摘要 大型语言模型(LLM)如GPT-4、PaLM和Llama在自然语言生成能力方面取得了显著进步。然而,它们倾向于产生看似连贯但实际上不正确或与输入上下文脱节的幻觉内容,这限制了它们的可靠性和安全部署。随着LLM在…...
机器学习流程—AutoML
文章目录 机器学习流程—AutoMLAutoML工具Auto-SKLearnMLBoxTPOTRapidMinerPyCaretAuto-KerasH2OAutoML谷歌AutoML云Uber LudwigTransmogrifAIAutoGluonAutoWekaDataRobot...
Ubuntu 23.10 tar包安装和配置Elasticsearch kibana 7.13.3
目录 一、环境说明 二、准备工作 三、安装elasticsearch 3.1 安装elasticsearch 3.2 添加服务和设置开机启动 四、安装kibana 4.1. 安装kibana 4.2 添加服务和设置开机启动 出于工作需要,需要在Ubuntu 23.10系统上通过tar包方式安…...
glibc内存管理ptmalloc
1、前言 今天想谈谈ptmalloc如何为应用程序分配释放内存的,基于以下几点原因才聊它: C/C 70%的问题是内存问题。了解一点分配器原理对解决应用程序内存问题肯定有帮助。C也在用ptmalloc. 当你在C中new一个对象时,底层还是依赖glibc中的ptma…...
HarmonyOS入门学习
HarmonyOS入门学习 前言快速入门ArkTS组件基础组件Image组件Text组件TextInput 文本输入框Buttonslider 滑动组件 页面布局循环控制ForEach循环创建组件 List自定义组件创建自定义组件Builder 自定义函数 状态管理Prop和LinkProvide和ConsumeObjectLink和Observed ArkUI页面路由…...
【Mock|JS】Mock的get传参+获取参数信息
mockjs的get传参 前端请求 const { data } await axios("/video/childcomments", {params: {sort: 1,start: 2,count: 5,childCount: 6,commenIndex: 0,},});后端获取参数 使用正则匹配url /*** # 根据url获取query参数* param {Url} urlStr get请求获取参数 eg:…...
spring cloud gateway k8s优雅启停
通过配置readiness探针和preStop hook,实现优雅启动和停止(滚动部署) 1. k8s工作负载配置 readinessProbe:httpGet:path: /datetimeport: 8080scheme: HTTPinitialDelaySeconds: 30timeoutSeconds: 1periodSeconds: 30successThreshold: 1fa…...
嵌入式软件面试-linux-中高级问题
Linux系统启动过程: BIOS自检并加载引导程序。引导程序(如GRUB)加载Linux内核到内存。内核初始化硬件,加载驱动,建立内存管理。加载init进程(PID为1),通常是systemd或SysVinit。init…...
css禁用元素指针事件,鼠标穿透,点击下层元素,用`pointer-events:none;`
pointer-events: 对鼠标事件的反应 MDN pointer-events 英文 https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events 菜鸟教程 CSS pointer-events 属性 https://www.runoob.com/cssref/css3-pr-pointer-events.html 常用取值 auto 和 none pointer-events: aut…...
Eureka的介绍和作用,以及搭建
一、Eureka的介绍和作用 Eureka是Netflix开源的一种服务发现和注册工具,它为分布式系统中的服务提供了可靠的服务发现和故障转移能力。Eureka是Netflix的微服务架构的关键组件之一,它能够实时地监测和管理服务实例的状态和可用性。 在Eureka架构中&…...
shell和linux的关系
Shell 和 Linux 之间存在密切的关系,但它们并不是同一个东西。让我们分别了解一下它们: Linux: Linux 是一个自由和开放源代码的类UNIX操作系统。 Linux 的内核由林纳斯托瓦兹(Linus Torvalds)于1991年首次发布&…...
数据在内存的存储
整数在内存中的存储 我们来回顾一下,整数在计算机是以补码的形式进行存储的,整数分为正整数和负整数,正整数的原码、反码和补码是一样的,负整数的原码、反码和补码略有不同(反码是原码除符号位,其他位按位取…...
JavaScript之ES中的类继承与Promise
类 ES5中的类及继承 //人function Person(name,age){this.name name;this.age age;}Person.prototype.eat function () {console.log(this.name "eat");}//程序员,继承,人function Programmer(name,age,language){//构造函数继承Person.…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...
