vulkanscenegraph顯示傾斜模型(6.4)-多線程下的記錄與提交

前言

? ? ? ? 上章深入分析了幀循環中呈現階段的具體實現。本章將分析多線程下的記錄與提交,進一步剖析vsg幀循環過程中的同步機制,并揭露信號量(VkSemaphore)和圍欄(VkFence)以及vsg::FrameBlock與vsg::Barrier在其中的作用。


目錄

  • 1 信號量(VkSemaphore)、柵欄(VkFence)、vsg::FrameBlock與vsg::Barrier
  • 2?多線程記錄與提交

1 信號量(VkSemaphore)、圍欄(VkFence)與vsg::FrameBlock、vsg::Barrier

? ? ? ? vsg::Semaphore封裝了VkSaphore,用于將vulkan命令的完成與其他vulkan命令提交的開始同步,為GPU內部的同步;vsg::Fence封裝了vkFence,用于同步Vulkan命令提交到隊列的完成情況,用于應用程序(CPU端)與Vulkan命令提交到隊列的完成情況(GPU端)的同步;vsg::FrameBlock提供了一種機制,用于同步等待新幀開始的線程;vsg::Barrier提供了一種同步多個線程的方法,一旦指定數量的線程加入Barrier,這些線程就會一起釋放。

1.1 vsg::Semaphore

Semaphore::Semaphore(Device* device, VkPipelineStageFlags pipelineStageFlags, void* pNextCreateInfo) :_pipelineStageFlags(pipelineStageFlags),_device(device)
{VkSemaphoreCreateInfo semaphoreInfo = {};semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;semaphoreInfo.pNext = pNextCreateInfo;VkResult result = vkCreateSemaphore(*device, &semaphoreInfo, _device->getAllocationCallbacks(), &_semaphore);if (result != VK_SUCCESS){throw Exception{"Error: Failed to create semaphore.", result};}
}

? ? ? ? vsg::Semaphore構造函數使用vkCreateSemaphore創建信號量VkSemaphore,信號量的創建與某一邏輯設備綁定,即信號量可用于GPU內部同一隊列或同一邏輯設備的不同隊列間的同步。

Semaphore::~Semaphore()
{if (_semaphore){vkDestroySemaphore(*_device, _semaphore, _device->getAllocationCallbacks());}
}

? ? ? ? vsg::Semaphore析構函數使用vkDestroySemaphore釋放信號量VkSemaphore。

1.2 vsg::Fence

Fence::Fence(Device* device, VkFenceCreateFlags flags) :_device(device)
{VkFenceCreateInfo createFenceInfo = {};createFenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;createFenceInfo.flags = flags;createFenceInfo.pNext = nullptr;if (VkResult result = vkCreateFence(*device, &createFenceInfo, _device->getAllocationCallbacks(), &_vkFence); result != VK_SUCCESS){throw Exception{"Error: Failed to create Fence.", result};}
}

? ? ? ? vsg::Fence構造函數使用vkCreateFence創建VkFence,圍欄的創建與某一邏輯設備綁定。

Fence::~Fence()
{if (_vkFence){vkDestroyFence(*_device, _vkFence, _device->getAllocationCallbacks());}
}

? ? ? ? vsg::Fence析構函數使用vkDestroyFence釋放圍欄。

VkResult Fence::wait(uint64_t timeout) const
{return vkWaitForFences(*_device, 1, &_vkFence, VK_TRUE, timeout);
}

? ? ? ? vsg::Fence在應用層(CPU端)的使用通過調用wait函數,其通過封裝vkWaitForFences實現。

VkResult Fence::reset() const
{return vkResetFences(*_device, 1, &_vkFence);
}

? ? ?vsg::Fence在GPU端使用時,需重置為無信號狀態,否則可能會導致應用層調用vkWaitForFences卡死。

1.3 vsg::FrameBlock

? ? ? ? vsg::FrameBlock提供了一種機制,用于同步等待新幀開始的線程。

        std::mutex _mutex;std::condition_variable _cv;ref_ptr<FrameStamp> _value;ref_ptr<ActivityStatus> _status;

