国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Message Loop 原理及應用

時飛 / 1921人閱讀

摘要:此文已由作者王榮濤授權網易云社區發布。新消息的添加可能來自于本線程也可能來自于其他線程,甚至包括其他進程中的線程。另一種結束消息循環的方式是強制中止其所屬線程的執行,當然了,這是極不推薦的。

此文已由作者王榮濤授權網易云社區發布。

歡迎訪問網易云社區,了解更多網易技術產品運營經驗。

Message loop,即消息循環,在不同系統或者機制下叫法也不盡相同,有被叫做event loop,也有被叫做run loop或者其他名字的,它是一種等待和分派消息的編程結構,是經典的消息驅動機制的基礎。為了方便起見,本文對各系統下類似的結構統稱為message loop。

結構
Message loop,顧名思義,首先它是一種循環,這和我們初學C語言時接觸的for、while是同一種結構。

在Windows下它可能是這個樣子的:

MSG msg;BOOL bRet;
...while (bRet = ::GetMessage(&msg, NULL, 0, 0)) {    if (bRet == -1) {        // Handle Error
    } else {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
}

在iOS下它可能是這個樣子的:

BOOL shouldQuit = NO;
...BOOL ok = YES;
NSRunLoop *loop = [NSRunLoop currentRunLoop];while (ok && !shouldQuit) {
    ok = [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

而用libuv實現的I/O消息循環則可能是這樣:

bool should_quit = false;
...
uv_loop_t *loop = ...while (!should_quit) {
    uv_run(loop, UV_RUN_ONCE);
}

在其他系統或機制下,它還有各自獨特的實現,但都大體相似。

事實上,正常運行過程中在接到特殊消息或者指令之前,它就是一個徹底的死循環!同時,這樣的結構也決定了它更多意義上是一種單線程上的設計。也正因為如此,對這種編程結構進行了封裝的系統(比如iOS)也往往不保證或者根本不屑于提及其線程安全性。而多線程共享的消息循環在筆者看來在絕大部分場景下都屬于逆天的設計,本文只討論單線程上的消息循環。

Loop前面有個定語message,進一步表明它要處理的對象,即消息。這里說的消息是廣義上的消息,它可能是UI消息、通知、I/O事件等等。那么消息從哪里來?消息循環又從哪里提取它們?這在不同系統或機制下有所不同:有來自消息隊列的,有來自輸入源/定時器源的,有來自異步網絡、文件完成操作通知的,還有來自可觀察對象狀態變化的等等。這里把消息循環提取消息的源統稱為消息源,簡稱源。

消息產生后源不會也無法主動推給消息循環。以Windows消息為例,一條異步窗口消息產生后它會被存放在窗口所屬線程的消息隊列上,如果消息循環不采取任何措施,那么它將永遠無法被處理。消息循環從消息隊列中去抽取,它才能被取出并分派。這種從消息隊列中抽取消息的機制,我們叫做消息泵。

生命期
Message loop的生命期始于線程執行過程中第一次進入該循環的循環體,終于循環被break或者線程被強行終止那一刻,而兩者之間便是運行期。

運行期內,消息泵不停嘗試從源那里抽取消息,如果源內消息非空,那么消息將被立即取出,接著被分派處理。如果源內沒有消息,消息循環便進入空載(idling)階段。就像水池中沒有水時抽水泵開著是浪費電能一樣,如果消息泵在空載時也無休止地工作也將浪費幾乎所有的CPU資源。為了解決這個問題,需要消息泵在空載時能夠自我阻塞,這種特征往往需要源來提供。源的另一個特點是在新消息到達之后將阻塞中的消息泵(準確說是消息循環所在線程)喚醒,使之恢復工作。以上面的例子來說,GetMessage、NSRunLoop.runMode:beforeDate:以及uv_run操作的對象都具備這兩個特點。

新消息的添加可能來自于本線程也可能來自于其他線程,甚至包括其他進程中的線程。另外很多系統提供了對待處理消息的撤銷或者移除操作,比如Windows下的PeekMessage、CancelIo分別可以移除待處理的UI消息和I/O操作,iOS下的NSRunLoop.cancelPerformSelectorsWithTarget:族方法則可以撤銷待處理的selector。

結束消息循環的過程和結束一個普通的for、while循環大致相同,就是改變循環控制表達式的值使之不滿足繼續循環的條件。不同的地方在于,普通循環往往是自發的,而消息循環可能來自外部的需求,然后通過某種方式通知該消息循環讓其自我退出。另一種結束消息循環的方式是強制中止其所屬線程的執行,當然了,這是極不推薦的。

嵌套
Message loop是可以嵌套(nested)的,簡而言之就是Loop1上在處理一個任務的過程中又起了一個另一個Loop2。請看以下場景:

void RunLoop() {    while (GetMessage(&msg)) {
        ...
        ProcessMessage(&msg);
        ...
    }
}void Start() {
    RunLoop(); // 進入Loop1}void ProcessMessage(MSG *msg) {
    ...    if (msg->should_do_foo_bar) {
        Foo();
        RunLoop(); // 進入Loop2,嵌套!
        Bar();
    }
    ...
}

嵌套的一個典型案例就是模態對話框。在模態對話框返回之前此后的語句不會被執行,比如上例中Bar在RunLoop返回之前不會被執行,因為Loop1在Loop2啟動后就處于阻塞狀態了,這就引出了嵌套消息循環的一個特點:任何時刻有且只有一個Loop是活動的,其余都是被阻塞的。嵌套消息循環的另一個特點是它們同屬于一個線程,反過來說,非同線程的message loop無法形成嵌套。

嵌套的一個比較明顯的坑:如果Bar運行需要資源R,而R在Loop2生命期內被釋放了,那么等Loop2生命期結束后Loop1恢復執行,第一個調用的就是Bar,此時R已經不存在了,Bar的代碼如果缺乏足夠的保護就有可能會引起crash!

多線程通信
Message loop讓線程間通信變得足夠靈活。

Alt pic

如上圖,運行消息循環的兩個線程Thread 1和Thread 2之間通過向對方的消息隊列中投遞消息來進行通信,這個過程是完全異步的。

結合前文提到的消息循環嵌套技術,多線程通信時,通信發起線程可以在不阻塞本線程消息處理的前提下等待對方回應后再進行后續操作。以上文中的Foo和Bar為例,如果Foo異步請求資源,Bar處理接收到的資源,Loop 2等到資源被接收后立即結束,那么它們三者宏觀上看起來像是一次同步資源請求和處理操作,而且在此期間Thread 1和Thread 2消息處理順暢!這非常奇妙,在很多情況下比阻塞式的傻等有用多了。

然而,消息投遞過程本身是跨線程的操作,對于使用C++這樣的Native語言開發的場景,這意味著樸素地操作別的線程的消息隊列本身就存在隱患,所以一般需要對消息隊列進行鎖保護。此外,線程間一般推薦只持有對方消息隊列的弱引用,否則很容易陷入循環引用或者導致野指針范圍——試想如果Thread 2先退出,其消息隊列實體也被銷毀,此后如果Thread 1嘗試通過Thread 2消息隊列的裸指針向其投遞消息勢必造成災難。

多線程之間通信比較難以處理的是消息的撤銷和資源的管理,但是這個不在本文的討論范圍之內,如果有時間,筆者將在未來撰文討論這個問題。

附加機制
至此,本文描述的消息循環僅僅在處理消息本身,其實我們在消息循環中還可以加入一些十分有用的機制,這里介紹其中最常用的兩種。

空閑任務(Idle tasks)是在消息循環處于空載狀態時被處理的任務。消息循環空載往往意味著沒有特別緊要的消息需要處理,這個時候是處理空閑任務的絕佳時機,比如發送一些后臺統計數據。以基于libuv的I/O消息循環為例,對其稍加改動便可加入這種機制:

class UVMessageLoop {public:
    ...private:    bool should_quit_;    bool message_processed_;
    uv_loop_t *loop_;
};void UVMessageLoop::OnUVNotification(uv_poll_t *req, int status, int events) {
    UVMessageLoop *loop = static_cast(req->data);
    ...
    loop->message_processed_ = true;
}void UVMessageLoop::Run() {    for (;;) {
        uv_run(loop, UV_RUN_ONCE);        if (should_quit_)            break;        if (message_process_) {            // 剛剛處理了一條消息
            continue;
        }        // 沒有消息,處理idle task
        bool has_idle_task = DoIdleTasks();        if (should_quit_)            break;        if (has_idle_task) {            continue;
        }        // idle task都沒有,再抽取一次消息,沒有就自我阻塞
        uv_run(loop, UV_RUN_NOWAIT);
    }
}

注意上例中兩次uv_run調用的第二個參數是不同的,UV_RUN_NOWAIT用于嘗試從源抽取并處理一次I/O事件但是若沒有也立即返回;而UV_RUN_ONCE則是在沒有事件的時候被阻塞直到新事件到達。需要注意的是,在uv_run處理事件的時候最終會同步調用到UVMessageLoop::OnUVNotification,這樣其返回后可以通過檢查message_processed_來知道是否有消息被處理了。

遞延任務(Deferred tasks)是晚于投遞時間被執行的任務,比如在播放動畫時使用它可以在幀時間到達時才真正渲染某個幀。繼續以基于libuv的I/O消息循環為例,作如下改動后可以加入這種機制:

class UVMessageLoop {public:
    ...private:    bool should_quit_;    bool message_processed_;
    TimeTicks deferred_task_time_;
    uv_loop_t *loop_;
    uv_timer_t *timer_;
};void UVMessageLoop::OnUVNotification(uv_poll_t *req, int status, int events) {
    UVMessageLoop *loop = static_cast(req->data);
    ...
    loop->message_processed_ = true;
}void UVMessageLoop::OnUVTimer(uv_timer_t* handle, int status) {
    ...
}void UVMessageLoop::Run() {    for (;;) {
        uv_run(loop, UV_RUN_ONCE);        if (should_quit_)            break;        if (message_process_) {            // 剛剛處理了一條消息
            continue;
        }        // 沒有消息,處理遞延任務,同時獲取下一個遞延任務的時間
        bool has_deferred_task = DoDeferredTasks(&deferred_task_time_);        if (should_quit_)            break;        if (has_deferred_task) {            continue;
        }        // 也沒有遞延任務,處理idle task
        bool has_idle_task = DoIdleTasks();        if (should_quit_)            break;        if (has_idle_task) {            continue;
        }        // 沒有idle task
        if (delayed_task_time_.is_null()) {            // 也沒有deferred task,再抽取一次消息,沒有就自我阻塞
            uv_run(loop_, UV_RUN_ONCE);
        } else {
            TimeDelta delay = delayed_task_time_ - TimeTicks::Now();            if (delay > TimeDelta()) {                // 設置定時器,如果在定時器到期前還沒有其他事件到達而被解除阻塞,
                // 那么uv_run將因為定時到期事件而被解除阻塞
                uv_timer_start(timer_, OnUVTimer, delay.ToMilliseconds(), 0);
                uv_run(loop_, UV_RUN_ONCE);
                uv_timer_stop(timer_);
            } else {                // 有遞延任務未及時處理,進入下一輪后處理
                delayed_task_time_ = TimeTicks();
            }
        }        if (should_quit_)            break;
    }
}

由于遞延任務一般優先級高于空閑任務,所以我們先于空閑任務處理它們。另外deferred_task_time_記錄了下一個遞延任務的單調遞增時間(比如當前線程的clock值),當沒有I/O事件需要處理且也沒有Idle任務需要處理時,如果有尚未到期的遞延任務,那么需要在源上開啟一個定時器在遞延任務到期后解除消息泵的阻塞。因此,要支持遞延任務的源必須具備第三個特點,那就是支持定時喚醒。

參考資料:
http://docs.libuv.org/en/late...

https://msdn.microsoft.com/en...

https://developer.apple.com/l...

https://docs.google.com/docum...

網易云免費體驗館,0成本體驗20+款云產品!

更多網易技術、產品、運營經驗分享請點擊。

文章來源: 網易云社區

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/25255.html

相關文章

  • Android之消息機制問題

    摘要:通過向消息池發送各種消息事件通過處理相應的消息事件。消息泵通過不斷地從中抽取,按分發機制將消息分發給目標處理者。也稱之為消息隊列,特點是先進先出,底層實現是單鏈表數據結構。目錄介紹 6.0.0.1 談談消息機制Hander作用?有哪些要素?流程是怎樣的?簡單說一下你的看法! 6.0.0.2 為什么一個線程只有一個Looper、只有一個MessageQueue,可以有多個Handler? 6...

    番茄西紅柿 評論0 收藏0
  • 06.Android之消息機制問題

    摘要:通過向消息池發送各種消息事件通過處理相應的消息事件。子線程往消息隊列發送消息,并且往管道文件寫數據,主線程即被喚醒,從管道文件讀取數據,主線程被喚醒只是為了讀取消息,當消息讀取完畢,再次睡眠。 目錄介紹 6.0.0.1 談談消息機制Hander作用?有哪些要素?流程是怎樣的? 6.0.0.2 為什么一個線程只有一個Looper、只有一個MessageQueue,可以有多個Handle...

    Aomine 評論0 收藏0
  • Handler 使用詳解

    摘要:機制處理的個關鍵對象線程之間傳遞的消息,可以攜帶一些簡單的數據供子線程與主線程進行交換數據。解決方法子線程通過發送消息給主線程,讓主線程處理消息,進而更新。 showImg(https://segmentfault.com/img/remote/1460000019975019?w=157&h=54); 極力推薦文章:歡迎收藏Android 干貨分享 showImg(https://...

    sihai 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<