您好,登錄后才能下訂單哦!
依據(jù)微軟的MSDN上的解說:
(1) 同步函數(shù):當(dāng)一個函數(shù)是同步執(zhí)行時,那么當(dāng)該函數(shù)被調(diào)用時不會立即返回,直到該函數(shù)所要做的事情全都做完了才返回。
(2) 異步函數(shù):如果一個異步函數(shù)被調(diào)用時,該函數(shù)會立即返回盡管該函數(shù)規(guī)定的操作任務(wù)還沒有完成。
(3) 在一個線程中分別調(diào)用上述兩種函數(shù)會對調(diào)用線程有何影響呢?
當(dāng)一個線程調(diào)用一個同步函數(shù)時(例如:該函數(shù)用于完成寫文件任務(wù)),如果該函數(shù)沒有立即完成規(guī)定的操作,則該操作會導(dǎo)致該調(diào)用線程的掛起(將CPU的使用權(quán)交給系統(tǒng),讓系統(tǒng)分配給其他線程使用),直到該同步函數(shù)規(guī)定的操作完成才返回,最終才能導(dǎo)致該調(diào)用線程被重新調(diào)度。
當(dāng)一個線程調(diào)用的是一個異步函數(shù)(例如:該函數(shù)用于完成寫文件任務(wù)),該函數(shù)會立即返回盡管其規(guī)定的任務(wù)還沒有完成,這樣線程就會執(zhí)行異步函數(shù)的下一條語句,而不會被掛起。那么該異步函數(shù)所規(guī)定的工作是如何被完成的呢?當(dāng)然是通過另外一個線程完成的了?。荒敲葱碌木€程是哪里來的呢?可能是在異步函數(shù)中新創(chuàng)建的一個線程也可能是系統(tǒng)中已經(jīng)準(zhǔn)備好的線程。
(4)一個調(diào)用了異步函數(shù)的線程如何與異步函數(shù)的執(zhí)行結(jié)果同步呢?
為了解決該問題,調(diào)用線程需要使用“等待函數(shù)”來確定該異步函數(shù)何時完成了規(guī)定的任務(wù)。因此在線程調(diào)用異步函數(shù)之后立即調(diào)用一個“等待函數(shù)”掛起調(diào)用線程,一直等到異步函數(shù)執(zhí)行完其所有的操作之后,再執(zhí)行線程中的下一條指令。
我們是否已經(jīng)發(fā)現(xiàn)了一個有趣的地方呢?!就是我們可以使用等待函數(shù)將一個異步執(zhí)行的函數(shù)封裝成一個同步函數(shù)。
2.同步調(diào)用與異步調(diào)用
操作系統(tǒng)發(fā)展到今天已經(jīng)十分精巧,線程就是其中一個杰作。操作系統(tǒng)把 CPU 處理時間劃分成許多短暫時間片,在時間 T1 執(zhí)行一個線程的指令,到時間 T2 又執(zhí)行下一線程的指令,各線程輪流執(zhí)行,結(jié)果好象是所有線程在并肩前進(jìn)。這樣,編程時可以創(chuàng)建多個線程,在同一期間執(zhí)行,各線程可以“并行”完成不同的任務(wù)。
在單線程方式下,計算機(jī)是一臺嚴(yán)格意義上的馮·諾依曼式機(jī)器,一段代碼調(diào)用另一段代碼時,只能采用同步調(diào)用,必須等待這段代碼執(zhí)行完返回結(jié)果后,調(diào)用方才能繼續(xù)往下執(zhí)行。有了多線程的支持,可以采用異步調(diào)用,調(diào)用方和被調(diào)方可以屬于兩個不同的線程,調(diào)用方啟動被調(diào)方線程后,不等對方返回結(jié)果就繼續(xù)執(zhí)行后續(xù)代碼。被調(diào)方執(zhí)行完畢后,通過某種手段通知調(diào)用方:結(jié)果已經(jīng)出來,請酌情處理。
計算機(jī)中有些處理比較耗時。調(diào)用這種處理代碼時,調(diào)用方如果站在那里苦苦等待,會嚴(yán)重影響程序性能。例如,某個程序啟動后如果需要打開文件讀出其中的數(shù)據(jù),再根據(jù)這些數(shù)據(jù)進(jìn)行一系列初始化處理,程序主窗口將遲遲不能顯示,讓用戶感到這個程序怎么等半天也不出來,太差勁了。借助異步調(diào)用可以把問題輕松化解:把整個初始化處理放進(jìn)一個單獨(dú)線程,主線程啟動此線程后接著往下走,讓主窗口瞬間顯示出來。等用戶盯著窗口犯呆時,初始化處理就在背后悄悄完成了。程序開始穩(wěn)定運(yùn)行以后,還可以繼續(xù)使用這種技巧改善人機(jī)交互的瞬時反應(yīng)。用戶點(diǎn)擊鼠標(biāo)時,所激發(fā)的操作如果較費(fèi)時,再點(diǎn)擊鼠標(biāo)將不會立即反應(yīng),整個程序顯得很沉重。借助異步調(diào)用處理費(fèi)時的操作,讓主線程隨時恭候下一條消息,用戶點(diǎn)擊鼠標(biāo)時感到輕松快捷,肯定會對軟件產(chǎn)生好感。
異步調(diào)用用來處理從外部輸入的數(shù)據(jù)特別有效。假如計算機(jī)需要從一臺低速設(shè)備索取數(shù)據(jù),然后是一段冗長的數(shù)據(jù)處理過程,采用同步調(diào)用顯然很不合算:計算機(jī)先向外部設(shè)備發(fā)出請求,然后等待數(shù)據(jù)輸入;而外部設(shè)備向計算機(jī)發(fā)送數(shù)據(jù)后,也要等待計算機(jī)完成數(shù)據(jù)處理后再發(fā)出下一條數(shù)據(jù)請求。雙方都有一段等待期,拉長了整個處理過程。其實(shí),計算機(jī)可以在處理數(shù)據(jù)之前先發(fā)出下一條數(shù)據(jù)請求,然后立即去處理數(shù)據(jù)。如果數(shù)據(jù)處理比數(shù)據(jù)采集快,要等待的只有計算機(jī),外部設(shè)備可以連續(xù)不停地采集數(shù)據(jù)。如果計算機(jī)同時連接多臺輸入設(shè)備,可以輪流向各臺設(shè)備發(fā)出數(shù)據(jù)請求,并隨時處理每臺設(shè)備發(fā)來的數(shù)據(jù),整個系統(tǒng)可以保持連續(xù)高速運(yùn)轉(zhuǎn)。編程的關(guān)鍵是把數(shù)據(jù)索取代碼和數(shù)據(jù)處理代碼分別歸屬兩個不同的線程。數(shù)據(jù)處理代碼調(diào)用一個數(shù)據(jù)請求異步函數(shù),然后徑自處理手頭的數(shù)據(jù)。待下一組數(shù)據(jù)到來后,數(shù)據(jù)處理線程將收到通知,結(jié)束 wait 狀態(tài),發(fā)出下一條數(shù)據(jù)請求,然后繼續(xù)處理數(shù)據(jù)。
異步調(diào)用時,調(diào)用方不等被調(diào)方返回結(jié)果就轉(zhuǎn)身離去,因此必須有一種機(jī)制讓被調(diào)方有了結(jié)果時能通知調(diào)用方。在同一進(jìn)程中有很多手段可以利用,筆者常用的手段是回調(diào)、event 對象和消息。
回調(diào):回調(diào)方式很簡單:調(diào)用異步函數(shù)時在參數(shù)中放入一個函數(shù)地址,異步函數(shù)保存此地址,待有了結(jié)果后回調(diào)此函數(shù)便可以向調(diào)用方發(fā)出通知。如果把異步函數(shù)包裝進(jìn)一個對象中,可以用事件取代回調(diào)函數(shù)地址,通過事件處理例程向調(diào)用方發(fā)通知。
event : event 是 Windows 系統(tǒng)提供的一個常用同步對象,以在異步處理中對齊不同線程之間的步點(diǎn)。如果調(diào)用方暫時無事可做,可以調(diào)用 wait 函數(shù)等在那里,此時 event 處于 nonsignaled 狀態(tài)。當(dāng)被調(diào)方出來結(jié)果之后,把 event 對象置于 signaled 狀態(tài),wait 函數(shù)便自動結(jié)束等待,使調(diào)用方重新動作起來,從被調(diào)方取出處理結(jié)果。這種方式比回調(diào)方式要復(fù)雜一些,速度也相對較慢,但有很大的靈活性,可以搞出很多花樣以適應(yīng)比較復(fù)雜的處理系統(tǒng)。
消息:借助 Windows 消息發(fā)通知是個不錯的選擇,既簡單又安全。程序中定義一個用戶消息,并由調(diào)用方準(zhǔn)備好消息處理例程。被調(diào)方出來結(jié)果之后立即向調(diào)用方發(fā)送此消息,并通過 WParam 和 LParam 這兩個參數(shù)傳送結(jié)果。消息總是與窗口 handle 關(guān)聯(lián),因此調(diào)用方必須借助一個窗口才能接收消息,這是其不方便之處。另外,通過消息聯(lián)絡(luò)會影響速度,需要高速處理時回調(diào)方式更有優(yōu)勢。
如果調(diào)用方和被調(diào)方分屬兩個不同的進(jìn)程,由于內(nèi)存空間的隔閡,一般是采用 Windows 消息發(fā)通知比較簡單可靠,被調(diào)方可以借助消息本身向調(diào)用方傳送數(shù)據(jù)。event 對象也可以通過名稱在不同進(jìn)程間共享,但只能發(fā)通知,本身無法傳送數(shù)據(jù),需要借助 Windows 消息和 FileMapping 等內(nèi)存共享手段或借助 MailSlot 和 Pipe 等通信手段。
異步調(diào)用原理并不復(fù)雜,但實(shí)際使用時容易出莫名其妙的問題,特別是不同線程共享代碼或共享數(shù)據(jù)時容易出問題,編程時需要時時注意是否存在這樣的共享,并通過各種狀態(tài)標(biāo)志避免沖突。Windows 系統(tǒng)提供的 mutex 對象用在這里特別方便。mutex 同一時刻只能有一個管轄者。一個線程放棄管轄權(quán)后,另一線程才能接管。當(dāng)某線程執(zhí)行到敏感區(qū)之前先接管 mutex,使其他線程被 wait 函數(shù)堵在身后;脫離敏感區(qū)之后立即放棄管轄權(quán),使 wait 函數(shù)結(jié)束等待,另一個線程便有機(jī)會光臨此敏感區(qū)。這樣就可以有效避免多個線程進(jìn)入同一敏感區(qū)。
由于異步調(diào)用容易出問題,要設(shè)計一個安全高效的編程方案需要比較多的設(shè)計經(jīng)驗,所以最好不要濫用異步調(diào)用。同步調(diào)用畢竟讓人更舒服些:不管程序走到哪里,只要死盯著移動點(diǎn)就能心中有數(shù),不至于象異步調(diào)用那樣,總有一種四面受敵、惶惶不安的感覺。必要時甚至可以把異步函數(shù)轉(zhuǎn)換為同步函數(shù)。方法很簡單:調(diào)用異步函數(shù)后馬上調(diào)用 wait 函數(shù)等在那里,待異步函數(shù)返回結(jié)果后再繼續(xù)往下走。
假如回調(diào)函數(shù)中包含文件處理之類的低速處理,調(diào)用方等不得,需要把同步調(diào)用改為異步調(diào)用,去啟動一個單獨(dú)的線程,然后馬上執(zhí)行后續(xù)代碼,其余的事讓線程慢慢去做。一個替×××法是借 API 函數(shù) PostMessage 發(fā)送一個異步消息,然后立即執(zhí)行后續(xù)代碼。這要比自己搞個線程省事許多,而且更安全。
如果你的服務(wù)端的客戶端數(shù)量多,你的服務(wù)端就采用異步的,但是你的客戶端可以用同步的,客戶端一般功能比較單一,收到數(shù)據(jù)后才能執(zhí)行下面的工作,所以弄成同步的在那等。
一、舉個打電話的例子:
阻塞 block 是指,你撥通某人的電話,但是此人不在,于是你拿著電話等他回來,其間不能再用電話。同步大概和阻塞差不多。
非阻塞 nonblock 是指,你撥通某人的電話,但是此人不在,于是你掛斷電話,待會兒再打。至于到時候他回來沒有,只有打了電話才知道。即所謂的“輪詢 / poll”。
異步是指,你撥通某人的電話,但是此人不在,于是你叫接電話的人告訴那人(leave a message),回來后給你打電話(call back)。
二、同步異步與阻塞和非阻塞是兩種不同的概念來著
同步異步指的是通信模式,而阻塞和非阻塞指的是在接收和發(fā)送時是否等待動作完成才返回
首先是通信的同步,主要是指客戶端在發(fā)送請求后,必須得在服務(wù)端有回應(yīng)后才發(fā)送下一個請求。所以這個時候的所有請求將會在服務(wù)端得到同步
其次是通信的異步,指客戶端在發(fā)送請求后,不必等待服務(wù)端的回應(yīng)就可以發(fā)送下一個請求,這樣對于所有的請求動作來說將會在服務(wù)端得到異步,這條請求的鏈路就象是一個請求隊列,所有的動作在這里不會得到同步的。
阻塞和非阻塞只是應(yīng)用在請求的讀取和發(fā)送。
在實(shí)現(xiàn)過程中,如果服務(wù)端是異步的話,客戶端也是異步的話,通信效率會很高,但如果服務(wù)端在請求的返回時也是返回給請求的鏈路時,客戶端是可以同步的,這種情況下,服務(wù)端是兼容同步和異步的。相反,如果客戶端是異步而服務(wù)端是同步的也不會有問題,只是處理效率低了些。
設(shè)想你是一位體育老師,需要測驗100位同學(xué)的400米成績。你當(dāng)然不會讓100位同學(xué)一起起跑,因為當(dāng)同學(xué)們返回終點(diǎn)時,你根本來不及掐表記錄各位同學(xué)的成績。
如果你每次讓一位同學(xué)起跑并等待他回到終點(diǎn)你記下成績后再讓下一位起跑,直到所有同學(xué)都跑完。恭喜你,你已經(jīng)掌握了同步阻塞模式。你設(shè)計了一個函數(shù),傳入?yún)?shù)是學(xué)生號和起跑時間,返回值是到達(dá)終點(diǎn)的時間。你調(diào)用該函數(shù)100次,就能完成這次測驗任務(wù)。這個函數(shù)是同步的,因為只要你調(diào)用它,就能得到結(jié)果;這個函數(shù)也是阻塞的,因為你一旦調(diào)用它,就必須等待,直到它給你結(jié)果,不能去干其他事情。
如果你一邊每隔10秒讓一位同學(xué)起跑,直到所有同學(xué)出發(fā)完畢;另一邊每有一個同學(xué)回到終點(diǎn)就記錄成績,直到所有同學(xué)都跑完。恭喜你,你已經(jīng)掌握了異步非阻塞模式。你設(shè)計了兩個函數(shù),其中一個函數(shù)記錄起跑時間和學(xué)生號,該函數(shù)你會主動調(diào)用100次;另一個函數(shù)記錄到達(dá)時間和學(xué)生號,該函數(shù)是一個事件驅(qū)動的callback函數(shù),當(dāng)有同學(xué)到達(dá)終點(diǎn)時,你會被動調(diào)用。你主動調(diào)用的函數(shù)是異步的,因為你調(diào)用它,它并不會告訴你結(jié)果;這個函數(shù)也是非阻塞的,因為你一旦調(diào)用它,它就馬上返回,你不用等待就可以再次調(diào)用它。但僅僅將這個函數(shù)調(diào)用100次,你并沒有完成你的測驗任務(wù),你還需要被動等待調(diào)用另一個函數(shù)100次。
當(dāng)然,你馬上就會意識到,同步阻塞模式的效率明顯低于異步非阻塞模式。那么,誰還會使用同步阻塞模式呢?不錯,異步模式效率高,但更麻煩,你一邊要記錄起跑同學(xué)的數(shù)據(jù),一邊要記錄到達(dá)同學(xué)的數(shù)據(jù),而且同學(xué)們回到終點(diǎn)的次序與起跑的次序并不相同,所以你還要不停地在你的成績冊上查找學(xué)生號。忙亂之中你往往會張冠李戴。你可能會想出更聰明的辦法:你帶了很多塊秒表,讓同學(xué)們分組互相測驗。恭喜你!你已經(jīng)掌握了多線程同步模式!
每個拿秒表的同學(xué)都可以獨(dú)立調(diào)用你的同步函數(shù),這樣既不容易出錯,效率也大大提高,只要秒表足夠多,同步的效率也能達(dá)到甚至超過異步。
可以理解,你現(xiàn)的問題可能是:既然多線程同步既快又好,異步模式還有存在的必要嗎?
很遺憾,異步模式依然非常重要,因為在很多情況下,你拿不出很多秒表。你需要通信的對端系統(tǒng)可能只允許你建立一個SOCKET連接,很多金融、電信行業(yè)的大型業(yè)務(wù)系統(tǒng)都如此要求。
一、同步阻塞模式
在這個模式中,用戶空間的應(yīng)用程序執(zhí)行一個系統(tǒng)調(diào)用,并阻塞,直到系統(tǒng)調(diào)用完成為止(數(shù)據(jù)傳輸完成或發(fā)生錯誤)。
二、同步非阻塞模式
同步阻塞 I/O 的一種效率稍低的。非阻塞的實(shí)現(xiàn)是 I/O 命令可能并不會立即滿足,需要應(yīng)用程序調(diào)用許多次來等待操作完成。這可能效率不高,因為在很多情況下,當(dāng)內(nèi)核執(zhí)行這個命令時,應(yīng)用程序必須要進(jìn)行忙碌等待,直到數(shù)據(jù)可用為止,或者試圖執(zhí)行其他工作。因為數(shù)據(jù)在內(nèi)核中變?yōu)榭捎玫接脩粽{(diào)用 read 返回數(shù)據(jù)之間存在一定的間隔,這會導(dǎo)致整體數(shù)據(jù)吞吐量的降低。但異步非阻塞由于是多線程,效率還是高。
/* create the connection by socket * means that connect "sockfd" to "server_addr" * 同步阻塞模式 */if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) { perror("connect"); exit(1); }/* 同步非阻塞模式 */while (send(sockfd, snd_buf, sizeof(snd_buf), MSG_DONTWAIT) == -1) { sleep(1); printf("sleep\n"); }
網(wǎng)絡(luò)程序開發(fā)流程:
1. 需求分析
2. 根據(jù)需求, 進(jìn)行數(shù)據(jù)包設(shè)計(一般分為包頭和包數(shù)據(jù)兩部分, 包頭用來存儲包的必要信息, 如信息類型, 數(shù)據(jù)長度等)
3. 定義傳輸協(xié)議(如何傳輸).
4. 理解需求, 設(shè)計總體架構(gòu), 利用設(shè)計模式等方法, 進(jìn)行問題分析和設(shè)計類圖.
5. 實(shí)現(xiàn), 通常要配合多線程來實(shí)現(xiàn)通信問題. (一般有等待客戶請求線程, 接收數(shù)據(jù)線程, 發(fā)送數(shù)據(jù)線程, 資源清理線程).
6. 實(shí)現(xiàn)服務(wù)器端.
7. 實(shí)現(xiàn)客戶端.
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。