chromium代碼學(xué)習(xí)

chromium代碼學(xué)習(xí)

chromium開源工程中有太多值得學(xué)習(xí)的地方,值得我們通過一個(gè)單獨(dú)的專題來記錄對這個(gè)“偉大”工程的學(xué)習(xí)

base庫Event事件實(shí)現(xiàn)方式

對于可等待的通知事件來說,“等待”部分實(shí)現(xiàn)的核心就是用鎖來進(jìn)行實(shí)現(xiàn)。在POSIX標(biāo)準(zhǔn)下,通過pthread_mutex_t來實(shí)現(xiàn)鎖的功能?!巴ㄖ辈糠謱?shí)現(xiàn)的核心則是通過條件變量來實(shí)現(xiàn)。在POSIX標(biāo)準(zhǔn)下,通過pthread_cond_t來實(shí)現(xiàn)通知的功能,pthread_cond_t需要一個(gè)pthread_mutex_t的配合來完成通知的功能。

主要的流程是

pthread_cond_init(&cond) //初始化pthread_cond_t變量
pthread_mutex_lock(&mutex) //鎖住pthread_mutex_t變量
pthread_cond_wait(&cond, &mutex) //首先解鎖mutex,然后等待cond被signal激活(線程X)
pthread_cond_signal(&cond) //將會(huì)signal激活等待在這個(gè)條件變量上的線程(并不是調(diào)度這個(gè)線程執(zhí)行),并鎖住mutex
pthread_mutex_unlock(&mutex) //解鎖pthread_mutex_t變量

最后線程X,被OS調(diào)度后會(huì)執(zhí)行wait后續(xù)的代碼。POSIX為什么會(huì)前后兩次lock、unlock呢?因?yàn)榭傻却耐ㄖ录强梢员挥迷诙鄠€(gè)線程中,這些線程可以同時(shí)等待某個(gè)事件的完成。那么wait函數(shù)就需要是線程安全的,但wait函數(shù)要把等待線程的信息和事件關(guān)聯(lián)起來,以及事件signal以后把線程信息和事件解除關(guān)聯(lián)。所以就需要lock、unlock兩次。

基于base庫的APP全流程 基礎(chǔ)消息循環(huán)

程序的入口:src/ios/wework/main.mm

作為普通的C語言入口函數(shù)main,其中實(shí)現(xiàn)的最重要的當(dāng)然是消息循環(huán)。消息循環(huán)這塊后面會(huì)專門的開專題來進(jìn)行講述。
除此之外,這個(gè)入口文件(函數(shù))還負(fù)責(zé):

  • [x] A、給版本號全局變量APP_VERSION進(jìn)行賦值。
  • [x] B、解析命令行參數(shù)并將參數(shù)存入內(nèi)存,供后續(xù)的初始化使用。需要特別注意的是,wework并沒有使用到命令行,所以這個(gè)功能是可選的。
  • [x] C、wework的將運(yùn)行時(shí)用到的信息定義在WeWorkRuntime這個(gè)類里面,顧名思義就是負(fù)責(zé)整個(gè)app運(yùn)行時(shí)環(huán)境所需要的一切的類。這個(gè)類肯定很龐雜,故而我們需要一個(gè)deleagte類來幫助WeWorkRuntime來完成一些輔助的工作如根據(jù)命令行啟動(dòng)一些觀察工作、初始化log系統(tǒng)。這個(gè)delegate存在的意義,為程序的初始化劃分了不同的階段讓我們可以為不同的工作的不同階段進(jìn)行一個(gè)合理的安排。
  • [x] D、在這個(gè)函數(shù)內(nèi)部,會(huì)記錄程序啟動(dòng)的時(shí)間點(diǎn)供后續(xù)的各個(gè)數(shù)據(jù)統(tǒng)計(jì)上報(bào)項(xiàng)使用。

運(yùn)行時(shí)的環(huán)境:src/common/wework_runtime.h src/common/wework_runtime.cpp