? ? ? ? 上述代碼為vsg::FrameBlock的成員變量,其通過對std::mutex和std::condition_variable的封裝實現了一種針對vsg::FrameStamp是否變化的阻塞能力,即同步所有等待新幀開始的線程,而變量_status(vsg::ActivityStatus類型)用于標記vsg::FrameBlock的阻塞能力是否有效。

        bool wait_for_change(ref_ptr<FrameStamp>& value){std::unique_lock lock(_mutex);while (_value == value && _status->active()){_cv.wait(lock);}value = _value;return _status->active();}

? ? ? ? 通過調用wait_for_change接口,當傳入的vsg::FrameStamp對象與已有的一致時,阻塞應用程序所在線程。

        void set(ref_ptr<FrameStamp> frameStamp){std::scoped_lock lock(_mutex);_value = frameStamp;_cv.notify_all();}

? ? ? ? 當設置vsg::FrameStamp對象后,則通知所有阻塞線程解除阻塞。

1.4?vsg::Barrier

? ? ? ? vsg::Barrier提供了一種同步多個線程的方法,一旦指定數量的線程加入Barrier,這些線程就會一起釋放。

        const uint32_t _num_threads;uint32_t _num_arrived;uint32_t _phase;std::mutex _mutex;std::condition_variable _cv;

? ? ? ? vsg::Barrier同樣是封裝std::mutex和std::condition_variable實現,輔以_num_theads(同步的線程數)和_num_arrived(到達的線程數)實現多線程同步。

        void arrive_and_wait(){std::unique_lock lock(_mutex);if (++_num_arrived == _num_threads){_release();}else{auto my_phase = _phase;_cv.wait(lock, [this, my_phase]() { return this->_phase != my_phase; });}}

? ? ? ? 如上代碼為arrive_and_wait函數實現,當到達的線程數與總線程數一致時,則釋放所有線程,否則阻塞且記錄當前階段my_phase。

        void _release(){_num_arrived = 0;++_phase;_cv.notify_all();}

? ? ? ? 釋放所有線程的代碼如上所示,同時更新當前的階段(++_phase),將當前到達的線程數_num_arrived置為0。

? ? ? ? void arrive_and_drop(){std::unique_lock lock(_mutex);if (++_num_arrived == _num_threads){_release();}}

? ? ? ? arrive_and_drop會更新當前到達的線程數,同時判斷,當到達線程數等于所有線程數時,則釋放所有線程,但不阻塞當前線程。

2?多線程記錄與提交

#if 1if (_threading)
#else// The following is a workaround for an odd "Possible data race during write of size 1" warning that valgrind tool=helgrind reports// on the first call to vkBeginCommandBuffer despite them being done on independent command buffers.  This could well be a driver bug or a false positive.// If you want to quieten this warning then change the #if above to #if 0 as rendering the first three frames single threaded avoids the warning.if (_threading && _frameStamp->frameCount > 2)
#endif{_frameBlock->set(_frameStamp);_submissionCompleted->arrive_and_wait();}else{for (auto& recordAndSubmitTask : recordAndSubmitTasks){recordAndSubmitTask->submit(_frameStamp);}}

? ? ? ? 上述代碼為Viewer.cpp中的821-838行,當標記_threading為true時,執行多線程提交。首先更新當前幀(上述代碼第9行),接著等待提交的完成(上述代碼第10行)。其中_frameBlock和_submissionComplete分別為vsg::FrameBlock和vsg::Barrier對象,其初始化在vsg::Viewer::setupThreading函數中完成。vsg::Viewer::setupThreading的執行可分為多線程同步變量初始化、創建多線程兩部分。

    uint32_t numValidTasks = 0;for (const auto& task : recordAndSubmitTasks){if (!task->commandGraphs.empty()){++numValidTasks;}}// check if there is any point in setting up threadingif (numValidTasks == 0){return;}status->set(true);_threading = true;_frameBlock = FrameBlock::create(status);_submissionCompleted = Barrier::create(1 + numValidTasks);

? ? ? ? 上述代碼首先統計有效的提交任務數,接著創建vsg::FrameBlock和vsg::Barrier對象,其中vsg::Barrier對象_submissionCompleted傳入的線程數為有效任務數+1,其中'+1'代表主線程,即主線程調用其arrive_and_wait方法并阻塞,當所有提交線程完成時,則釋放主線程。

