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.…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