作為APP運(yùn)行時(shí)的載體,這個(gè)接口非常的簡單就一個(gè)靜態(tài)的創(chuàng)建方法+三個(gè)初始化、處理運(yùn)行時(shí)消息和關(guān)閉消息循環(huán)的純虛函數(shù)組成。

  • [x] A、WeWorkRuntimeImpl繼承了接口WeWorkRuntime,是其一個(gè)具體的實(shí)現(xiàn)。其中接口的靜態(tài)創(chuàng)建方法就是創(chuàng)建一個(gè)WeWorkRuntimeImpl的裸指針。這個(gè)WeWorkRuntimeImpl在cpp實(shí)現(xiàn)文件中同時(shí)完成了成員變量、方法的定義和實(shí)現(xiàn)。原來可以這樣用,以前一直是接口一個(gè).h文件,實(shí)現(xiàn)類分別有.h和.cpp文件。
  • [x] B、成員變量方面,三個(gè)bool分別描述,運(yùn)行時(shí)是否初始化完畢、是否已經(jīng)關(guān)閉和是否創(chuàng)建了非UI線程。一個(gè)AtExitManager的智能指針用來控制所有從singleton模板創(chuàng)建的單例的生命周期,所有的單例在其析構(gòu)的時(shí)候進(jìn)行析構(gòu),如果AtExitManager不進(jìn)行初始化,則singleton模板的繼承類創(chuàng)建也會(huì)失敗。第一章 · 第一節(jié)中提到的WeWorkMainDelegate*也是其一個(gè)成員變量
  • [x] C、NotificationService未研究,待續(xù)
  • [x] D、Initialize方法完成了各個(gè)成員變量的初始化,給android平臺(tái)下需要預(yù)先設(shè)定sqlite3_temp_directory這個(gè)全局變量的值,否則sqlite的初始化會(huì)失敗。調(diào)用第一章 · 第一節(jié)提到的delegate的為各個(gè)階段準(zhǔn)備的預(yù)處理函數(shù)(雖然我們完全沒有用到這些函數(shù)如(SandboxInitialized等)。然后對WeWorkMainLoop對象main_loop_進(jìn)行賦值、初始化、再初始化、啟動(dòng)(Init()、EarlyInitialization()、InitializeToolkit()、MainMessageLoopStart()),在最后創(chuàng)建其他的非UI線程并檢查有無錯(cuò)誤。這里有個(gè)小疑問在最后成功return前,為什么調(diào)用了signal(SIGPIPE, SIG_IGN);意義何在???(轉(zhuǎn):當(dāng)服務(wù)器close一個(gè)連接時(shí),若client端接著發(fā)數(shù)據(jù)。
  • [x] 根據(jù)TCP 協(xié)議的規(guī)定,會(huì)收到一個(gè)RST響應(yīng),client再往這個(gè)服務(wù)器發(fā)送數(shù)據(jù)時(shí),系統(tǒng)會(huì)發(fā)出一個(gè)SIGPIPE信號給進(jìn)程,告訴進(jìn)程這個(gè)連接已經(jīng)斷開了,不要再寫了。
  • [x] 根據(jù)信號的默認(rèn)處理規(guī)則SIGPIPE信號的默認(rèn)執(zhí)行動(dòng)作是terminate(終止、退出),所以client會(huì)退出。若不想客戶端退出可以把SIGPIPE設(shè)為SIG_IGN),從描述可以看到因?yàn)槲覀兊腁PP是單進(jìn)程的所以這一行的意義并不大。
  • [x] E、Run()和Shutdown()其實(shí)更多的是對WeWorkMainLoop對象main_loop_的一些方法的組合調(diào)用。Run()里面調(diào)用的是WeWorkMainLoop的RunMainMessageLoopParts(),Shutdown()調(diào)用的是WeWorkMainLoop的ShutdownThreadsAndCleanUp(),并進(jìn)行了一些變量的反初始化和析構(gòu)的工作。
  • [x] F、RunMainMessageLoopParts()函數(shù)在iOS下,因?yàn)橹骶€程的消息循環(huán)我們不能接管,故而只能采取Attach的方式,把一些消息循環(huán)中需要用到的變量、對象告訴給pump。真正的主線程消息循環(huán)是UIApplicationMain函數(shù)。

接下來我們進(jìn)入到整個(gè)框架最關(guān)鍵、最靈魂也最復(fù)雜的,處理整個(gè)APP進(jìn)程消息的WeWorkMainLoop。

消息循環(huán):src/common/wework_main_loop.h src/common/wework_main_loop.cpp

