Android 源碼學(xué)習(xí) -- 底層多線程task設(shè)計(jì)

以前工作多偏重于業(yè)務(wù)邏輯刹衫,較少關(guān)注到底層的邏輯實(shí)現(xiàn),自己寫內(nèi)部工具中時(shí)也比較隨意搞挣,遇到用多線程的地方都是臨時(shí)起一個(gè)線程處理耗時(shí)的復(fù)雜任務(wù)带迟,任務(wù)結(jié)束后,自動(dòng)被主線程回收柿究。線程的頻繁創(chuàng)建銷毀其實(shí)是存在很大開(kāi)銷的邮旷。下面的這種設(shè)計(jì)使用方式可以更高效的利用multi-thread

MessageLoopThread

循環(huán)隊(duì)列任務(wù)處理機(jī)制簡(jiǎn)單說(shuō)明:

  • 一個(gè)持續(xù)運(yùn)行的Thread(while (1) { do tasks})
  • 一個(gè) task queue (如果用的不是線程安全的標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu),則需要另外加鎖來(lái)保證)
  • task queue中存放的是 類似于 C++ Functor的對(duì)象蝇摸,或者簡(jiǎn)單理解就是task function的一段代碼
  • 其他線程會(huì)將耗時(shí)任務(wù)封裝成task婶肩,加入隊(duì)列中
  • Thread 運(yùn)行時(shí),按queue中順序依次執(zhí)行其中的task function
    上面有一個(gè)很重要解決的問(wèn)題是貌夕,需要把不同的邏輯處理函數(shù) 封裝成統(tǒng)一的task function律歼,答案就是Android里面使用了chromium的base::Bind來(lái)實(shí)現(xiàn)統(tǒng)一的封裝

可以參考關(guān)于std::bind 來(lái)理解這個(gè)base::Bind作用,實(shí)際作用有些像python的functools 里面的輔助函數(shù)啡专,也有些類似python里的 wrapper裝飾器险毁,簡(jiǎn)單理解就是把一個(gè)函數(shù)固化入?yún)⒑螅梢粋€(gè)新的函數(shù)们童。

  • 下面舉例說(shuō)明:
static bt_status_t btif_gatts_add_service(int server_if,
                                          const btgatt_db_element_t* service,
                                          size_t service_count) {
  CHECK_BTGATT_INIT();
  return do_in_jni_thread(FROM_HERE,
                          Bind(&add_service_impl, server_if,
                               std::vector(service, service + service_count)));
}

static void add_service_impl(int server_if,
                             vector<btgatt_db_element_t> service) {
  if (service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER) ||
      service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) {
    LOG_ERROR("%s: Attept to register restricted service", __func__);
    HAL_CBACK(bt_gatt_callbacks, server->service_added_cb, BT_STATUS_FAIL,
              server_if, service.data(), service.size());
    return;
  }

  BTA_GATTS_AddService(
      server_if, service,
      jni_thread_wrapper(FROM_HERE, base::Bind(&on_service_added_cb)));
}

bt_status_t do_in_jni_thread(const base::Location& from_here,
                             base::OnceClosure task) {
  if (!jni_thread.DoInThread(from_here, std::move(task))) {
    LOG(ERROR) << __func__ << ": Post task to task runner failed!";
    return BT_STATUS_FAIL;
  }
  return BT_STATUS_SUCCESS;
}

static MessageLoopThread jni_thread("bt_jni_thread");

bool MessageLoopThread::DoInThread(const base::Location& from_here,
                                   base::OnceClosure task) {
  return DoInThreadDelayed(from_here, std::move(task), base::TimeDelta());
}

