利用信號(hào)量實(shí)現(xiàn)簡(jiǎn)單進(jìn)程內(nèi)消息隊(duì)列
????????前幾天學(xué)習(xí)了線程和線程的通信方式,線程的通信方式與進(jìn)程有部分類(lèi)似,比如自旋鎖、信號(hào)量等。但進(jìn)程間還有一個(gè)消息隊(duì)列的通信方式,用于進(jìn)程內(nèi)線程的一些輕量級(jí)應(yīng)用場(chǎng)合顯得過(guò)于大材小用。如果要做到進(jìn)程內(nèi)線程之間類(lèi)似于消息隊(duì)列的數(shù)據(jù)傳輸通信,盡管可以使用條件變量加上全局?jǐn)?shù)據(jù)的方式,但這樣未免線程間的耦合性太強(qiáng)了。因此打算以信號(hào)量為基礎(chǔ)在應(yīng)用層實(shí)現(xiàn)一個(gè)簡(jiǎn)單的消息隊(duì)列機(jī)制。
????????引入信號(hào)量的目的是為了令等待(接收)消息的線程在隊(duì)列中沒(méi)有消息的情況下陷入阻塞,或者在隊(duì)列中存在多個(gè)消息時(shí),可以多次獲取到信號(hào)量,這里的信號(hào)量是計(jì)數(shù)型信號(hào)量。
有了信號(hào)量為基礎(chǔ),就可以得出編程思路:
①消息隊(duì)列結(jié)構(gòu):包含一個(gè)信號(hào)量、一個(gè)自旋鎖和一個(gè)先進(jìn)先出的隊(duì)列結(jié)構(gòu)。
②隊(duì)列結(jié)構(gòu):一個(gè)單向鏈表,包含一個(gè)頭節(jié)點(diǎn)指針、下一個(gè)節(jié)點(diǎn)指針、一個(gè)數(shù)據(jù)指針、一個(gè)數(shù)據(jù)長(zhǎng)度變量。
③消息隊(duì)列的初始化:內(nèi)存空間清0,即將所有指針設(shè)為NULL。
④發(fā)送消息:申請(qǐng)一個(gè)消息節(jié)點(diǎn)的內(nèi)存空間,再向隊(duì)列里添加該消息節(jié)點(diǎn),然后釋放1個(gè)信號(hào)量。
⑤接受消息:阻塞請(qǐng)求信號(hào)量,獲取到信號(hào)量后,從隊(duì)列里取出第一個(gè)消息節(jié)點(diǎn),拷貝數(shù)據(jù)后,釋放該節(jié)點(diǎn)的內(nèi)存空間。
⑥消息隊(duì)列的銷(xiāo)毀:循環(huán)移出隊(duì)列里剩余的消息并釋放內(nèi)存空間,最后銷(xiāo)毀信號(hào)量和自旋鎖。
下面就開(kāi)始逐一實(shí)現(xiàn):
·首先是隊(duì)列結(jié)構(gòu)類(lèi)型的聲明:

·消息隊(duì)列的初始化

????????初始化做的事情比較少,只需要進(jìn)行空指針判斷、消息隊(duì)列結(jié)構(gòu)清0和信號(hào)量的初始化。
·向隊(duì)列添加消息節(jié)點(diǎn)

?????????在消息隊(duì)列結(jié)構(gòu)初始化好后,其中的隊(duì)列是一個(gè)空鏈表。因此每次在添加節(jié)點(diǎn)時(shí)都需要判斷當(dāng)前隊(duì)列是否為空,若為空則新節(jié)點(diǎn)插入表頭并設(shè)置新的表頭,若不為空則插入表尾。
????????? ?這是一個(gè)文件內(nèi)部調(diào)用的函數(shù),因此定義成靜態(tài)static。
·從隊(duì)列移出一個(gè)節(jié)點(diǎn)

????????根據(jù)先進(jìn)先出的原則,從隊(duì)列中移除一個(gè)節(jié)點(diǎn)應(yīng)從鏈表的第一個(gè)節(jié)點(diǎn)開(kāi)始移除,所謂移除節(jié)點(diǎn)僅僅是將其從鏈表中移出,其所占的內(nèi)存空間還不能釋放,需要等該節(jié)點(diǎn)的消息數(shù)據(jù)被讀取后才可以釋放。因此通過(guò)指向指針的指針將該節(jié)點(diǎn)返回以便處理。
????????這是也一個(gè)文件內(nèi)部調(diào)用的函數(shù)。
·發(fā)送消息