這個(gè)類以及下一節(jié)要介紹的這一組類說夸張一點(diǎn),是任何平臺(tái)的客戶端類程序的心臟,它為進(jìn)程提供了可用的線程,驅(qū)動(dòng)程序相應(yīng)各類外部消息,管理進(jìn)程類各個(gè)對象的生命周期。當(dāng)客戶端程序員已經(jīng)熟練掌握了UI、邏輯、網(wǎng)絡(luò)、數(shù)據(jù)、兼容層以及等等客戶端的實(shí)現(xiàn)以后。就像醫(yī)學(xué)院里,成績最好的、最有理想的學(xué)生會(huì)選擇外科,這部分佼佼者中的金字塔尖的學(xué)生會(huì)選擇胸外科對心臟動(dòng)手術(shù)一樣。對消息循環(huán)的掌握對客戶端程序員尤其重要。
這個(gè)類極其復(fù)雜,這里會(huì)抽絲剝繭,只講其最精華的部分。

  • [x] A、WeWorkMainLoop* g_current_browser_main_loop 每個(gè)進(jìn)程都會(huì)有一個(gè)全局唯一的全局變量。構(gòu)造函數(shù)、析構(gòu)函數(shù)分別對其初始化和反初始化
  • [x] B、EarlyInitialization()函數(shù)進(jìn)行各種需要提前準(zhǔn)備的工作,比如根據(jù)命令行啟動(dòng)各種監(jiān)控,給windows程序初始化socket。wework在這里沒有需要做的工作。
  • [x] C、MainMessageLoopStart()函數(shù)判斷當(dāng)前的線程是否有一個(gè)base::MessageLoop綁定,如果沒有就初始化 scoped_ptr<base::MessageLoop> main_message_loop_,并調(diào)用InitializeMainThread()函數(shù)將當(dāng)前主線程命名,把成員變量 scoped_ptr<WeWorkThreadImpl> main_thread_用當(dāng)前線程初始化。有意思的是,如果去搜索一下這個(gè)變量你會(huì)發(fā)現(xiàn),main_message_loop_除了在這里被reset賦了初始值,它就沒有在其他任何地方被引用了。這是說它的存在是沒有意義的嗎?當(dāng)然不是!具體它的作用請參考第一章 · 第四節(jié)。如果是安卓平臺(tái),還需要主動(dòng)調(diào)用 base::MessageLoopForUI::current()->Start() 去進(jìn)行初始化???

底層的消息循環(huán):src/third_party/base/message_loop/ (主目錄) message_loop.h message_loop.cc message_pump.h message_pump_mac.h.mm message_pump_libevent.h.cc message_pump_default.h.cc

