溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

高性能服務(wù)器開發(fā)基礎(chǔ)系列 (一)主線程與工作線程的分工

發(fā)布時間:2020-07-21 09:20:12 來源:網(wǎng)絡(luò) 閱讀:882 作者:張小方32 欄目:建站服務(wù)器

服務(wù)器端為了能流暢處理多個客戶端鏈接,一般在某個線程A里面accept新的客戶端連接并生成新連接的socket fd,然后將這些新連接的socketfd給另外開的數(shù)個工作線程B1、B2、B3、B4,這些工作線程處理這些新連接上的網(wǎng)絡(luò)IO事件(即收發(fā)數(shù)據(jù)),同時,還處理系統(tǒng)中的另外一些事務(wù)。這里我們將線程A稱為主線程,B1、B2、B3、B4等稱為工作線程。工作線程的代碼框架一般如下:

while (!m_bQuit)  {
    epoll_or_select_func();

    handle_io_events();

    handle_other_things();
}

epoll_or_select_func()中通過select()或者poll/epoll()去檢測socket fd上的io事件,若存在這些事件則下一步handle_io_events()來處理這些事件(收發(fā)數(shù)據(jù)),做完之后可能還要做一些系統(tǒng)其他的任務(wù),即調(diào)用handle_other_things()。

這樣做有三個好處:

線程A只需要處理新連接的到來即可,不用處理網(wǎng)絡(luò)IO事件。由于網(wǎng)絡(luò)IO事件處理一般相對比較慢,如果在線程A里面既處理新連接又處理網(wǎng)絡(luò)IO,則可能由于線程忙于處理IO事件,而無法及時處理客戶端的新連接,這是很不好的。

線程A接收的新連接,可以根據(jù)一定的負載均衡原則將新的socket fd分配給工作線程。常用的算法,比如round robin,即輪詢機制,即,假設(shè)不考慮中途有連接斷開的情況,一個新連接來了分配給B1,又來一個分配給B2,再來一個分配給B3,再來一個分配給B4。如此反復(fù),也就是說線程A記錄了各個工作線程上的socket fd數(shù)量,這樣可以最大化地來平衡資源,避免一些工作線程“忙死”,另外一些工作線程“閑死”的現(xiàn)象。

即使工作線程不滿載的情況下,也可以讓工作線程做其他的事情。比如現(xiàn)在有四個工作線程,但只有三個連接。那么線程B4就可以在handle_other_thing()做一些其他事情。

下面討論一個很重要的效率問題:

在上述while循環(huán)里面,epoll_or_selec_func()中的epoll_wait/poll/select等函數(shù)一般設(shè)置了一個超時時間。如果設(shè)置超時時間為0,那么在沒有任何網(wǎng)絡(luò)IO時間和其他任務(wù)處理的情況下,這些工作線程實際上會空轉(zhuǎn),白白地浪費cpu時間片。如果設(shè)置的超時時間大于0,在沒有網(wǎng)絡(luò)IO時間的情況,epoll_wait/poll/select仍然要掛起指定時間才能返回,導(dǎo)致handle_other_thing()不能及時執(zhí)行,影響其他任務(wù)不能及時處理,也就是說其他任務(wù)一旦產(chǎn)生,其處理起來具有一定的延時性。這樣也不好。

那如何解決該問題呢?

其實我們想達到的效果是,如果沒有網(wǎng)絡(luò)IO時間和其他任務(wù)要處理,那么這些工作線程最好直接掛起而不是空轉(zhuǎn);如果有其他任務(wù)要處理,這些工作線程要立刻能處理這些任務(wù)而不是在epoll_wait/poll/selec掛起指定時間后才開始處理這些任務(wù)。

我們采取如下方法來解決該問題,以linux為例,不管epoll_fd上有沒有文件描述符fd,我們都給它綁定一個默認的fd,這個fd被稱為喚醒fd。當我們需要處理其他任務(wù)的時候,向這個喚醒fd上隨便寫入1個字節(jié)的,這樣這個fd立即就變成可讀的了,epoll_wait()/poll()/select()函數(shù)立即被喚醒,并返回,接下來馬上就能執(zhí)行handle_other_thing(),其他任務(wù)得到處理。反之,沒有其他任務(wù)也沒有網(wǎng)絡(luò)IO事件時,epoll_or_select_func()就掛在那里什么也不做。

這個喚醒fd,在linux平臺上可以通過以下幾種方法實現(xiàn):

管道pipe,創(chuàng)建一個管道,將管道綁定到epoll_fd上。需要時,向管道一端寫入一個字節(jié),工作線程立即被喚醒。

linux 2.6新增的eventfd:

int eventfd(unsigned int initval, int flags); 

步驟也是一樣,將生成的eventfd綁定到epoll_fd上。需要時,向這個 eventfd上寫入一個字節(jié),工作線程立即被喚醒。

第三種方法最方便。即linux特有的socketpair,socketpair是一對相互連接的socket,相當于服務(wù)器端和客戶端的兩個端點,每一端都可以讀寫數(shù)據(jù)。

int socketpair(int domain, int type, int protocol, int sv[2]);

調(diào)用這個函數(shù)返回的兩個socket句柄就是sv[0],和sv[1],在一個其中任何一個寫入字節(jié),在另外一個收取字節(jié)。

將收取的字節(jié)的socket綁定到epoll_fd上。需要時,向另外一個寫入的socket上寫入一個字節(jié),工作線程立即被喚醒。

如果是使用socketpair,那么domain參數(shù)一定要設(shè)置成AFX_UNIX。

由于在windows,select函數(shù)只支持檢測socket這一種fd,所以windows上一般只能用方法3的原理。而且需要手動創(chuàng)建兩個socket,然后一個連接另外一個,將讀取的那一段綁定到select的fd上去。這在寫跨兩個平臺代碼時,需要注意的地方。

歡迎關(guān)注公眾號『easyserverdev』。如果有任何技術(shù)或者職業(yè)方面的問題需要我提供幫助,可通過這個公眾號與我取得聯(lián)系,此公眾號不僅分享高性能服務(wù)器開發(fā)經(jīng)驗和故事,同時也免費為廣大技術(shù)朋友提供技術(shù)答疑和職業(yè)解惑,您有任何問題都可以在微信公眾號直接留言,我會盡快回復(fù)您。
高性能服務(wù)器開發(fā)基礎(chǔ)系列 (一)主線程與工作線程的分工

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI