事件迴圈系統的設計和實現

2021-10-19 14:11:48 字數 3238 閱讀 4619

事件迴圈相信大家都不陌生,很多同學都知道事件迴圈是乙個"死迴圈",今天我們看一下這個死迴圈到底是怎樣的。我們先看乙個樸素版的事件迴圈系統。

class

eventsystem ​

// 追加任務

enqueue

(func)

​ // 事件迴圈

run()}

}}// 新建乙個事件迴圈系統

const eventsystem =

neweventsystem()

;// 生產任務

eventsystem.

enqueue((

)=>);

// 啟動事件迴圈

eventsystem.

run(

);

以上**實現了乙個非常樸素的事件迴圈系統

1 新建乙個事件迴圈系統

2 生產任務

3 啟動事件迴圈系統

但是我們發現當沒有任務的時候,事件迴圈系統陷入了死迴圈,這無疑浪費了cpu。我們看一下執行以上**的cpu的情況(我電腦4核,可以看到以上**對應的程序幾乎完全佔據了乙個cpu,1/4)。

接著我們優化一下這個樸素版的事件迴圈。

class

eventsystem

// 沒有任務時,事件迴圈的睡眠時間

sleep

(time)

; timer =

settimeout((

)=>

}, time);}

);}// 停止事件迴圈

setstop()

​ // 追加任務

enqueue

(func)

​ // 事件迴圈

async run()

// 沒有任務了,一直等待(math.pow(2, 31) - 1為nodejs中定時器的最大值)

await this

.sleep

(math.

pow(2,

31)-1

);}}

}// 新建乙個事件迴圈系統

const eventsystem =

neweventsystem()

;// 生產任務

eventsystem.

enqueue((

)=>);

// 模擬定時生成乙個任務

settimeout((

)=>);

},1000);

// 模擬退出事件迴圈

settimeout((

)=>

,2000);

// 啟動事件迴圈

eventsystem.

run(

);

上面**的執行結果如下

1 啟動事件迴圈時輸出hi。

2 事件迴圈進入睡眠,1s時被喚醒,輸出hello。

3 2s後退出事件迴圈。

麻雀雖小五臟俱全,以上**雖然只是個demo,但是已經具備了事件迴圈的一些核心概念。

1 事件迴圈的整體架構是乙個while迴圈

2 定義任務型別和佇列,這裡只有一種任務型別和乙個佇列,比如nodejs裡有好幾種。

3 沒有任務的時候怎麼處理?進入睡眠,而不是真的是乙個死迴圈。

其中第3點是事件迴圈系統中非常重要的邏輯。因為事件迴圈是屬於生產者、消費者模式。任務佇列中不可能一直都有任務需要處理,這就意味著生產任務可以是乙個非同步的過程。所以事件迴圈系統就需要有一種等待的機制。這就會帶來兩個問題,什麼情況下需要等待,什麼時候需要退出。這個和具體的業務場景有關,本文實現的事件迴圈中,沒有任務的時候就會一直等待,而不是退出。除非使用者手動執行setstop退出。而nodejs中,如果沒有actived狀態的handle和request並且close階段沒有任務時就會自動退出。另外乙個問題就是如何實現等待。這裡使用的是settimeout來模擬睡眠,從而達到等待的效果。但是這時候程序是沒有被掛起的,這意味著,我們還可以做其他事情。而在nodejs中,會在poll io階段,程序會被掛起。我們看看nodejs事件迴圈的實現。

while

(r !=

0&& loop-

>stop_flag ==0)

nodejs的事件迴圈也是乙個while迴圈,然後在裡面執行各個階段的任務,其中uv__io_poll對應的poll io階段可能會導致程序掛起。我們看一下uv__io_poll關於等待的邏輯。

nfds =

epoll_wait

(loop-

>backend_fd,

events,

array_size

(events)

, timeout)

;

epoll_wait會根據timeout的值決定如果沒有就緒事件時,是否需要掛起程序。timeout大於0說明是定時器掛起,timeout等於-1說明是永遠掛起。直到有就緒佇列。這就是nodejs中關於等待的處理邏輯。這和我們自己實現的事件迴圈系統是類似的,只不過我們是自己喚醒自己,而nodejs中是被作業系統喚醒,因為我們在js層面無法呼叫作業系統的系統呼叫掛起程序。epoll是和檔案描述符相關的,如果我們不涉及到檔案、網路操作,那麼我們又如何實現等待呢?我們從libuv的執行緒池實現中,找到了另一種實現。libuv的執行緒池中有多個執行緒,他們共享乙個任務佇列,每個子執行緒裡不斷從共享的任務佇列中獲取任務處理(需要加鎖)。所以這也是乙個事件迴圈的模型。那麼當沒有任務可處理的時候,libuv是如何實現等待的呢?

static

void

worker

(void

* arg)}}

我們看到libuv使用執行緒庫提供的api實現了執行緒的掛起。從而實現了等待的邏輯。接下來我們看一下喚醒的邏輯。

static

void

post

(queue* q,

enum uv__work_kind kind)

libuv每次提交新的任務到共享佇列時,都會判斷是否有空閒執行緒,如果有則喚醒他。

本文介紹了事件迴圈的設計和實現中涉及到的一些知識,我們看到事件迴圈的整體架構是類似的,但是具體實現有很多種方式,這取決於你的業務場景。同時,任務的生產是非同步的,所以沒有任務的時候的等待機制的設計也就變得很重要,我們不能不斷地浪費cpu進行輪詢,而是要借助一直掛起,喚醒的機制來實現。

外幣卡收單系統的設計和實現

隨著我國對外交往的不斷擴大,到我國旅遊和從事商務活動的外籍人士將越來越多,這對我國的銀行卡受理環境提出了更高的要求。另外,只有改善外幣卡受理環境,積極參與國際業務,才能積累豐富的國際金融業務經驗,進一步提高金融服務水平。因此,商業銀行提高對外幣卡的受理能力變得愈加重要,開發 建設功能強大的外幣卡收單...

外幣卡收單系統的設計和實現

隨著我國對外交往的不斷擴大,到我國旅遊和從事商務活動的外籍人士將越來越多,這對我國的銀行卡受理環境提出了更高的要求。另外,只有改善外幣卡受理環境,積極參與國際業務,才能積累豐富的國際金融業務經驗,進一步提高金融服務水平。因此,商業銀行提高對外幣卡的受理能力變得愈加重要,開發 建設功能強大的外幣卡收單...

實現範例的Observer設計模式 事件 委託

上面的例子已不足以再進行下面的講解了,我們來看乙個新的範例,因為之前已經介紹了很多的內容,所以本節的進度會稍微快一些 假設我們有個高檔的熱水器,我們給它通上電,當水溫超過95度的時候 1 揚聲器會開始發出語音,告訴你水的溫度 2 液晶屏也會改變水溫的顯示,來提示水已經快燒開了。現在我們需要寫個程式來...