這一部分是更底層的消息循環(huán)。從上面提供的涉及到的文件名稱我們可以看到,pump就是那個(gè)驅(qū)動(dòng)程序生生不息的心臟。接下來我們回答上一節(jié)留下的問題。要回答這個(gè)問題我們就要去base::MessageLoop的構(gòu)造函數(shù)去看看它做了什么工作。不過在看之前,我們還是先簡單分析一些這個(gè)類的構(gòu)成

  • [x] A、base::MessageLoop的父類是MessagePump::Delegate,從繼承關(guān)系就能夠猜到base::MessageLoop是MessagePump的一個(gè)delegate,幫助MessagePump來完成一些輔助的工作。如果我們具體的去看看MessagePump::Delegate的實(shí)現(xiàn),這個(gè)delegate由三個(gè)純虛函數(shù)加一個(gè)定義為空的虛函數(shù)構(gòu)成。三個(gè)純虛函數(shù)都和“執(zhí)行工作”有關(guān)。分別是 DoWork() DoDelayedWork(TimeTicks* next_delayed_work_time) DoIdleWork(),從名字可以看出它們就是來真正執(zhí)行各種Task的函數(shù)(Task請參看下一節(jié)),我們接下來就要仔細(xì)分析這三個(gè)函數(shù),不過無本之木無源之水,我們還是從構(gòu)造函數(shù)開始。

  • [x] B、base::MessageLoop的構(gòu)造函數(shù)還是慣例的初始化成員變量以及調(diào)用構(gòu)造函數(shù)。type_說明base::MessageLoop是UI或DB或IO。nestable_tasks_allowed_說明在一個(gè)Task執(zhí)行中能否處理這個(gè)Task產(chǎn)生的新Task,默認(rèn)是允許的。這里還有第三個(gè)成員變量是RunLoop* run_loop_,不過我們看見構(gòu)造函數(shù)并沒有初始化這個(gè)變量。實(shí)際上這個(gè)變量就是對它所在的base::MessageLoop的消息循環(huán)函數(shù)族的一個(gè)封裝類。由這個(gè)類來負(fù)責(zé)消息循環(huán)生命周期、嵌套深度等運(yùn)行信息。特別強(qiáng)調(diào)一點(diǎn),iOS主線程其實(shí)沒有用到這個(gè)變量(大霧)。

  • [x] C、構(gòu)造函數(shù)里面除了初始化變量,還做了兩件重要的事情,調(diào)用Init()函數(shù)以及創(chuàng)建了scoped_ptr<MessagePump> pump_成員變量。這里需要注意的是pump_是protected繼承的。從構(gòu)造函數(shù)我們可以看出MessagePump是在它的delegate中創(chuàng)建的(F點(diǎn)會(huì)繼續(xù)解釋如何創(chuàng)建的)。

  • [x] D、在Init()函數(shù)中,代碼首先就是一句 lazy_tls_ptr.Pointer()->Set(this); 這一行代碼能夠被單獨(dú)提出來進(jìn)行說明,因?yàn)?LazyInstance<base::ThreadLocalPointer<MessageLoop> >::Leaky lazy_tls_ptr 這個(gè)變量用到了LazyInstance延遲創(chuàng)建模板。所謂延遲創(chuàng)建就是當(dāng)這個(gè)類對象真正被使用到了也就是說lazy_tls_ptr的Pointer()方法或Get()被調(diào)用的時(shí)候,才采用POD的方式(http://en.wikipedia.org/wiki/Plain_old_data_structure),把對象的真實(shí)數(shù)據(jù)通過拷貝的方式,賦值給堆上在程序啟動(dòng)前就分配好空間的地址上。用全局變量提前在堆上分配空間,不僅速度更快還能有效的避免堆上過早的產(chǎn)生碎片。所以看chromium的代碼,LazyInstance的對象都是全局對象,不過現(xiàn)在的代碼已經(jīng)刪除了很多與chrome瀏覽器業(yè)務(wù)邏輯相關(guān)的LazyInstance全局對象了。話說回來,這個(gè)對象封裝的是Thread Local Storage,每個(gè)平臺(tái)都略有區(qū)別,POSIX是用pthread_setspecific。

  • [x] E、在Init()函數(shù)中,會(huì)創(chuàng)建一個(gè)IncomingTaskQueue用來接收向這個(gè)線程輸入的Task。這個(gè)類繼承了RefCountedThreadSafe,這說明了兩點(diǎn):一、這個(gè)變量會(huì)被多個(gè)線程使用到。二、因?yàn)橛玫搅艘糜?jì)數(shù),說明這個(gè)成員變量的生命周期是長于它所在的類的,這點(diǎn)也很好理解,當(dāng)要銷毀base::MessageLoop的時(shí)候,可能還有Task在輸入隊(duì)列中。我們在這里簡單分析一下IncomingTaskQueue的實(shí)現(xiàn),它就是一個(gè)輸入Task隊(duì)列的維護(hù)管理類。把鎖放入這個(gè)對象,這樣Get和Set看起來就很整潔了。緊接著函數(shù)會(huì)為message_loop_proxy_成員變量初始化創(chuàng)建一個(gè)MessageLoopProxy接口的實(shí)例??雌饋砗芨呱畹挠玫搅嗽O(shè)計(jì)模式中的Proxy模式,確實(shí)也是。從MessageLoopProxy的構(gòu)造函數(shù)可以看到傳入了incoming_task_queue_成員變量,實(shí)際上Proxy就是對incoming_task_queue_的一個(gè)訪問控制和封裝,提供剛出來的三個(gè)虛函數(shù)接口,在其內(nèi)部的實(shí)現(xiàn)都是調(diào)用incoming_task_queue_->AddToIncomingQueue把Task加入到輸入隊(duì)列中。注意不要被proxy的名字給騙了,這個(gè)proxy其實(shí)只代理了message_loop_對象添加Taks這個(gè)功能族的相關(guān)方法。正因?yàn)槿绱?,其類的結(jié)構(gòu)也和教科書上的UML結(jié)構(gòu)有區(qū)別,這種改變將message_loop_真正需要開發(fā)給其他線程調(diào)用的接口,不僅進(jìn)行了很好的封裝(單獨(dú)的父類),也把需要暴露的部分盡可能的最小化了。這樣使用者在使用時(shí),將無法對這個(gè)重要類的其他重要功能造成任何影響。

  • [x] F、ThreadTaskRunnerHandle包含一個(gè)SingleThreadTaskRunner成員變量,這個(gè)類也是MessageLoopProxy的父類,其實(shí)代碼就是傳的MessageLoopProxy變量的地址進(jìn)去的。ThreadTaskRunnerHandle對象會(huì)把自己的地址裝到TLS里面,這樣當(dāng)timer定時(shí)器到時(shí)間的時(shí)候,就會(huì)把TLS里面的ThreadTaskRunnerHandle對象地址取出來,就是上面提到的MessageLoopProxy變量。然后調(diào)用變量的三個(gè)純虛函數(shù),把Task放入輸入隊(duì)列。記住只有Timer觸發(fā)的Task任務(wù)是通過ThreadTaskRunnerHandle加入到輸入隊(duì)列的。結(jié)合上面的E點(diǎn),非Timer的Task中的一部分,會(huì)通過message_loop_proxy_把任務(wù)放入對應(yīng)線程的輸入隊(duì)列等待執(zhí)行。剩余的Task則通過第一章 · 第六節(jié)的WeWorkThread接口把Task添加到輸入隊(duì)列。

  • [x] G、創(chuàng)建MessagePump是一個(gè)MessageLoop的靜態(tài)方法CreateMessagePumpForType(Type type)。從實(shí)現(xiàn)我們可以看到從線程類型的角度來說,對于iOS的程序來說,一共有三類:UI線程、IO線程和其他線程,分別對應(yīng)的Pump也不同為MessagePumpMac、MessagePumpLibevent和MessagePumpDefault。

  • 這一節(jié)寫到這里才能回答上一節(jié)留下的問題 Q:“WeWorkMainLoop類中的main_message_loop_只被初始化了,但沒有被任何地方‘引用’,為什么?或者說如果不初始化main_message_loop_為什么程序就不能正常工作了?”A:main_message_loop_構(gòu)造函數(shù)中創(chuàng)建了incoming_task_queue_,并以此為基礎(chǔ)創(chuàng)建了message_loop_proxy_和thread_task_runner_handle_。這三個(gè)變量是線程消息循環(huán)核心。沒有他們的支持任何自定義的Task都無法執(zhí)行,所有需要跨線程的地方都無法正常執(zhí)行了。說到這里又引入了一個(gè)疑問,你介紹的這三個(gè)變量都是輔助支撐消息循環(huán)。那么真正的消息循環(huán)又是怎么跑起來的?好像不解釋清楚這個(gè)問題,就沒辦法說清楚,誰在用這些變量來驅(qū)動(dòng)程序工作。其實(shí)這些工作都是pump_變量來完成的。接下來我們繼續(xù)分析pump_的工作

  • [x] H、iOS的主線程用到的pump是MessagePumpMac和MessagePumpUIApplication。前者只包含一個(gè)靜態(tài)創(chuàng)建MessagePump*對象指針的靜態(tài)函數(shù),而這個(gè)指針指向的就是后者說定義的對象。MessagePumpUIApplication因?yàn)閕OS的消息循環(huán)不能接管,并沒有真正的Run()函數(shù)。當(dāng)有新的Task需要主線程執(zhí)行的時(shí)候,先加入到incoming_task_queue_隊(duì)列中,然后通過CFRunLoopSourceSignal(...)激活我們自定義的Task處理函數(shù)RunWorkSource()。因?yàn)闆]有MessagePumpUIApplication::Run()函數(shù)(雖然有,但是不會(huì)被調(diào)用,內(nèi)部實(shí)現(xiàn)也是空的),所以在main.mm中我們會(huì)顯示的調(diào)用WeWorkRuntimeImpl的Run()函數(shù),在這個(gè)函數(shù)內(nèi)部會(huì)調(diào)用MessagePumpUIApplication::Attatch()初始化run_loop_。雖然run_loop_在iOS主線程中并沒有什么用,但因?yàn)镸essagePump和message_loop中的函數(shù)實(shí)現(xiàn)是為所有線程服務(wù)的,如果不調(diào)用Attach()初始化run_loop_,很多DCHECK都無法通過。還記得B點(diǎn)那個(gè)大霧嗎?我們發(fā)現(xiàn)在MessagePumpUIApplication里面也有個(gè)run_loop_變量。這個(gè)類只給iOS的主線程使用,雖然所有的pump中都有一個(gè)同名的run_loop_變量,但這個(gè)類的run_loop_變量的類型卻與眾不同是CFRunLoopRef。它其實(shí)就是當(dāng)前iOS主線程消息循環(huán)的一個(gè)引用,我們用它給iOS主線程消息循環(huán)加上我們自己的函數(shù)到消息循環(huán)中,當(dāng)條件滿足的時(shí)候(沒有UI優(yōu)先的事件需要處理、Timer到時(shí)、主動(dòng)signal激活等等),對應(yīng)的自定義函數(shù)即被調(diào)用。

  • [x] I、普通線程用到的pump是MessagePumpDefault,在Run()函數(shù)中依次調(diào)用loop的DoWork() DoDelayedWork(...) DoIdleWork()三個(gè)函數(shù)。如果有沒有延遲執(zhí)行的Task,則線程會(huì)等待在一個(gè)event上,其他線程把Task放入本線程的incoming_task_queue_后,會(huì)signal這個(gè)event,讓本線程的Run()函數(shù)跑起來執(zhí)行Task。相反,如果有延遲執(zhí)行的任務(wù),線程的event就會(huì)用當(dāng)前時(shí)刻到最近一個(gè)延遲Task的時(shí)間間隔作為等待時(shí)間進(jìn)行等待??戳松厦娴慕榻B,我們能夠發(fā)現(xiàn)MessagePumpDefault的ScheduleWork()可以在任意線程被調(diào)用用來激活當(dāng)前線程進(jìn)行工作。而MessagePumpDefault的ScheduleDelayedWork()一定是在當(dāng)前線程被觸發(fā)的,因?yàn)樵谶@個(gè)函數(shù)的內(nèi)部僅僅是計(jì)算延遲任務(wù)需要延遲的時(shí)間。

  • [x] J、IO線程負(fù)責(zé)網(wǎng)絡(luò)的收發(fā),他用到的pump是MessagePumpLibevent。通過名字可以看到它用到了LibEvent庫。LibEvent庫是一個(gè)跨平臺(tái)的網(wǎng)絡(luò)庫,它為各個(gè)平臺(tái)封裝了最適合平臺(tái)的網(wǎng)絡(luò)收發(fā)的實(shí)現(xiàn)。它的具體實(shí)現(xiàn)請參考專門的介紹文章。因?yàn)槭荌O線程,其signal的方式是通過建立一對pipe的fd。讓IO線程檢查輸入fd是否可讀,一旦可讀事件發(fā)生,就處理輸入隊(duì)列的Task事件。