bool MessageLoopThread::DoInThreadDelayed(const base::Location& from_here,
                                          base::OnceClosure task,
                                          const base::TimeDelta& delay) {
  std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
  if (is_main_ && init_flags::gd_rust_is_enabled()) {
    if (rust_thread_ == nullptr) {
      LOG(ERROR) << __func__ << ": rust thread is null for thread " << *this
                 << ", from " << from_here.ToString();
      return false;
    }

    shim::rust::main_message_loop_thread_do_delayed(
        **rust_thread_,
        std::make_unique<shim::rust::OnceClosure>(std::move(task)),
        delay.InMilliseconds());
    return true;
  }

上面這段代碼btif_gatts_add_service 就是將函數(shù)add_service_impl通過(guò)Bind入?yún)⒐袒筠D(zhuǎn)換成統(tǒng)一的OnceClosure task玉控,放到bt_jni_thread中

這里需要注意的一點(diǎn)shim::rust::main_message_loop_thread_do_delayed最終調(diào)用的是Rust實(shí)現(xiàn),這里似乎是Android在新版本里最終底層多線程的處理都改用Rust實(shí)現(xiàn)了汞扎,目前尚不熟悉Rust語(yǔ)言和C++的調(diào)用Rust處理柠傍,對(duì)應(yīng)rust文件message_loop_thread.rs,據(jù)說(shuō)是Rust相比C++在多線程處理上使用起來(lái)更方便高效齐板;DoInThreadDelayed函數(shù)也有相關(guān)mock實(shí)現(xiàn)吵瞻,可以從mock實(shí)現(xiàn)來(lái)理解上面所說(shuō)的細(xì)節(jié)葛菇,這里就不列舉出來(lái)了

  • 下面這段代碼的設(shè)計(jì)也很巧妙,可以在實(shí)際應(yīng)用中借鑒:
static void add_service_impl(int server_if,
                             vector<btgatt_db_element_t> service) {
  if (service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER) ||
      service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) {
    LOG_ERROR("%s: Attept to register restricted service", __func__);
    HAL_CBACK(bt_gatt_callbacks, server->service_added_cb, BT_STATUS_FAIL,
              server_if, service.data(), service.size());
    return;
  }

  BTA_GATTS_AddService(
      server_if, service,
      jni_thread_wrapper(FROM_HERE, base::Bind(&on_service_added_cb)));
}

extern void BTA_GATTS_AddService(tGATT_IF server_if,
                                 std::vector<btgatt_db_element_t> service,
                                 BTA_GATTS_AddServiceCb cb) {
  do_in_main_thread(FROM_HERE,
                    base::Bind(&bta_gatts_add_service_impl, server_if,
                               std::move(service), std::move(cb)));
}

template <typename R, typename... Args>
base::Callback<R(Args...)> jni_thread_wrapper(const base::Location& from_here,
                                              base::Callback<R(Args...)> cb) {
  return base::Bind(
      [](const base::Location& from_here, base::Callback<R(Args...)> cb,
         Args... args) {
        do_in_jni_thread(from_here,
                         base::Bind(cb, std::forward<Args>(args)...));
      },
      from_here, std::move(cb));
}

static void on_service_added_cb(tGATT_STATUS status, int server_if,
                                vector<btgatt_db_element_t> service) {
  HAL_CBACK(bt_gatt_callbacks, server->service_added_cb, status, server_if,
            service.data(), service.size());
}

有兩點(diǎn):

  • 用了兩個(gè)線程來(lái)完成一個(gè)task橡羞,準(zhǔn)確的說(shuō)是一個(gè)線程完成主體任務(wù)處理眯停,另一個(gè)線程完成任務(wù)結(jié)果callback處理,這樣主體任務(wù)線程的處理不會(huì)因?yàn)閱我蝗蝿?wù)callback而阻塞 (這個(gè)比較經(jīng)典的場(chǎng)景就是在UI設(shè)計(jì)里卿泽,以前最開(kāi)始使用Qt做tool時(shí)有遇到:后臺(tái)處理莺债,前臺(tái)刷新邏輯做線程分離,后臺(tái)處理不會(huì)導(dǎo)致UI界面卡住又厉,UI界面重繪不會(huì)導(dǎo)致后臺(tái)任務(wù)阻塞九府,甚至復(fù)雜處理邏輯可以使用更多的線程來(lái)保證)
  • jni_thread_wrapper中使用了Bind和C++ 匿名函數(shù)對(duì)callback函數(shù)做統(tǒng)一封裝,實(shí)際處理時(shí)調(diào)用cb.Run(GATT_ERROR, server_if, std::move(service));