????????發(fā)送消息函數(shù)有3個(gè)形式參數(shù),第一個(gè)msg就是指向需要請(qǐng)求消息的消息隊(duì)列,第二個(gè)參數(shù)buf是指向發(fā)送線程要發(fā)送的數(shù)據(jù)緩沖區(qū),第三個(gè)參數(shù)size指示數(shù)據(jù)的大小,單位字節(jié)。
????????發(fā)送消息首先要申請(qǐng)一個(gè)隊(duì)列節(jié)點(diǎn)占用的內(nèi)存空間,然后將該節(jié)點(diǎn)添加進(jìn)隊(duì)列中,對(duì)該節(jié)點(diǎn)添加消息數(shù)據(jù)和消息大小。最后再釋放1個(gè)信號(hào)量即可。
·接收(等待)消息

????????接收消息函數(shù)有4個(gè)形式參數(shù),第一個(gè)msg與發(fā)送消息相同,也是指向需要請(qǐng)求消息的消息隊(duì)列;第二個(gè)參數(shù)buf是指向接收線程要存放數(shù)據(jù)的緩沖區(qū);第三個(gè)參數(shù)buf_size指示接收線程數(shù)據(jù)緩沖區(qū)的大小,防止消息長(zhǎng)度超過(guò)該數(shù)據(jù)緩沖區(qū)而導(dǎo)致的非法訪問(wèn);第四個(gè)參數(shù)msg_size指向保存消息長(zhǎng)度的變量,需要由接收線程提供。
????????發(fā)送數(shù)據(jù)首先要對(duì)參數(shù)有效性進(jìn)行判斷,之后請(qǐng)求一個(gè)信號(hào)量,若此時(shí)信號(hào)量為0,則表示隊(duì)列中沒(méi)有消息,此時(shí)線程應(yīng)陷入阻塞。若有一個(gè)線程向此消息隊(duì)列中添加了一個(gè)消息,
則接收數(shù)據(jù)的線程因?yàn)檎?qǐng)求到了信號(hào)量而被喚醒,之后應(yīng)從隊(duì)列中移出一個(gè)消息節(jié)點(diǎn),并將節(jié)點(diǎn)的數(shù)據(jù)拷貝到接收線程,最后釋放該節(jié)點(diǎn)的內(nèi)存空間。
注:在向隊(duì)列添加和移除節(jié)點(diǎn)時(shí),應(yīng)該有保護(hù)機(jī)制,這里使用的是自旋鎖spinlock,以確保多個(gè)線程并發(fā)訪問(wèn)隊(duì)列時(shí)不存在數(shù)據(jù)一致性問(wèn)題。
·測(cè)試:? ? ????
????????最后編寫(xiě)一個(gè)測(cè)試程序測(cè)試該消息隊(duì)列是否可以使用,判斷是否可以使用的標(biāo)準(zhǔn)有:
①是否可以在2個(gè)線程之間實(shí)現(xiàn)1對(duì)1單向通信(1收1發(fā))。
②是否可以在多個(gè)線程之間實(shí)現(xiàn)1對(duì)多單向通信(1收多發(fā))。
測(cè)試程序:

????????測(cè)試程序中創(chuàng)建了3個(gè)入口地址相同的線程,通過(guò)傳入線程的參數(shù)來(lái)令他們每隔1秒向主線程發(fā)送不同的消息,而主線程一直循環(huán)等待消息并打印。執(zhí)行結(jié)果如下:

????????可以看出這個(gè)消息隊(duì)列在“3個(gè)線程發(fā)送,1個(gè)線程接收”的情況下是沒(méi)有問(wèn)題的??梢杂米饕恍┖?jiǎn)單應(yīng)用場(chǎng)合。
最后附上源碼文件:
鏈接:https://pan.baidu.com/s/1YDZLqxm3elmG2gf3sNkNqQ?
提取碼:1234