線程和Task:src/third_party/base/threading/thread.h src/common/wework_client_thread.h src/common/wework_client_thread_impl.h src/common/wework_client_thread_impl.cc

我們先來看線程方面的接口,一共兩個(gè) WeWorkThread 和Thread。前者提供了一組跨線程分發(fā)任務(wù)的接口,后者則提供了對線程本身的一個(gè)封裝。

  • [x] A、WeWorkThread提供了一組PostXXXTask的靜態(tài)方法,這些方法會(huì)調(diào)用WeWorkTHreadImpl::PostTaksHelper(...)。后者其實(shí)就是就是根據(jù)參數(shù)確定需要執(zhí)行的Task的線程tid,然后從保存各個(gè)線程的message_loop_全局?jǐn)?shù)組中找到對應(yīng)的loop_,然后用loop_的PostXXXTask的方法把任務(wù)加入輸入隊(duì)列。不過這里因?yàn)橛玫搅巳謹(jǐn)?shù)組,必須要加鎖,所以用WeWorkThread接口,整個(gè)Post的過程會(huì)加鎖兩次。所以推薦用message_loop_proxy_來發(fā)送Task。
  • [x] B、Thread則是一個(gè)線程的管理類。對線程的生命周期、名字和loop的關(guān)聯(lián)等提供服務(wù),本身沒有多少對外提供的接口可供使用。