void bta_gatts_add_service_impl(tGATT_IF server_if,
                                std::vector<btgatt_db_element_t> service,
                                BTA_GATTS_AddServiceCb cb) {
  uint8_t rcb_idx =
      bta_gatts_find_app_rcb_idx_by_app_if(&bta_gatts_cb, server_if);

  LOG(INFO) << __func__ << ": rcb_idx=" << +rcb_idx;

  if (rcb_idx == BTA_GATTS_INVALID_APP) {
    cb.Run(GATT_ERROR, server_if, std::move(service));
    return;
  }

Notes:

  • 當(dāng)然上面任務(wù)的處理是屬于異步的覆致,對(duì)于時(shí)效性要求特別高的場(chǎng)景侄旬,可能不適合使用;如果有很多產(chǎn)生任務(wù)的線程煌妈,那可能需要合理規(guī)劃任務(wù)處理線程的個(gè)數(shù)以及實(shí)際分配協(xié)同等
  • 下面rust中多線程處理代碼儡羔,rust在多線程處理上比C++更高效,這個(gè)需要更多研究璧诵。
pub fn main_message_loop_thread_do_delayed(
    thread: &mut MessageLoopThread,
    closure: cxx::UniquePtr<ffi::OnceClosure>,
    delay_ms: i64,
) {
    assert!(init_flags::gd_rust_is_enabled());
    if delay_ms == 0 {
        if thread.tx.send(closure).is_err() {
            log::error!("could not post task - shutting down?");
        }
    } else {
        thread.rt.spawn(async move {
            // NOTE: tokio's sleep can't wake up the system...
            // but hey, neither could the message loop from libchrome.
            //
            // ...and this way we don't use timerfds arbitrarily.
            //
            // #yolo
            tokio::time::sleep(Duration::from_millis(delay_ms.try_into().unwrap_or(0))).await;
            closure.Run();
        });
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汰蜘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子之宿,更是在濱河造成了極大的恐慌族操,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件比被,死亡現(xiàn)場(chǎng)離奇詭異色难,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)等缀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門枷莉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人尺迂,你說(shuō)我怎么就攤上這事笤妙。” “怎么了噪裕?”我有些...
    開(kāi)封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蹲盘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我膳音,道長(zhǎng)召衔,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任严蓖,我火速辦了婚禮薄嫡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颗胡。我一直安慰自己毫深,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布毒姨。 她就那樣靜靜地躺著哑蔫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弧呐。 梳的紋絲不亂的頭發(fā)上闸迷,一...
    開(kāi)封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音俘枫,去河邊找鬼腥沽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鸠蚪,可吹牛的內(nèi)容都是我干的今阳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼茅信,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盾舌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蘸鲸,我...
    開(kāi)封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤妖谴,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后酌摇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體膝舅,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年妙痹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铸史。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡怯伊,死狀恐怖琳轿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情耿芹,我是刑警寧澤崭篡,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站吧秕,受9級(jí)特大地震影響琉闪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜砸彬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一颠毙、第九天 我趴在偏房一處隱蔽的房頂上張望斯入。 院中可真熱鬧,春花似錦蛀蜜、人聲如沸刻两。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)磅摹。三九已至,卻和暖如春霎奢,著一層夾襖步出監(jiān)牢的瞬間户誓,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工幕侠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帝美,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓橙依,卻偏偏與公主長(zhǎng)得像证舟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窗骑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容