muduo中事件循環線程池的理解

事件循環線程池的理解

  • 前置知識
  • reactor模型
    • thread::start()方法的理解
  • 創建線程池
  • 子線程被喚醒的幾種情況
    • 子線程被主線程喚醒
      • 新連接到來
      • 有消息需要發送時(多reactor情況時)
    • 關閉連接時
    • 子線程被喚醒執行任務

在 上一篇中,我們討論了關于簡單的線程池的實現,此次我們就基于簡單的線程池實現,深入剖析muduo網絡庫的事件循環線程池的代碼:

前置知識

reactor模型

在這里插入圖片描述

thread::start()方法的理解

#include "Thread.h"
#include "CurrentThread.h"#include <semaphore.h>std::atomic_int Thread::numCreated_(0);Thread::Thread(ThreadFunc func, const std::string &name): started_(false), joined_(false), tid_(0), func_(std::move(func)), name_(name)
{setDefaultName();
}Thread::~Thread()
{if (started_ && !joined_){   // thread類提供了設置分離線程的方法 線程運行后后臺自動銷毀(非阻塞)thread_->detach();                                                  }
}void Thread::start()                                                        // 一個Thread對象 記錄的就是一個新線程的詳細信息
{started_ = true;//定義一個信號量sem_t sem;//設置信號量sem,是用于線程間,初值為0sem_init(&sem, false, 0);                                               // false指的是 不設置進程間共享// 開啟線程thread_ = std::shared_ptr<std::thread>(new std::thread([&]() {tid_ = CurrentThread::tid();                                        // 獲取線程的tid值sem_post(&sem);//sem值+1func_();                                                            // 開啟一個新線程 專門執行該線程函數}));// 這里必須等待獲取上面新創建的線程的tid值sem_wait(&sem);
}void Thread::join()
{joined_ = true;thread_->join();//阻塞等待當前線程執行完畢
}void Thread::setDefaultName()
{int num = ++numCreated_;if (name_.empty()){char buf[32] = {0};snprintf(buf, sizeof buf, "Thread%d", num);name_ = buf;}
}

這個類是對std::thread進行了一個封裝,主要關注Thread::start方法,核心實現是定義了一個信號量sem,以確保子線程能夠成功被創建,具體的創建流程如下圖。
在這里插入圖片描述

創建線程池

在這里插入圖片描述

  1. TcpServer啟動線程池
  2. 在線程池中根據傳入的線程數量創建事件循環線程EventLoopThread,并且將創建的事件循環線程加到線程池threads中去,并且調用EventLoopThread->loop函數,隨后綁定返回的已經創建好事件循環EventLoop
  3. 創建事件循環EventLoopThread的時候綁定函數threadFunc
  4. 調用thread_.start()函數,thread_創建了一個子線程,該子線程執行綁定的threadFunc函數。在threadFunc函數中,創建事件循環EventLoop,創建成功了通知主線程,并在開始事件循環EventLoop.loop();與此同時,主線程在循環等待子線程成功創建EventLoop,并接收創建好的EventLoop對象,并返回。

上述過程可以總結為:主線程隨著函數調用開辟子線程,子線程成功創建事件循環,并在在子線程中事件事件循環,實現了one loop per thread,主線程返回到創建事件循環線程池中,繼續執行其他任務。如下圖所示。
在這里插入圖片描述
關于主線程的生命周期的理解

EventLoop *EventLoopThread::startLoop()
{thread_.start(); // 啟用底層線程Thread類對象thread_中通過start()創建的線程EventLoop *loop = nullptr;{std::unique_lock<std::mutex> lock(mutex_);cond_.wait(lock, [this](){return loop_ != nullptr;});loop = loop_;}return loop;
}

這里是存在開辟了一個新的線程,可以稱為子線程,子線程中是執行eventloop.loop()事件循環的,在程序的運行過程中不會被回收;但是這個主線程的,隨著return loop;的返回,主線程是不是會被釋放呢?其實不是的,這里的主線程可以看成是負責整個項目的實現的核心線程,當主線程執行startLoop()函數后創建了子線程并返回,主線程就繼續執行其他的函數,并不是說返回了就被銷毀了。
比如說主線程執行完startLoop()的返回順序是:EventLoopThread::startLoop()—>>> EventLoopThreadPool::start—>>>然后執行TcpServer::start()中的loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get())),跟調用順序是相反的。見上圖

感悟:關于這一點理解起來還是花了挺久的,這個項目前前后后也是花了挺多時間看了,但是總是存在這里那里的小問題,不影響理解整體項目,但是這些小點是真的才能感受到項目的高明;也是花時間整理這些小知識點,才可以有更深的感悟。

子線程被喚醒的幾種情況

首先,我們需要強調一下,這里存在mainloop(一個)和subloop(多個),mainloop所在的線程為主線程,subloop所在的線程為子線程(可以理解為,subloop1對應為子線程1,subloop2對應為子線程2等)

子線程被主線程喚醒

新連接到來

當一切都初始化好后,MainLoop的Acceptor開始監聽,當有新的連接到來時,觸發新連接到來的回調函數,在回調函數中處理新連接的相關操作。
在這里插入圖片描述

  1. 調用getNextLoop()函數
  2. 執行輪詢算法返回一個事件循環EventLoop
  3. 對得到的事件循環loop綁定TCP連接,并且設置回調函數(通過TcpConnection設置,但最終是設置到EventLoop中的Channel回調函數中),見下圖(圖片來源萬字長文梳理Muduo庫核心代碼及優秀編程細節思想剖析,更多可以參考這篇文章)
    在這里插入圖片描述
  4. 調用runInLoop,在對應的EventLoop中執行相應的回調函數。但是這里存在一個問題,即我是通過MainLoop上所在的線程選擇了一個子Loop,假如說為subLoop1,并且需要執行在subLoop1上綁定的函數,而當前的線程是MainLoop對應的線程。如果之間運行,這就不滿足每一個線程運行一個EventLoop的條件了(one loop per thread)。所以在這里,就需要判斷一下,是否是在當前loop中對應的線程執行的任務。如果不是,需要通過wakeup函數喚醒當前loop對應的線程。
  5. 然后loop對應的線程就被喚醒起來工作(相當于消費者來消費任務了),執行綁定的connectEstablished()函數,在connectEstablished()函數實現tie()函數綁定,并且設置監聽讀事件。

建立Tcp連接后,以后發生在這個連接上的所有事件都交由這個SubLoop1來負責了。

有消息需要發送時(多reactor情況時)

在這里插入代碼片

關閉連接時

當連接斷開或者關閉連接,執行TcpServer::removeConnection()回調函數。
在這里插入圖片描述

  1. 上層調用TcpServer::removeConnection函數,調用runInLoop函數,并綁定執行TcpServer::removeConnectionInLoop
  2. 因為removeConnectionInLoop是執行在mainloop對應的線程(執行TcpServer類下的函數都在mainloop中對應的線程,也可以說在主線程中運行),所以這里之間是在當前的mainloop中執行回調,執行removeConnectionInLoop函數
  3. 首先移除TCP連接,并且得到將要移除的連接對應的ioloop;因為當前是處于mainloop對應的主線程,所以ioloop執行調用queueInLoop
  4. queueInLoop函數中,核心是異步喚醒自己對應的線程,并執行等待執行的任務(在這里是connectDestroyed)
  5. connectDestroyed中,將待移除的TCP連接中的channel中的所有感興趣的事件從poller中移除掉,并將channel從poller中移除。

子線程被喚醒執行任務

我們知道,在線程池初始化過程中,每一個線程就對應一個事件循環(eventloop)已開始循環,并且當新連接到來時,mainloop按照輪詢方法將新連接分發給一個事件循環,以后這個事件循環就負責這個新連接的所有操作了(包括接收消息、發送消息等)。