任務(wù)隊(duì)列:src/third_party/base/message_loop/incoming_task_queue.h.cc

這個(gè)類用戶維護(hù)輸入(PostTaskXXX)的Task,因?yàn)門ask多來自于其他線程,在Task進(jìn)入隊(duì)列的時(shí)候需要加鎖。但整個(gè)APP進(jìn)程內(nèi)部的跨線程執(zhí)行,僅僅只在Task進(jìn)入隊(duì)列的時(shí)候加鎖,不需要程序員加鎖。程序員只需要關(guān)注代碼的邏輯,已經(jīng)邏輯需要在合適的地方執(zhí)行即可。這種跨線程只在進(jìn)入隊(duì)列的時(shí)候加鎖一次的實(shí)現(xiàn)方法非常巧妙值得推薦,網(wǎng)上有很多很詳細(xì)的文章深入的介紹這里的具體實(shí)現(xiàn),建議大家找來看看。

  • [x] A、incoming_queue_lock_唯一的一個(gè)負(fù)責(zé)跨線程調(diào)用同步的鎖。incoming_queue_是存放的Task的std::queue隊(duì)列。和隊(duì)列關(guān)系最密切的無疑是執(zhí)行隊(duì)列里面Taks的消息循環(huán),所以message_loop_就是那個(gè)隊(duì)列對應(yīng)的消息循環(huán)。next_sequence_num_是用來非順序執(zhí)行Task的時(shí)候,取出對應(yīng)的num的Task進(jìn)行執(zhí)行。
  • [x] B、AddToIncomingQueue就是把各種PostTask過來的Closure保存起來放到incoming_queue_里面。如果是delay執(zhí)行的Task還需要根據(jù)FIFO的規(guī)則,為其記錄一個(gè)sequence號。當(dāng)delay的時(shí)間結(jié)束需要開始執(zhí)行Task的時(shí)候,可能同時(shí)會(huì)有多個(gè)Task滿足執(zhí)行的條件,就需要根據(jù)sequence號區(qū)分先來后到。
  • [x] C、加入到incoming_queue_完畢以后,需要調(diào)用base::MessageLoop的ScheduleWork方法。這個(gè)方法根據(jù)不同的平臺(tái),不同的線程的類型有著不同的實(shí)現(xiàn),但一般都叫signal,一般是在非執(zhí)行線程被調(diào)用的。詳情請參考第一章 · 第四節(jié)。ScheduleDelayedWork方法上面也提到過,只是把延遲的最短時(shí)間記錄下,線程消息循環(huán)會(huì)檢查時(shí)間是否過期,過期就執(zhí)行對應(yīng)的Task。這個(gè)方法是在本線程調(diào)用的。

