新聞中心
隨著互聯(lián)網(wǎng)應(yīng)用的不斷發(fā)展,網(wǎng)絡(luò)通信技術(shù)已成為我們生活和工作中不可或缺的部分。為了提高網(wǎng)絡(luò)連接效率,Linux中常常通過一些技巧來實(shí)現(xiàn)TCP短鏈接。本文將介紹如何在Linux系統(tǒng)中實(shí)現(xiàn)TCP短鏈接,并提高網(wǎng)絡(luò)連接效率。

10多年的溫嶺網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。網(wǎng)絡(luò)營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整溫嶺建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)公司從事“溫嶺網(wǎng)站設(shè)計(jì)”,“溫嶺網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
一、TCP半鏈接狀態(tài)
在介紹TCP短鏈接技巧之前,我們需要先了解一下TCP半鏈接狀態(tài)。當(dāng)客戶端向服務(wù)器端發(fā)送SYN包時(shí),服務(wù)器會(huì)回應(yīng)一個(gè)SYN/ACK包,表示接受請求,并等待客戶端發(fā)送ACK包以確認(rèn)建立連接。在這個(gè)過程中,服務(wù)器端會(huì)處于半鏈接狀態(tài),因?yàn)樗呀?jīng)接受了一個(gè)新的鏈接,但還沒有完全建立起來。
如果客戶端在接受到服務(wù)器端的SYN/ACK包前中止了連接,那么該半鏈接將留在服務(wù)器端,不能及時(shí)釋放,從而浪費(fèi)服務(wù)器端資源。這種情況下,服務(wù)器會(huì)一直等到這個(gè)連接超時(shí)才會(huì)釋放,導(dǎo)致連接效率降低。因此,我們需要一些技巧來處理這種情況。
二、TCP Keepalive技巧
TCP Keepalive是一個(gè)用于檢測TCP連接是否活動(dòng)的機(jī)制。當(dāng)某個(gè)連接在一段時(shí)間內(nèi)沒有活動(dòng),Keepalive機(jī)制會(huì)發(fā)送一些特殊的探測包來檢查連接是否仍然存在。如果連接已經(jīng)中斷,那么服務(wù)器將及時(shí)釋放資源,從而提高連接效率。
在Linux中,可以通過以下命令設(shè)置TCP Keepalive機(jī)制:
“`
# 命令格式
echo seconds > /proc/sys/net/ipv4/tcp_keepalive_time
echo tries > /proc/sys/net/ipv4/tcp_keepalive_probes
echo seconds > /proc/sys/net/ipv4/tcp_keepalive_intvl
# 參數(shù)解釋
# seconds:表示開始發(fā)送TCP Keepalive包的時(shí)間間隔(單位為秒),默認(rèn)為7200秒(2小時(shí))。
# tries:表示在發(fā)送之一個(gè)TCP Keepalive包前嘗試多少次發(fā)送數(shù)據(jù)包,默認(rèn)為9次。
# seconds:表示在發(fā)送TCP Keepalive包后多長時(shí)間發(fā)送下一個(gè)包(單位為秒),默認(rèn)為75秒。
“`
通過設(shè)置TCP Keepalive機(jī)制,可以讓服務(wù)器端及時(shí)釋放TCP半鏈接狀態(tài),從而提高網(wǎng)絡(luò)連接效率。
三、TCP Fastopen技巧
TCP Fastopen是一種新的TCP協(xié)議擴(kuò)展,它可以在之一次握手時(shí)傳送數(shù)據(jù),從而避免建立鏈接的延遲。在TCP Fastopen機(jī)制下,服務(wù)器端可以在之一次握手時(shí)向客戶端發(fā)送一些數(shù)據(jù),而不需要等待客戶端發(fā)送ACK包。
在Linux中,可以通過以下命令開啟TCP Fastopen機(jī)制:
“`
# 命令格式
sysctl -w net.ipv4.tcp_fastopen=1
“`
啟用TCP Fastopen機(jī)制后,可以通過一些技巧來利用它提高網(wǎng)絡(luò)連接效率。例如,可以將網(wǎng)站的靜態(tài)資源(例如圖片、CSS、JavaScript等)放在一個(gè)單獨(dú)的域名下,然后將該域名的TCP Fastopen機(jī)制開啟,從而加速加載速度。
四、TCP Time_wt優(yōu)化技巧
TCP Time_wt是一個(gè)用于保持連接狀態(tài)的機(jī)制,它確保當(dāng)連接中的最后一個(gè)數(shù)據(jù)包丟失時(shí)服務(wù)器不會(huì)立即釋放連接,從而避免數(shù)據(jù)包丟失。然而,如果連接量過大,Time_wt機(jī)制會(huì)占用服務(wù)器端的大量資源,從而使連接效率降低。
在Linux中,可以通過以下技巧來優(yōu)化TCP Time_wt機(jī)制:
(1)調(diào)整Time_wt狀態(tài)的更大數(shù)量
通過調(diào)整Time_wt狀態(tài)的更大數(shù)量,可以控制服務(wù)器端的資源占用情況。在Linux中,可以通過以下命令調(diào)整Time_wt狀態(tài)的更大數(shù)量:
“`
# 命令格式
echo value > /proc/sys/net/ipv4/tcp_max_tw_buckets
# 參數(shù)解釋
# value:表示更大的Time_wt狀態(tài)數(shù)量,默認(rèn)為180000。
“`
(2)開啟TCP Long Time_wt
TCP Long Time_wt是一種可以延長Time_wt狀態(tài)的機(jī)制,它可以在超時(shí)時(shí)間為2倍的MSL(Maximum Segment Lifetime,更大報(bào)文壽命)時(shí)關(guān)閉TCP連接。在Linux中,可以通過以下命令開啟TCP Long Time_wt機(jī)制:
“`
# 命令格式
echo seconds > /proc/sys/net/ipv4/tcp_tw_reuse
# 參數(shù)解釋
# seconds:表示最長的TCP Long Time_wt時(shí)間,默認(rèn)為0。
“`
通過以上技巧,可以優(yōu)化TCP Time_wt機(jī)制,從而提高網(wǎng)絡(luò)連接效率。
五、
通過以上介紹,我們可以了解Linux中如何實(shí)現(xiàn)TCP短鏈接技巧,從而提高網(wǎng)絡(luò)連接效率。通過采用TCP Keepalive、TCP Fastopen和TCP Time_wt優(yōu)化技巧,可以讓服務(wù)器端及時(shí)釋放TCP半鏈接狀態(tài),避免TCP連接過程中的延遲,提高網(wǎng)絡(luò)連接效率,為互聯(lián)網(wǎng)應(yīng)用的發(fā)展提供更好的支持。
相關(guān)問題拓展閱讀:
- 如何看懂《Linux多線程服務(wù)端編程
如何看懂《Linux多線程服務(wù)端編程
一:進(jìn)程和線程
每個(gè)進(jìn)程有自己獨(dú)立的地址空間?!霸谕粋€(gè)進(jìn)程”還是“不在同一個(gè)進(jìn)程”是系統(tǒng)功能劃分的重要決策點(diǎn)?!禘rlang程序設(shè)計(jì)》把進(jìn)程比喻為人:
每個(gè)人有自己的記憶(內(nèi)存),人與人通過談話(消息傳遞)來交流,談話既可以是面談姿并野(同一臺服務(wù)器),也可以在里談(不同的服務(wù)器,有網(wǎng)絡(luò)通信)。面談和談的區(qū)別在于,面談可以立即知道對方是否死了(crash,SIGCHLD),而談只能通過周期性的心跳來判斷對方是否還活著。
有了這些比喻,設(shè)計(jì)分布式系統(tǒng)時(shí)可以采取“角色扮演”,團(tuán)隊(duì)里的幾個(gè)人各自扮演一個(gè)進(jìn)程,人的角色由進(jìn)程的代碼決定(管登錄的、管消息分發(fā)的、管買賣的等等)。每個(gè)人有自己的記憶,但不知道別人的記憶,要想知道別人的看法,只能通過交談(暫不考慮共享內(nèi)存這種IPC)。然后就可以思考:跡喊
·容錯(cuò):萬一有人突然死了
·擴(kuò)容:新人中途加進(jìn)來
·負(fù)載均衡:把甲的活兒挪給乙做
·退休:甲要修復(fù)bug,先別派新任務(wù),等他做完手上的事情就把他重啟
等等各種場景,十分便利。
線程的特點(diǎn)是共享地址空間,從而可以高效地共享數(shù)據(jù)。一臺機(jī)器上的多個(gè)進(jìn)程能高效地共享代碼段(操作系統(tǒng)可以映射為同樣的物理內(nèi)存),但不能共享數(shù)據(jù)。如果多個(gè)進(jìn)程大量共享內(nèi)存,等于是把多進(jìn)程程序當(dāng)成多線程來寫,掩耳盜鈴。
“多線程”的價(jià)值,我認(rèn)為是為了更好地發(fā)揮多核處理器(multi-cores)的效能。在單核時(shí)代,多線程沒有多大價(jià)值(個(gè)人想法:如果要完成的任務(wù)是CPU密集型的,那多線程沒有優(yōu)勢,甚至因?yàn)榫€程切換的開銷,多線程反而更慢;如果要完成的任務(wù)既有CPU計(jì)算,又有磁盤或網(wǎng)絡(luò)IO,則使用多線程的好處是,當(dāng)某個(gè)線程因?yàn)镮O而阻塞時(shí),OS可以調(diào)度其他線程執(zhí)行,雖然效率確實(shí)要比任務(wù)的順序執(zhí)行效率要高,然而,這種類型的任務(wù),可以通過單線程的”non-blocking IO+IO multiplexing”的模型(事件驅(qū)動(dòng))來提高效率,采用多線程的方式,帶來的可能僅僅是編程上的簡單而已)。Alan Cox說過:”A computer is a state machine.Threads are for people who can’t program state machines.”(計(jì)算機(jī)是一臺狀態(tài)機(jī)。線程是給那些不能編寫狀態(tài)機(jī)程序的人準(zhǔn)備的)如果只有一塊CPU、一個(gè)執(zhí)行單元,那么確實(shí)如Alan Cox所說,按狀態(tài)機(jī)的思路去寫程序是最蔽謹(jǐn)高效的。
二:單線程服務(wù)器的常用編程模型
據(jù)我了解,在高性能的網(wǎng)絡(luò)程序中,使用得最為廣泛的恐怕要數(shù)”non-blocking IO + IO multiplexing”這種模型,即Reactor模式。
在”non-blocking IO + IO multiplexing”這種模型中,程序的基本結(jié)構(gòu)是一個(gè)事件循環(huán)(event loop),以事件驅(qū)動(dòng)(event-driven)和事件回調(diào)的方式實(shí)現(xiàn)業(yè)務(wù)邏輯:
view plain copy
//代碼僅為示意,沒有完整考慮各種情況
while(!done)
{
int timeout_ms = max(1000, getNextTimedCallback());
int retval = poll(fds, nfds, timeout_ms);
if (retval0){
處理IO事件,回調(diào)用戶的IO event handler
}
}
}
這里select(2)/poll(2)有伸縮性方面的不足(描述符過多時(shí),效率較低),Linux下可替換為epoll(4),其他操作系統(tǒng)也有對應(yīng)的高性能替代品。
Reactor模型的優(yōu)點(diǎn)很明顯,編程不難,效率也不錯(cuò)。不僅可以用于讀寫socket,連接的建立(connect(2)/accept(2)),甚至DNS解析都可以用非阻塞方式進(jìn)行,以提高并發(fā)度和吞吐量(throughput),對于IO密集的應(yīng)用是個(gè)不錯(cuò)的選擇。lighttpd就是這樣,它內(nèi)部的fdevent結(jié)構(gòu)十分精妙,值得學(xué)習(xí)。
基于事件驅(qū)動(dòng)的編程模型也有其本質(zhì)的缺點(diǎn),它要求事件回調(diào)函數(shù)必須是非阻塞的。對于涉及網(wǎng)絡(luò)IO的請求響應(yīng)式協(xié)議,它容易割裂業(yè)務(wù)邏輯,使其散布于多個(gè)回調(diào)函數(shù)之中,相對不容易理解和維護(hù)。
三:多線程服務(wù)器的常用編程模型
大概有這么幾種:
a:每個(gè)請求創(chuàng)建一個(gè)線程,使用阻塞式IO操作。在Java 1.4引人NIO之前,這是Java網(wǎng)絡(luò)編程的推薦做法??上炜s性不佳(請求太多時(shí),操作系統(tǒng)創(chuàng)建不了這許多線程)。
b:使用線程池,同樣使用阻塞式IO操作。與第1種相比,這是提高性能的措施。
c:使用non-blocking IO + IO multiplexing。即Java NIO的方式。
d:Leader/Follower等高級模式。
在默認(rèn)情況下,我會(huì)使用第3種,即non-blocking IO + one loop per thread模式來編寫多線程C++網(wǎng)絡(luò)服務(wù)程序。
1:one loop per thread
此種模型下,程序里的每個(gè)IO線程有一個(gè)event loop,用于處理讀寫和定時(shí)事件(無論周期性的還是單次的)。代碼框架跟“單線程服務(wù)器的常用編程模型”一節(jié)中的一樣。
libev的作者說:
One loop per thread is usually a good model. Doing this is almost never wrong, some times a better-performance model exists, but it is always a good start.
這種方式的好處是:
a:線程數(shù)目基本固定,可以在程序啟動(dòng)的時(shí)候設(shè)置,不會(huì)頻繁創(chuàng)建與銷毀。
b:可以很方便地在線程間調(diào)配負(fù)載。
c:IO事件發(fā)生的線程是固定的,同一個(gè)TCP連接不必考慮事件并發(fā)。
Event loop代表了線程的主循環(huán),需要讓哪個(gè)線程干活,就把timer或IO channel(如TCP連接)注冊到哪個(gè)線程的loop里即可:對實(shí)時(shí)性有要求的connection可以單獨(dú)用一個(gè)線程;數(shù)據(jù)量大的connection可以獨(dú)占一個(gè)線程,并把數(shù)據(jù)處理任務(wù)分?jǐn)偟搅韼讉€(gè)計(jì)算線程中(用線程池);其他次要的輔助性connections可以共享一個(gè)線程。
比如,在dbproxy中,一個(gè)線程用于專門處理客戶端發(fā)來的管理命令;一個(gè)線程用于處理客戶端發(fā)來的MySQL命令,而與后端數(shù)據(jù)庫通信執(zhí)行該命令時(shí),是將該任務(wù)分配給所有事件線程處理的。
對于non-trivial(有一定規(guī)模)的服務(wù)端程序,一般會(huì)采用non-blocking IO + IO multiplexing,每個(gè)connection/acceptor都會(huì)注冊到某個(gè)event loop上,程序里有多個(gè)event loop,每個(gè)線程至多有一個(gè)event loop。
多線程程序?qū)vent loop提出了更高的要求,那就是“線程安全”。要允許一個(gè)線程往別的線程的loop里塞東西,這個(gè)loop必須得是線程安全的。
在dbproxy中,線程向其他線程分發(fā)任務(wù),是通過管道和隊(duì)列實(shí)現(xiàn)的。比如主線程accept到連接后,將表示該連接的結(jié)構(gòu)放入隊(duì)列,并向管道中寫入一個(gè)字節(jié)。計(jì)算線程在自己的event loop中注冊管道的讀事件,一旦有數(shù)據(jù)可讀,就嘗試從隊(duì)列中取任務(wù)。
2:線程池
不過,對于沒有IO而光有計(jì)算任務(wù)的線程,使用event loop有點(diǎn)浪費(fèi)??梢允褂靡环N補(bǔ)充方案,即用blocking queue實(shí)現(xiàn)的任務(wù)隊(duì)列:
view plain copy
typedef boost::functionFunctor;
BlockingQueue taskQueue; //線程安全的全局阻塞隊(duì)列
//計(jì)算線程
void workerThread()
{
while (running) //running變量是個(gè)全局標(biāo)志
{
Functor task = taskQueue.take(); //this blocks
task(); //在產(chǎn)品代碼中需要考慮異常處理
}
}
// 創(chuàng)建容量(并發(fā)數(shù))為N的線程池
int N = num_of_computing_threads;
for (int i = 0; i
{
create_thread(&workerThread); //啟動(dòng)線程
}
//向任務(wù)隊(duì)列中追加任務(wù)
Foo foo; //Foo有calc()成員函數(shù)
boost::function task = boost::bind(&Foo::calc,&foo);
taskQueue.post(task);
除了任務(wù)隊(duì)列,還可以用BlockingQueue實(shí)現(xiàn)數(shù)據(jù)的生產(chǎn)者消費(fèi)者隊(duì)列,即T是數(shù)據(jù)類型而非函數(shù)對象,queue的消費(fèi)者從中拿到數(shù)據(jù)進(jìn)行處理。其實(shí)本質(zhì)上是一樣的。
3:總結(jié)
總結(jié)而言,我推薦的C++多線程服務(wù)端編程模式為:one (event) loop per thread + thread pool:
event loop用作IO multiplexing,配合non-blockingIO和定時(shí)器;
thread pool用來做計(jì)算,具體可以是任務(wù)隊(duì)列或生產(chǎn)者消費(fèi)者隊(duì)列。
以這種方式寫服務(wù)器程序,需要一個(gè)優(yōu)質(zhì)的基于Reactor模式的網(wǎng)絡(luò)庫來支撐,muduo正是這樣的網(wǎng)絡(luò)庫。比如dbproxy使用的是libevent。
程序里具體用幾個(gè)loop、線程池的大小等參數(shù)需要根據(jù)應(yīng)用來設(shè)定,基本的原則是“阻抗匹配”(解釋見下),使得CPU和IO都能高效地運(yùn)作。所謂阻抗匹配原則:
如果池中線程在執(zhí)行任務(wù)時(shí),密集計(jì)算所占的時(shí)間比重為 P (0
以后我再講這個(gè)經(jīng)驗(yàn)公式是怎么來的,先驗(yàn)證邊界條件的正確性。
假設(shè) C = 8,P = 1.0,線程池的任務(wù)完全是密集計(jì)算,那么T = 8。只要 8 個(gè)活動(dòng)線程就能讓 8 個(gè) CPU 飽和,再多也沒用,因?yàn)?CPU 資源已經(jīng)耗光了。
假設(shè) C = 8,P = 0.5,線程池的任務(wù)有一半是計(jì)算,有一半等在 IO 上,那么T = 16??紤]操作系統(tǒng)能靈活合理地調(diào)度 sleeping/writing/running 線程,那么大概 16 個(gè)“50%繁忙的線程”能讓 8 個(gè) CPU 忙個(gè)不停。啟動(dòng)更多的線程并不能提高吞吐量,反而因?yàn)樵黾由舷挛那袚Q的開銷而降低性能。
如果 P
另外,公式里的 C 不一定是 CPU 總數(shù),可以是“分配給這項(xiàng)任務(wù)的 CPU 數(shù)目”,比如在 8 核機(jī)器上分出 4 個(gè)核來做一項(xiàng)任務(wù),那么 C=4。
四:進(jìn)程間通信只用TCP
Linux下進(jìn)程間通信的方式有:匿名管道(pipe)、具名管道(FIFO)、POSIX消息隊(duì)列、共享內(nèi)存、信號(signals),以及Socket。同步原語有互斥器(mutex)、條件變量(condition variable)、讀寫鎖(reader-writer lock)、文件鎖(record locking)、信號量(semaphore)等等。
進(jìn)程間通信我首選Sockets(主要指TCP,我沒有用過UDP,也不考慮Unix domain協(xié)議)。其好處在于:
可以跨主機(jī),具有伸縮性。反正都是多進(jìn)程了,如果一臺機(jī)器的處理能力不夠,很自然地就能用多臺機(jī)器來處理。把進(jìn)程分散到同一局域網(wǎng)的多臺機(jī)器上,程序改改host:port配置就能繼續(xù)用;
TCP sockets和pipe都是操作文件描述符,用來收發(fā)字節(jié)流,都可以read/write/fcntl/select/poll等。不同的是,TCP是雙向的,Linux的pipe是單向的,進(jìn)程間雙向通信還得開兩個(gè)文件描述符,不方便;而且進(jìn)程要有父子關(guān)系才能用pipe,這些都限制了pipe的使用;
TCP port由一個(gè)進(jìn)程獨(dú)占,且進(jìn)程退出時(shí)操作系統(tǒng)會(huì)自動(dòng)回收文件描述符。因此即使程序意外退出,也不會(huì)給系統(tǒng)留下垃圾,程序重啟之后能比較容易地恢復(fù),而不需要重啟操作系統(tǒng)(用跨進(jìn)程的mutex就有這個(gè)風(fēng)險(xiǎn));而且,port是獨(dú)占的,可以防止程序重復(fù)啟動(dòng),后面那個(gè)進(jìn)程搶不到port,自然就沒法初始化了,避免造成意料之外的結(jié)果;
與其他IPC相比,TCP協(xié)議的一個(gè)天生的好處是“可記錄、可重現(xiàn)”。tcpdump和Wireshark是解決兩個(gè)進(jìn)程間協(xié)議和狀態(tài)爭端的好幫手,也是性能(吞吐量、延遲)分析的利器。我們可以借此編寫分布式程序的自動(dòng)化回歸測試。也可以用tcpcopy之類的工具進(jìn)行壓力測試。TCP還能跨語言,服務(wù)端和客戶端不必使用同一種語言。
分布式系統(tǒng)的軟件設(shè)計(jì)和功能劃分一般應(yīng)該以“進(jìn)程”為單位。從宏觀上看,一個(gè)分布式系統(tǒng)是由運(yùn)行在多臺機(jī)器上的多個(gè)進(jìn)程組成的,進(jìn)程之間采用TCP長連接通信。
使用TCP長連接的好處有兩點(diǎn):一是容易定位分布式系統(tǒng)中的服務(wù)之間的依賴關(guān)系。只要在機(jī)器上運(yùn)行netstat -tpna|grep 就能立刻列出用到某服務(wù)的客戶端地址(Foreign Address列),然后在客戶端的機(jī)器上用netstat或lsof命令找出是哪個(gè)進(jìn)程發(fā)起的連接。TCP短連接和UDP則不具備這一特性。二是通過接收和發(fā)送隊(duì)列的長度也較容易定位網(wǎng)絡(luò)或程序故障。在正常運(yùn)行的時(shí)候,netstat打印的Recv-Q和Send-Q都應(yīng)該接近0,或者在0附近擺動(dòng)。如果Recv-Q保持不變或持續(xù)增加,則通常意味著服務(wù)進(jìn)程的處理速度變慢,可能發(fā)生了死鎖或阻塞。如果Send-Q保持不變或持續(xù)增加,有可能是對方服務(wù)器太忙、來不及處理,也有可能是網(wǎng)絡(luò)中間某個(gè)路由器或交換機(jī)故障造成丟包,甚至對方服務(wù)器掉線,這些因素都可能表現(xiàn)為數(shù)據(jù)發(fā)送不出去。通過持續(xù)監(jiān)控Recv-Q和Send-Q就能及早預(yù)警性能或可用性故障。以下是服務(wù)端線程阻塞造成Recv-Q和客戶端Send-Q激增的例子:
view plain copy
$netstat -tn
Proto Recv-Q Send-Q Local Address Foreign
tcp.0.0.10:.0.0.10:39748 #服務(wù)端連接
tcp.0.0.10:.0.0.10:2023 #客戶端連接
tcp.0.0.10:22 10.0.0.4:55572
五:多線程服務(wù)器的適用場合
如果要在一臺多核機(jī)器上提供一種服務(wù)或執(zhí)行一個(gè)任務(wù),可用的模式有:
a:運(yùn)行一個(gè)單線程的進(jìn)程;
b:運(yùn)行一個(gè)多線程的進(jìn)程;
c:運(yùn)行多個(gè)單線程的進(jìn)程;
d:運(yùn)行多個(gè)多線程的進(jìn)程;
考慮這樣的場景:如果使用速率為50MB/s的數(shù)據(jù)壓縮庫,進(jìn)程創(chuàng)建銷毀的開銷是800微秒,線程創(chuàng)建銷毀的開銷是50微秒。如何執(zhí)行壓縮任務(wù)?
如果要偶爾壓縮1GB的文本文件,預(yù)計(jì)運(yùn)行時(shí)間是20s,那么起一個(gè)進(jìn)程去做是合理的,因?yàn)檫M(jìn)程啟動(dòng)和銷毀的開銷遠(yuǎn)遠(yuǎn)小于實(shí)際任務(wù)的耗時(shí)。
如果要經(jīng)常壓縮500kB的文本數(shù)據(jù),預(yù)計(jì)運(yùn)行時(shí)間是10ms,那么每次都起進(jìn)程 似乎有點(diǎn)浪費(fèi)了,可以每次單獨(dú)起一個(gè)線程去做。
如果要頻繁壓縮10kB的文本數(shù)據(jù),預(yù)計(jì)運(yùn)行時(shí)間是200微秒,那么每次起線程似 乎也很浪費(fèi),不如直接在當(dāng)前線程搞定。也可以用一個(gè)線程池,每次把壓縮任務(wù)交給線程池,避免阻塞當(dāng)前線程(特別要避免阻塞IO線程)。
由此可見,多線程并不是萬靈丹(silver bullet)。
1:必須使用單線程的場合
據(jù)我所知,有兩種場合必須使用單線程:
a:程序可能會(huì)fork(2);
實(shí)際編程中,應(yīng)該保證只有單線程程序能進(jìn)行fork(2)。多線程程序不是不能調(diào)用fork(2),而是這么做會(huì)遇到很多麻煩:
fork一般不能在多線程程序中調(diào)用,因?yàn)長inux的fork只克隆當(dāng)前線程的thread of control,不可隆其他線程。fork之后,除了當(dāng)前線程之外,其他線程都消失了。
這就造成一種危險(xiǎn)的局面。其他線程可能正好處于臨界區(qū)之內(nèi),持有了某個(gè)鎖,而它突然死亡,再也沒有機(jī)會(huì)去解鎖了。此時(shí)如果子進(jìn)程試圖再對同一個(gè)mutex加鎖,就會(huì)立即死鎖。因此,fork之后,子進(jìn)程就相當(dāng)于處于signal handler之中(因?yàn)椴恢勒{(diào)用fork時(shí),父進(jìn)程中的線程此時(shí)正在調(diào)用什么函數(shù),這和信號發(fā)生時(shí)的場景一樣),你不能調(diào)用線程安全的函數(shù)(除非它是可重入的),而只能調(diào)用異步信號安全的函數(shù)。比如,fork之后,子進(jìn)程不能調(diào)用:
malloc,因?yàn)閙alloc在訪問全局狀態(tài)時(shí)幾乎肯定會(huì)加鎖;
任何可能分配或釋放內(nèi)存的函數(shù),比如snprintf;
任何Pthreads函數(shù);
printf系列函數(shù),因?yàn)槠渌€程可能恰好持有stdout/stderr的鎖;
除了man 7 signal中明確列出的信號安全函數(shù)之外的任何函數(shù)。
因此,多線程中調(diào)用fork,唯一安全的做法是fork之后,立即調(diào)用exec執(zhí)行另一個(gè)程序,徹底隔斷子進(jìn)程與父進(jìn)程的聯(lián)系。
在多線程環(huán)境中調(diào)用fork,產(chǎn)生子進(jìn)程后。子進(jìn)程內(nèi)部只存在一個(gè)線程,也就是父進(jìn)程中調(diào)用fork的線程的副本。
使用fork創(chuàng)建子進(jìn)程時(shí),子進(jìn)程通過繼承整個(gè)地址空間的副本,也從父進(jìn)程那里繼承了所有互斥量、讀寫鎖和條件變量的狀態(tài)。如果父進(jìn)程中的某個(gè)線程占有鎖,則子進(jìn)程同樣占有這些鎖。問題是子進(jìn)程并不包含占有鎖的線程的副本,所以子進(jìn)程沒有辦法知道它占有了哪些鎖,并且需要釋放哪些鎖。
盡管Pthread提供了pthread_atfork函數(shù)試圖繞過這樣的問題,但是這回使得代碼變得混亂。因此《Programming With Posix Threads》一書的作者說:”Avoid using fork in threaded code except where the child process will immediately exec a new program.”。
b:限制程序的CPU占用率;
這個(gè)很容易理解,比如在一個(gè)8核的服務(wù)器上,一個(gè)單線程程序即便發(fā)生busy-wait,占滿1個(gè)core,其CPU使用率也只有12.5%,在這種最壞的情況下,系統(tǒng)還是有87.5%的計(jì)算資源可供其他服務(wù)進(jìn)程使用。
linux tcp短鏈接的介紹就聊到這里吧,感謝你花時(shí)間閱讀本站內(nèi)容,更多關(guān)于linux tcp短鏈接,Linux TCP短鏈接技巧,提高網(wǎng)絡(luò)連接效率,如何看懂《Linux多線程服務(wù)端編程的信息別忘了在本站進(jìn)行查找喔。
香港服務(wù)器選創(chuàng)新互聯(lián),2H2G首月10元開通。
創(chuàng)新互聯(lián)(www.cdcxhl.com)互聯(lián)網(wǎng)服務(wù)提供商,擁有超過10年的服務(wù)器租用、服務(wù)器托管、云服務(wù)器、虛擬主機(jī)、網(wǎng)站系統(tǒng)開發(fā)經(jīng)驗(yàn)。專業(yè)提供云主機(jī)、虛擬主機(jī)、域名注冊、VPS主機(jī)、云服務(wù)器、香港云服務(wù)器、免備案服務(wù)器等。
標(biāo)題名稱:LinuxTCP短鏈接技巧,提高網(wǎng)絡(luò)連接效率(linuxtcp短鏈接)
地址分享:http://m.fisionsoft.com.cn/article/djhjosj.html


咨詢
建站咨詢