void EventLoop::loop()
{looping_ = true;quit_ = false;LOG_INFO("EventLoop %p start looping\n", this);while (!quit_){activeChannels_.clear();pollRetureTime_ = poller_->poll(kPollTimeMs, &activeChannels_);for (Channel *channel : activeChannels_){// Poller監聽哪些channel發生了事件 然后上報給EventLoop 通知channel處理相應的事件channel->handleEvent(pollRetureTime_);}doPendingFunctors();//執行等待處理的函數,一般是主線程添加的。}LOG_INFO("EventLoop %p stop looping.\n", this);looping_ = false;
}

可以看到,初始化的事件循環eventloop在自己的線程內循環等待任務的來臨。

pollRetureTime_ = poller_->poll(kPollTimeMs, &activeChannels_);

這句話是監聽對于每一個連接注冊的事件是否發生了(如果發生了,注冊到activeChannels_并依序執行);否則超時返回。

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

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

相關文章

AI智能體“上下文工程”實踐:來自 Manus 項目的經驗總結

轉載&#xff1a;https://manus.im/blog/Context-Engineering-for-AI-Agents-Lessons-from-Building-Manus 在啟動 Manus (manus.im/app) 項目之初&#xff0c;我的團隊面臨一個關鍵抉擇&#xff1a;究竟是基于開源基礎模型訓練一個端到端的智能體模型&#xff0c;還是在前沿大…

day19 鏈表

定義鏈式存儲的線性表頭文件相關定義 typedef int datatype;//定義數據域類型 typedef struct Node {union{int len; //頭結點數據域datatype data; //普通節點數據域};struct Node *next; //節點指針域 }Node,*Node_ptr;鏈表的函數 注意事項 1.創建節點時&#xff0c;需要初…

【第三節】Class與Style綁定

文章目錄Class與Style綁定綁定HTML Class對象語法數組語法綁定內聯樣式對象語法數組語法自動添加前綴Class與Style綁定 數據綁定一個常見需求是操作元素的 class 列表和它的內聯樣式,因為它們都是屬性&#xff0c;我們可以用 v-bind 處理它們:我們只需要計算出表達式最終的字符…

CMOS知識點 離子注入工藝

知識點8&#xff1a;離子注入是為了將摻雜劑&#xff08;如硼、磷等&#xff09;精確引入硅晶片的近表面區域&#xff0c;以改變其電學性質。工藝過程&#xff1a;電離與加速&#xff1a;摻雜劑原子在離子源中被電離&#xff08;帶電&#xff09;&#xff0c;通過高壓電場&…

從安裝到上手:Ubuntu 22.04 玩轉 Containerd 2.1.3 容器運行時

Containerd 是一款支持 OCI 規范的容器運行時&#xff0c;注重容器部署和生命周期管理的簡單性、健壯性與可移植性&#xff0c;常被嵌入到 Docker 和 Kubernetes 等系統中。本文將詳細介紹在 Ubuntu 22.04 服務器上通過二進制包手動安裝 Containerd 的完整步驟&#xff0c;包括…

Hadoop與云原生集成:彈性擴縮容與OSS存儲分離架構深度解析

Hadoop與云原生集成的必要性Hadoop在大數據領域的基石地位作為大數據處理領域的奠基性技術&#xff0c;Hadoop自2006年誕生以來已形成包含HDFS、YARN、MapReduce三大核心組件的完整生態體系。根據CSDN技術社區的分析報告&#xff0c;全球超過75%的《財富》500強企業仍在使用Had…

飛算科技:以創新科技引領數字化變革,旗下飛算 JavaAI 成開發利器

作為國家級高新技術企業&#xff0c;飛算科技專注于自主創新&#xff0c;在數字科技領域持續深耕&#xff0c;用前沿技術為各行業客戶賦能&#xff0c;助力其實現數字化轉型升級的飛躍。?飛算科技憑借深厚的技術積累&#xff0c;將互聯網科技、大數據、人工智能等技術與實際應…

多線程Python爬蟲:加速大規模學術文獻采集

1. 引言 在學術研究過程中&#xff0c;高效獲取大量文獻數據是許多科研工作者和數據分析師的需求。然而&#xff0c;傳統的單線程爬蟲在面對大規模數據采集時&#xff0c;往往效率低下&#xff0c;難以滿足快速獲取數據的要求。因此&#xff0c;利用多線程技術優化Python爬蟲&a…

NX717NX720美光固態閃存NX724NX728

美光NX系列固態閃存深度解析&#xff1a;技術、性能與市場洞察一、技術架構與核心創新美光NX系列固態閃存&#xff08;包括NX717、NX720、NX724、NX728&#xff09;的技術根基源于其先進的G9 NAND架構。該架構通過5納米制程工藝和多層3D堆疊技術&#xff0c;實現了存儲單元密度…

淺談——C++和C#差異

雖然這個話題看著似乎有些關公戰秦瓊的味道&#xff0c;但是作為游戲開發者&#xff0c;C和C#一定是繞不開的兩門語言。不過雖然說是比較二者差異&#xff0c;因為我學習的過程主要是先學C&#xff0c;所以我先基于C的認知&#xff0c;再來聊聊C#之中的不同。&#xff08;為什么…

rocky9-zabbix簡單部署

目錄 一、準備 1、&#xff08;rocky9&#xff09; 2、配置數據庫 二、配置文件 1、導入初始架構與數據 2、配置相關文件 三、啟動服務 1、瀏覽器訪問 2、解決亂碼問題 ?編輯 四、監控 ① 添加主機 1、修改配置文件 2、啟動服務 3、網頁添加 ②添加監控模塊 1…

tabBar設置底部菜單選項、iconfont圖標(圖片)庫、模擬京東app的底部導航欄

歡迎來到我的UniApp技術專欄&#xff01;&#x1f389; 在這里&#xff0c;我將與大家分享關于UniApp開發的實用技巧、最佳實踐和項目經驗。 專欄特色&#xff1a; &#x1f4f1; 跨平臺開發一站式解決方案 &#x1f680; 從入門到精通的完整學習路徑 &#x1f4a1; 實戰項目經…

7.22總結mstp,vrrp

一、MSTP技術&#xfeff;&#xfeff;MSTI和MSTI域根&#xfeff;&#xfeff;MSTP中的端口角色3. MSTP工作原理 MSTP 計算方法? CST/IST的計算和RSTP類似 ? MSTI的計算僅限于區域內 ? MSTI計算參數包含在IST BPDU中&#xff0c;和IST的計 算同步完成&#xfeff;&#xfe…

【電腦】網卡的基礎知識

網卡&#xff08;Network Interface Card, NIC&#xff09;是計算機中用于連接網絡的關鍵組件之一&#xff0c;它負責管理和發送數據包到互聯網或其他局域網設備。下面是一些關于網卡的詳細知識&#xff1a;網卡的基本結構MAC地址&#xff1a;每個網卡都有一個唯一的物理地址&a…

IPv4枯竭時代:從NAT技術到IPv6的演進之路

&#x1f50d; 開發者資源導航 &#x1f50d;&#x1f3f7;? 博客主頁&#xff1a; 個人主頁&#x1f4da; 專欄訂閱&#xff1a; JavaEE全棧專欄 IPv4&#xff08;Internet Protocol version 4&#xff09;是互聯網最核心的通信協議之一&#xff0c;自 1981 年正式標準化以來…

模式結構-微服務架構設計模式

需求&#xff08;Forces)結果上下文(Resulting context)相關模式(Related patterns)需求&#xff1a;必須解決的問題需求部分描述了必須解決的問題和圍繞這個問題的特定上下文環境。需求有時候是相互沖突的&#xff0c;所以不能指望把他們全部都解決&#xff08;必須取舍&#…

30個常用的Linux命令匯總和實戰場景示例

下面匯總常用的 30 個常用的 Linux 命令&#xff0c;每個都附有簡要說明和典型示例&#xff0c;適合日常開發、服務器維護或系統學習使用。30 個常用的 Linux 命令匯總 一、文件與目錄操作&#xff08;基礎&#xff09;命令說明示例ls列出文件和目錄ls -l 顯示詳細信息cd切換目…

Taro 網絡 API 詳解與實用案例

Taro 網絡 API 詳解與實用案例 在現代前端開發中&#xff0c;網絡通信是不可或缺的一環。Taro 作為一款多端開發框架&#xff0c;提供了豐富且統一的網絡 API&#xff0c;幫助開發者在小程序、H5、React Native 等多端環境下高效地進行數據交互。本文將詳細介紹 Taro 的四大網…

Bitbucket平臺的HTTP Access Tokens操作手冊

在Bitbucket平臺添加HTTP Access Tokens&#xff08;用于替代密碼進行認證&#xff09;。 1. 登錄Bitbucket并訪問個人設置 打開 Bitbucket 并登錄賬號。點擊右上角頭像 → 選擇 Manage account。 2. 生成Access Token 在左側菜單中選擇 Access tokens&#xff08;位于 Sec…

低成本、高泛化能力的無人機自主飛行!VLM-Nav:基于單目視覺與視覺語言模型的無地圖無人機導航

作者&#xff1a;Gobinda Chandra Sarker1^{1}1, AKM Azad2^{2}2, Sejuti Rahman1^{1}1, Md Mehedi Hasan1^{1}1單位&#xff1a;1^{1}1達卡大學&#xff0c;2^{2}2伊瑪目穆罕默德伊本沙特伊斯蘭大學論文標題&#xff1a;VLM-Nav: Mapless UAV-Navigation Using Monocular Visi…