? ? ? ? 創建多線程部分以vsg::RecordAndSubmitTask為粒度創建,vsg::RecordAndSubmitTask與vsg::CommandGraph和vsg::TransferTask關系如下:

? ? ? ? 線程的創建分兩種情況,當vsg::RecordAndSubmitTask對象中包含的CommandGraph數組數量為1且vsg::TransferTask對象為空時,則僅創建一個任務提交線程,否則需同時創建數據傳輸線程。

        if (task->commandGraphs.size() == 1 && !task->transferTask){// task only contains a single CommandGraph so keep thread simpleauto run = [](ref_ptr<RecordAndSubmitTask> viewer_task, ref_ptr<FrameBlock> viewer_frameBlock, ref_ptr<Barrier> submissionCompleted, const std::string& threadName) {auto local_instrumentation = shareOrDuplicateForThreadSafety(viewer_task->instrumentation);if (local_instrumentation) local_instrumentation->setThreadName(threadName);auto frameStamp = viewer_frameBlock->initial_value;// wait for this frame to be signaledwhile (viewer_frameBlock->wait_for_change(frameStamp)){CPU_INSTRUMENTATION_L1_NC(local_instrumentation, "Viewer run", COLOR_RECORD);viewer_task->submit(frameStamp);submissionCompleted->arrive_and_drop();}};threads.emplace_back(run, task, _frameBlock, _submissionCompleted, make_string("Viewer run thread"));}

? ? ? ?上述代碼為,當vsg::RecordAndSubmitTask對象中包含的CommandGraph數組數量為1且vsg::TransferTask對象為空時,僅創建一個提交線程,線程中主要調用vsg::RecordAndSubmitTask的submit方法執行提交任務。其中線程為std::thread。

else if (!task->commandGraphs.empty())
{// we have multiple CommandGraphs in a single Task so set up a thread per CommandGraphstruct SharedData : public Inherit<Object, SharedData>{SharedData(ref_ptr<RecordAndSubmitTask> in_task, ref_ptr<FrameBlock> in_frameBlock, ref_ptr<Barrier> in_submissionCompleted, uint32_t numThreads) :task(in_task),frameBlock(in_frameBlock),submissionCompletedBarrier(in_submissionCompleted){recordedCommandBuffers = RecordedCommandBuffers::create();recordStartBarrier = Barrier::create(numThreads);recordCompletedBarrier = Barrier::create(numThreads);}// shared between all threadsref_ptr<RecordAndSubmitTask> task;ref_ptr<FrameBlock> frameBlock;ref_ptr<Barrier> submissionCompletedBarrier;// shared between threads associated with each taskref_ptr<RecordedCommandBuffers> recordedCommandBuffers;ref_ptr<Barrier> recordStartBarrier;ref_ptr<Barrier> recordCompletedBarrier;};uint32_t numThreads = static_cast<uint32_t>(task->commandGraphs.size());if (task->transferTask) ++numThreads;ref_ptr<SharedData> sharedData = SharedData::create(task, _frameBlock, _submissionCompleted, numThreads);auto run_primary = [](ref_ptr<SharedData> data, ref_ptr<CommandGraph> commandGraph, const std::string& threadName) {auto local_instrumentation = shareOrDuplicateForThreadSafety(data->task->instrumentation);if (local_instrumentation) local_instrumentation->setThreadName(threadName);auto frameStamp = data->frameBlock->initial_value;// wait for this frame to be signaledwhile (data->frameBlock->wait_for_change(frameStamp)){CPU_INSTRUMENTATION_L1_NC(local_instrumentation, "Viewer primary", COLOR_RECORD);// primary thread starts the taskdata->task->start();data->recordStartBarrier->arrive_and_wait();//vsg::info("run_primary");commandGraph->record(data->recordedCommandBuffers, frameStamp, data->task->databasePager);data->recordCompletedBarrier->arrive_and_wait();// primary thread finishes the task, submitting all the command buffers recorded by the primary and all secondary threads to its queuedata->task->finish(data->recordedCommandBuffers);data->recordedCommandBuffers->clear();data->submissionCompletedBarrier->arrive_and_wait();}};auto run_secondary = [](ref_ptr<SharedData> data, ref_ptr<CommandGraph> commandGraph, const std::string& threadName) {auto local_instrumentation = shareOrDuplicateForThreadSafety(data->task->instrumentation);if (local_instrumentation) local_instrumentation->setThreadName(threadName);auto frameStamp = data->frameBlock->initial_value;// wait for this frame to be signaledwhile (data->frameBlock->wait_for_change(frameStamp)){CPU_INSTRUMENTATION_L1_NC(local_instrumentation, "Viewer secondary", COLOR_RECORD);data->recordStartBarrier->arrive_and_wait();commandGraph->record(data->recordedCommandBuffers, frameStamp, data->task->databasePager);data->recordCompletedBarrier->arrive_and_wait();}};auto run_transfer = [](ref_ptr<SharedData> data, ref_ptr<TransferTask> transferTask, TransferTask::TransferMask transferMask, const std::string& threadName) {auto local_instrumentation = shareOrDuplicateForThreadSafety(data->task->instrumentation);if (local_instrumentation) local_instrumentation->setThreadName(threadName);auto frameStamp = data->frameBlock->initial_value;// wait for this frame to be signaledwhile (data->frameBlock->wait_for_change(frameStamp)){CPU_INSTRUMENTATION_L1_NC(local_instrumentation, "Viewer transfer", COLOR_RECORD);data->recordStartBarrier->arrive_and_wait();//vsg::info("run_transfer");if (auto transfer = transferTask->transferData(transferMask); transfer.result == VK_SUCCESS){if (transfer.dataTransferredSemaphore){data->task->earlyDataTransferredSemaphore = transfer.dataTransferredSemaphore;}}data->recordCompletedBarrier->arrive_and_wait();}};for (uint32_t i = 0; i < task->commandGraphs.size(); ++i){if (i == 0)threads.emplace_back(run_primary, sharedData, task->commandGraphs[i], make_string("Viewer primary thread"));elsethreads.emplace_back(run_secondary, sharedData, task->commandGraphs[i], make_string("Viewer seconary thread ", i));}if (task->transferTask){threads.emplace_back(run_transfer, sharedData, task->transferTask, TransferTask::TRANSFER_BEFORE_RECORD_TRAVERSAL, make_string("Viewer early transferTask thread"));}
}

? ? ? ?其它情況創建的線程,需針對vsg::TransferTask對象創建傳輸線程,當存在多個CommandGraph時,創建的提交線程的方式需區分。線程使用std::thread,通過lambda表達式封裝線程的執行函數。

                SharedData(ref_ptr<RecordAndSubmitTask> in_task, ref_ptr<FrameBlock> in_frameBlock, ref_ptr<Barrier> in_submissionCompleted, uint32_t numThreads) :task(in_task),frameBlock(in_frameBlock),submissionCompletedBarrier(in_submissionCompleted){recordedCommandBuffers = RecordedCommandBuffers::create();recordStartBarrier = Barrier::create(numThreads);recordCompletedBarrier = Barrier::create(numThreads);}

? ? ? ?上述代碼為SharedData的構造函數,提交線程和數據傳輸線程的同步通過上述recordStartBarrier和recordCompletedBarrier兩個vsg::Barrier對象實現。

            auto run_primary = [](ref_ptr<SharedData> data, ref_ptr<CommandGraph> commandGraph, const std::string& threadName) {auto local_instrumentation = shareOrDuplicateForThreadSafety(data->task->instrumentation);if (local_instrumentation) local_instrumentation->setThreadName(threadName);auto frameStamp = data->frameBlock->initial_value;// wait for this frame to be signaledwhile (data->frameBlock->wait_for_change(frameStamp)){CPU_INSTRUMENTATION_L1_NC(local_instrumentation, "Viewer primary", COLOR_RECORD);// primary thread starts the taskdata->task->start();data->recordStartBarrier->arrive_and_wait();//vsg::info("run_primary");commandGraph->record(data->recordedCommandBuffers, frameStamp, data->task->databasePager);data->recordCompletedBarrier->arrive_and_wait();// primary thread finishes the task, submitting all the command buffers recorded by the primary and all secondary threads to its queuedata->task->finish(data->recordedCommandBuffers);data->recordedCommandBuffers->clear();data->submissionCompletedBarrier->arrive_and_wait();}};auto run_secondary = [](ref_ptr<SharedData> data, ref_ptr<CommandGraph> commandGraph, const std::string& threadName) {auto local_instrumentation = shareOrDuplicateForThreadSafety(data->task->instrumentation);if (local_instrumentation) local_instrumentation->setThreadName(threadName);auto frameStamp = data->frameBlock->initial_value;// wait for this frame to be signaledwhile (data->frameBlock->wait_for_change(frameStamp)){CPU_INSTRUMENTATION_L1_NC(local_instrumentation, "Viewer secondary", COLOR_RECORD);data->recordStartBarrier->arrive_and_wait();commandGraph->record(data->recordedCommandBuffers, frameStamp, data->task->databasePager);data->recordCompletedBarrier->arrive_and_wait();}};auto run_transfer = [](ref_ptr<SharedData> data, ref_ptr<TransferTask> transferTask, TransferTask::TransferMask transferMask, const std::string& threadName) {auto local_instrumentation = shareOrDuplicateForThreadSafety(data->task->instrumentation);if (local_instrumentation) local_instrumentation->setThreadName(threadName);auto frameStamp = data->frameBlock->initial_value;// wait for this frame to be signaledwhile (data->frameBlock->wait_for_change(frameStamp)){CPU_INSTRUMENTATION_L1_NC(local_instrumentation, "Viewer transfer", COLOR_RECORD);data->recordStartBarrier->arrive_and_wait();//vsg::info("run_transfer");if (auto transfer = transferTask->transferData(transferMask); transfer.result == VK_SUCCESS){if (transfer.dataTransferredSemaphore){data->task->earlyDataTransferredSemaphore = transfer.dataTransferredSemaphore;}}data->recordCompletedBarrier->arrive_and_wait();}};

? ? ? ?如上代碼為三個lambda函數,run_primary、run_secondary、run_transfer,分別對應第一個提交線程、其它提交線程、數據傳輸線程的執行函數。其將主體執行內容放置在recordStartBarrier->arrive_and_wait() 和?recordCompletedBarrier->arrive_and_wait()之間,實現線程間的同步,采用如下的模式實現線程和主線程的同步:

                while (data->frameBlock->wait_for_change(frameStamp)){//執行內容data->submissionCompletedBarrier->arrive_and_wait();}

? ? ? ?vulkanscenegraph顯示傾斜模型(6.2)-記錄與提交-CSDN博客中將任務提交的具體實現分為開始、recordTraversal前的數據傳輸、record、完成四個部分,而run_primary獨自負責任務的開始、recordTraversal前的數據傳輸、完成三個部分,run_primary與run_secondary共同負責record部分。通過run_primary函數的實現可看出,當前幀所有數據傳輸完成、命令錄制完成,最后調用finish方法提交任務到隊列。

文末:本章深入分析了幀循環中多線程下的記錄與提交,首先深入剖析了vsg中與多線程同步相關的封裝:vsg::Semaphore、vsg::Fence、vsg::FrameBlock、vsg::Barrier,接著進一步分析了記錄與提交過程中的多線程機制。下章將分析vsg::DatabasePager在更新場景圖過程中的作用。

待分析項:vsg::DatabasePager在更新場景圖過程中的作用。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/81736.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/81736.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/81736.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python爬蟲實戰:獲取扇貝單詞數據并分析,為用戶高效學習單詞做參考

一、引言 隨著互聯網的迅猛發展,在線學習資源日益豐富多樣。扇貝單詞作為一款備受歡迎的在線英語學習平臺,積累了海量的單詞學習數據。借助 Python 強大的爬蟲技術獲取這些數據,并運用數據分析和機器學習方法進行深度挖掘,能夠為用戶量身定制更個性化、更高效的單詞學習方…

【Vagrant+VirtualBox創建自動化虛擬環境】Ansible-Playbook

Vagrant 后續Ansible實戰&#xff1a;【Ansible自動化運維實戰&#xff1a;從Playbook到負載均衡指南】-CSDN博客 Vagrant是一個基于Ruby的工具&#xff0c;用于創建和部署虛擬化開發環境。它使用Oracle的開源VirtualBox虛擬化系統&#xff0c;使用 Chef創建自動化虛擬環境 Do…

Codigger Desktop:重新定義數字工作與生活方式

Codigger Desktop是一款革命性的智能桌面操作系統&#xff0c;專為現代數字生活和工作場景打造。它不僅成為開發者的強大生產力工具&#xff0c;更是普通用戶日常數字生活的得力助手&#xff0c;完美實現了專業性與易用性的平衡。 Multimedia Desktop全能數字生活平臺 重新定…

Servlet+tomcat

serverlet 定義&#xff1a;是一個接口&#xff0c;定義了java類被瀏覽器&#xff08;tomcat識別&#xff09;的規則 所以我們需要自定義一個類&#xff0c;實現severlet接口復寫方法 通過配置類實現路徑和servlet的對應關系 執行原理 當用戶在瀏覽器輸入路徑&#xff0c;會…

什么是 DDoS 攻擊?高防 IP 如何有效防護?2025全面解析與方案推薦

一、DDoS 攻擊&#xff1a;互聯網時代的 “數字核武器” 1. DDoS 攻擊的本質與原理 ** 分布式拒絕服務攻擊&#xff08;DDoS&#xff09;** 通過操控海量僵尸設備&#xff0c;向目標服務器發送洪水般請求&#xff0c;耗盡帶寬、連接或計算資源&#xff0c;導致合法用戶無法訪…

Circular Plot系列(一): 環形熱圖繪制

針對近期多個粉絲咨詢環形圖的繪制&#xff0c;我意識到&#xff0c;我們似乎沒有真正介紹過circle圖&#xff0c;但這一類圖確是非常常用的圖&#xff0c;所以這里詳細學習一下circle的繪制&#xff0c;使用的是circlize包&#xff0c;功能很完善&#xff1a;安裝包, #https:/…

【數據挖掘】時間序列預測-時間序列預測策略

時間序列預測策略 &#xff08;1&#xff09;單步預測與多步預測&#xff08;2&#xff09;直接多步預測&#xff08;3&#xff09;遞歸多步預測&#xff08;4&#xff09;直接遞歸的混合預測&#xff08;5&#xff09;多輸入多輸出預測 &#xff08;1&#xff09;單步預測與多…

【LLM】deepseek R1之GRPO訓練筆記(持續更新)

note 相關框架對比&#xff1a; 需微調模型且資源有限 → Unsloth&#xff1b;本地隱私優先的小規模推理 → Ollama&#xff1b;復雜邏輯或多模態任務 → SGLang&#xff1b;高并發生產環境 → vLLM 微調SFT和GRPO是確實能學到新知識的四種格式&#xff08;messages、sharegpt…

【數據結構】--- 單鏈表的增刪查改

前言&#xff1a; 經過了幾個月的漫長歲月&#xff0c;回頭時年邁的小編發現&#xff0c;數據結構的內容還沒有寫博客&#xff0c;于是小編趕緊停下手頭的活動&#xff0c;補上博客以洗清身上的罪孽 目錄 前言 概念&#xff1a; 單鏈表的結構 我們設定一個哨兵位頭節點給鏈…

【JAVA】數據類型與變量:深入理解棧內存分配(4)

核心知識點詳細解釋 Java 的基本數據類型和引用數據類型 基本數據類型 Java 有 8 種基本數據類型&#xff0c;它們可以分為 4 類&#xff1a; 整數類型&#xff1a;byte&#xff08;1 字節&#xff09;、short&#xff08;2 字節&#xff09;、int&#xff08;4 字節&#…

ReentrantLock實現公平鎖和非公平鎖

在 Java 里&#xff0c;公平鎖和非公平鎖是多線程編程中用于同步的兩種鎖機制&#xff0c;它們的主要差異在于獲取鎖的順序規則。下面是對二者的詳細介紹&#xff1a; 公平鎖 公平鎖遵循 “先來先服務” 原則&#xff0c;也就是線程獲取鎖的順序和請求鎖的順序一致。先請求鎖…

一篇擼清 Http,SSE 與 WebSocket

HTTP,SSE 和WebSocket都是網絡傳輸的協議,本篇快速介紹三者的概念和比較。 SSE(Server-Sent Events) 是什么? SSE(Server-Sent Events),服務器發送事件, 是一種基于 HTTP 的輕量級協議,允許服務器主動向客戶端(如瀏覽器)推送實時數據。它設計用于單向通信(服務器到…

5個重要的財務指標講解

1&#xff09;凈資產收益率 2&#xff09;銷售凈利率 3&#xff09; 銷售毛利率 4&#xff09;銷售成本率 5&#xff09; 期間費用率 好的&#xff0c;我將通過一個假設的案例&#xff08;某公司2023年數據&#xff09;逐步解釋這些財務指標&#xff0c;并用具體數字演示計算…

PISI:眼圖1:眼圖相關基本概念

0 英文縮寫 TIE&#xff08;Time Interval Error&#xff09;時間間隔誤差&#xff0c;UI&#xff08;Unit Interval&#xff09;單位間隔PDF&#xff08;Probability Density Function&#xff09;概率密度函數BER&#xff08;Bit Error Rate&#xff09;誤碼率TJ&#xff08…

前端八股 CSS 2 選擇器

選擇器功能&#xff1a;選中特定 DOM節點進行渲染 原始方法 getElementById() getElementByName() 現在方法選擇器 分類&#xff1a; id選擇器 類選擇器 標簽選擇器 邏輯與選擇器 其他類型選擇器&#xff1a; 偽類選擇器&#xff1a; :link&#xff1a;未被訪問的鏈接…

算法競賽進階指南.闇の連鎖

目錄 題目算法標簽: 樹上差分, L C A LCA LCA, 倍增思路代碼 題目 352. 闇の連鎖 算法標簽: 樹上差分, L C A LCA LCA, 倍增 思路 對于一個無向圖, 第一次切斷樹邊, 第二次切非樹邊, 一共多少種方案使得圖不連通, 點數和邊數都很大, 時間復雜度不能是 O ( n 2 ) O(n ^ 2…

ActiveMQ 與其他 MQ 的對比分析:Kafka/RocketMQ 的選型參考(二)

ActiveMQ、Kafka 和 RocketMQ 詳細對比 性能對比 在性能方面&#xff0c;Kafka 和 RocketMQ 通常在高吞吐量場景下表現出色&#xff0c;而 ActiveMQ 則相對較弱。根據相關測試數據表明&#xff0c;Kafka 在處理大規模日志數據時&#xff0c;單機吞吐量可以達到每秒數十萬條甚…

Electron 從零開始:構建你的第一個桌面應用

&#x1f5a5;? Electron 從零開始&#xff1a;構建你的第一個桌面應用 Electron 是一個可以使用 HTML、CSS 和 JavaScript 構建跨平臺桌面應用的框架。它將 Chromium 和 Node.js 融合到一個環境中&#xff0c;使 Web 開發者也能輕松開發原生桌面應用。 &#x1f680; 什么是 …

相向雙指針-16. 最接近的三數之和

16. 最接近的三數之和 題目描述思路講解代碼展示復雜度分析相關標簽 題目描述 思路講解 思路和 15. 三數之和 類似&#xff0c;排序后&#xff0c;枚舉 nums[i] 作為第一個數&#xff0c;那么問題變成找到另外兩個數&#xff0c;使得這三個數的和與 target 最接近&#xff0c;…

C 語 言 - - - 文 件 操 作

C 語 言 - - - 文 件 操 作 文 件文 件 名文 件 操 作fopenfclose 文 件 的 順 序 讀 寫fputcfgetcfputsfgetsfprintffscanffwritefread 流文 件 的 隨 機 讀 寫fseekftellrewind 總結 &#x1f4bb;作 者 簡 介&#xff1a;曾 與 你 一 樣 迷 茫&#xff0c;現 以 經 驗 助 你…