可等待事件 : src/third_party/base/synchronization/waitable_event.h.cc

對于可等待的通知事件來說,“等待”部分實(shí)現(xiàn)的核心就是用鎖來進(jìn)行實(shí)現(xiàn)。在POSIX標(biāo)準(zhǔn)下,通過pthread_mutex_t來實(shí)現(xiàn)鎖的功能?!巴ㄖ辈糠謱?shí)現(xiàn)的核心則是通過條件變量來實(shí)現(xiàn)。在POSIX標(biāo)準(zhǔn)下,通過pthread_cond_t來實(shí)現(xiàn)通知的功能,pthread_cond_t需要一個(gè)pthread_mutex_t的配合來完成通知的功能。

主要的流程是

pthread_cond_init(&cond) //初始化pthread_cond_t變量
pthread_mutex_lock(&mutex) //鎖住pthread_mutex_t變量
WaitableEvent //調(diào)用SyncWaiter記錄下當(dāng)前線程(等待線程)的相關(guān)信息
pthread_cond_wait(&cond, &mutex) //首先解鎖mutex,然后等待cond被signal激活(線程X)
pthread_cond_signal(&cond) //將會(huì)signal激活等待在這個(gè)條件變量上的線程(并不是調(diào)度這個(gè)線程執(zhí)行),并鎖住mutex
pthread_mutex_unlock(&mutex) //解鎖pthread_mutex_t變量

最后線程X,被OS調(diào)度后會(huì)執(zhí)行wait后續(xù)的代碼。POSIX為什么會(huì)前后兩次lock、unlock呢?因?yàn)榭傻却耐ㄖ录强梢员挥迷诙鄠€(gè)線程中,這些線程可以同時(shí)等待某個(gè)事件的完成。那么wait函數(shù)就需要是線程安全的,但wait函數(shù)要把等待線程的信息和事件關(guān)聯(lián)起來,以及事件signal以后把線程信息和事件解除關(guān)聯(lián)。所以就需要lock、unlock兩次。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容