C語(yǔ)言實(shí)現(xiàn)this指針
this指針作為面向?qū)ο笾械闹匾獧C(jī)制,可以指向當(dāng)前對(duì)象自己的屬性和方法。在之前的C語(yǔ)言實(shí)現(xiàn)面向?qū)ο蟮膶?/a>中,this指針僅僅采用了一個(gè)公用的全局變量來(lái)存儲(chǔ),這種設(shè)計(jì)方法不僅在多處理機(jī)多線程環(huán)境下會(huì)產(chǎn)生問題,甚至在單處理機(jī)環(huán)境下也會(huì)導(dǎo)致問題(中斷)。舉個(gè)例子:
上面的例子中,this函數(shù)同時(shí)被兩個(gè)函數(shù)訪存,且中斷事件導(dǎo)致代碼失去順序性,產(chǎn)生跳轉(zhuǎn),這和多線程輪詢執(zhí)行一樣,必然導(dǎo)致this指針被覆蓋或篡改的問題。
下面來(lái)解決這個(gè)問題:
首先肯定不能用一個(gè)統(tǒng)一的this指針為每個(gè)模塊共享使用,應(yīng)當(dāng)為每個(gè)模塊獨(dú)立創(chuàng)建一個(gè)私有的this指針,說(shuō)到私有,也就是每個(gè)C文件下的this指針是獨(dú)立存在的,不受其他文件的this指針?biāo)绊憽T贑語(yǔ)言中,如果你用常規(guī)方法創(chuàng)建同名的全局變量,會(huì)報(bào)重復(fù)定義的錯(cuò)誤,可以通過添加static關(guān)鍵字來(lái)表示此全局變量存放于靜態(tài)存儲(chǔ)區(qū)中,且只在本文件中可以被訪問,其他C文件無(wú)法訪問到此變量,如下例:
這樣就解決了每個(gè)模塊私有訪問自己的this指針的問題。
如何保證在多線程或中斷環(huán)境下,this指針仍能保證線程安全,下面來(lái)解決第二個(gè)問題:
眾所周知,多線程和中斷之所以能正確的執(zhí)行而不影響其他函數(shù),是因?yàn)樵谔D(zhuǎn)到中斷函數(shù)或線程任務(wù)函數(shù)之前,執(zhí)行了保護(hù)現(xiàn)場(chǎng)工作,即將CPU狀態(tài)寄存器和PC的值保留在當(dāng)前函數(shù)棧中,然后再執(zhí)行中斷函數(shù)或線程任務(wù)。執(zhí)行完畢后,先從??臻g中把之前保存的現(xiàn)場(chǎng)信息還原,再繼續(xù)執(zhí)行。參照上面的流程可知,this指針產(chǎn)生數(shù)據(jù)一致性問題的本質(zhì)原因是沒有做到在函數(shù)調(diào)用時(shí)保存現(xiàn)場(chǎng),在線程任務(wù)中或中斷函數(shù)中產(chǎn)生對(duì)象函數(shù)調(diào)用則將當(dāng)前對(duì)象指針壓入This棧中,在調(diào)用對(duì)象方法時(shí),第一行用于從This棧棧頂彈出自身對(duì)象,此時(shí)棧頂指針將指向上次的this指針,通過這種方式即可實(shí)現(xiàn)this指針的獨(dú)立性。
在對(duì)象調(diào)用自身方法時(shí),先通過setThis(n)操作將當(dāng)前對(duì)象壓入This棧中,再調(diào)用自身方法,在方法中,第一行用于獲取棧頂指針并保存在temp中,即剛才壓入棧中的對(duì)象,隨后的所有對(duì)象操作都通過temp來(lái)調(diào)用。通過棧來(lái)設(shè)計(jì)this指針,在中斷或多線程情況下,假如A執(zhí)行了setThis(a)將當(dāng)前對(duì)象a壓入棧中,但是沒來(lái)得及調(diào)用方法取出棧頂對(duì)象,就被B中斷信號(hào)打斷了,B中斷調(diào)用setThis(b)將b壓入棧中,而后b對(duì)象調(diào)用方法并正確從棧頂彈出自身對(duì)象b,繼續(xù)執(zhí)行方法,中斷函數(shù)執(zhí)行完畢后,回到A任務(wù),此時(shí)A再?gòu)臈m敨@取的對(duì)象正是自己的對(duì)象a。
由于中斷事件是以中斷函數(shù)為單位執(zhí)行,所以在中斷嵌套的情況下,是一級(jí)一級(jí)的從函數(shù)棧中彈出,不存在像多線程輪詢時(shí)的無(wú)序性,即A任務(wù)執(zhí)行了一半又去執(zhí)行B任務(wù),B任務(wù)可能剛開始執(zhí)行一會(huì)又去執(zhí)行C任務(wù),C任務(wù)執(zhí)行了一半又回到A任務(wù).....。而中斷是能確保最高級(jí)別中斷函數(shù)執(zhí)行完畢后再回到上一級(jí)函數(shù)繼續(xù)執(zhí)行,這樣一級(jí)級(jí)向上從函數(shù)棧中彈出。因此以上操作在中斷情況下能正確執(zhí)行,下面來(lái)討論多線程輪詢情況下的解決辦法。
由于多線程情況比較復(fù)雜,舉一個(gè)例子來(lái)說(shuō)明上述方法在多線程情況下的問題和解決方案。
例:現(xiàn)有3個(gè)線程A,B,C,以及三個(gè)同類對(duì)象a,b,c
(1).A線程首先調(diào)用setThis(a)操作將a對(duì)象壓入This棧
(2).沒等a對(duì)象調(diào)用方法從This棧頂獲取自身對(duì)象,A線程時(shí)間片消耗完畢,切換到B線程執(zhí)行
(3).B線程執(zhí)行setThis(b)操作將b對(duì)象壓入This棧,此時(shí)This棧中元素為:b,a
(4).b對(duì)象調(diào)用方法,第一行先從棧頂彈出自身對(duì)象后,再繼續(xù)執(zhí)行方法
(5).B線程方法執(zhí)行了一半,時(shí)間片消耗完畢,切換到C線程執(zhí)行,此時(shí)This棧中的元素為:a
(6).C線程執(zhí)行setThis(c)將c對(duì)象壓入This棧,此時(shí)This棧中元素為:c,a
(7).在c對(duì)象調(diào)用方法從This棧頂彈出自身對(duì)象之前,線程C時(shí)間片消耗完畢,切換到A線程執(zhí)行,此時(shí)This棧中的元素為:c,a
(8).A線程繼續(xù)執(zhí)行,a對(duì)象調(diào)用方法,第一行從This棧頂彈出對(duì)象,但此時(shí)彈出的對(duì)象是c,并非自身對(duì)象,這必然導(dǎo)致a對(duì)象調(diào)用方法出錯(cuò)。
那么如何解決這個(gè)問題呢,其實(shí)十分簡(jiǎn)單,熟悉線程安全的開發(fā)者立刻就想到了信號(hào)量。這里使用一個(gè)同步信號(hào)量來(lái)保證setThis()操作和對(duì)象調(diào)用方法從This棧彈棧操作這兩個(gè)操作成為原子操作,要么兩個(gè)操作都執(zhí)行,要么都不執(zhí)行。在setThis()函數(shù)壓棧時(shí),flag置為true,在方法調(diào)用的第一行彈棧操作時(shí),將flag置為false。如果A線程執(zhí)行了setThis(a)將對(duì)象a壓入棧中,并置flag為true,然后切換到B線程,執(zhí)行setThis(b)時(shí),執(zhí)行while(flag);等待鎖被釋放,即忙等,當(dāng)然也可直接yield(),主動(dòng)放棄此輪CPU調(diào)度。線程B忙等到時(shí)間片消費(fèi)完畢或主動(dòng)放棄CPU調(diào)度后,切換回A線程,A線程繼續(xù),調(diào)用方法從This棧中彈出a對(duì)象,并置flag為false,a繼續(xù)執(zhí)行方法,A線程時(shí)間片耗盡后切換到B線程,B線程繼續(xù)while(flag),由于此時(shí)flag為false,可以正常操作。
總結(jié)下兩種情況:
中斷:無(wú)需上鎖,可正確入棧出棧,CPU利用率高
多線程:需要上鎖,同類對(duì)象忙等情況下,CPU利用率低。yield()情況下,CPU利用率高。
不過不用擔(dān)心效率問題,上面的例子為多線程切換的極端情況,很少會(huì)發(fā)生,一般情況下線程時(shí)間片為毫秒級(jí)別,完全能夠保證對(duì)象入棧和出棧能在時(shí)間片周期內(nèi)完成,即不會(huì)產(chǎn)生等待。而在上述的極端情況下,A線程在時(shí)間片結(jié)束的那一刻調(diào)用了setThis將對(duì)象a入棧,但沒來(lái)得及調(diào)用對(duì)象方法從棧中獲取對(duì)象a,就切換到B線程執(zhí)行了,此時(shí)B線程如果調(diào)用同類對(duì)象,要么等待,要么主動(dòng)讓出CPU給A線程繼續(xù)執(zhí)行,解開同步鎖。在對(duì)象調(diào)用方法的流程如下:
call()是一個(gè)宏函數(shù),里面執(zhí)行了兩步:
(1) 設(shè)flag=true,調(diào)用setThis(str),將當(dāng)前str對(duì)象壓入String類的This棧中
(2) 調(diào)用.equals("123")方法,方法內(nèi)第一行從This棧棧頂取出str對(duì)象并設(shè)flag=false然后繼續(xù)執(zhí)行方法
可見在對(duì)象調(diào)用方法時(shí),入棧和出棧操作是緊挨著的兩條語(yǔ)句,即flag被置為true后,馬上就執(zhí)行入棧出棧,而入棧和出棧的時(shí)間復(fù)雜度是o(1),也就是在flag置為true后,僅需幾條指令就能完成入棧出棧,并置flag=false。為了再次提升效率,可將pop()函數(shù)改為內(nèi)聯(lián)函數(shù)或宏函數(shù),減少函數(shù)入棧指令和函數(shù)出棧指令對(duì)時(shí)間片的開銷。就像這樣:
可見入棧出??偣簿?條語(yǔ)句,在通過inline修飾,減少了函數(shù)跳轉(zhuǎn)的開銷,保證在絕大多數(shù)情況下不會(huì)出現(xiàn)忙等。
針對(duì)兩種情況的setThis()函數(shù)實(shí)現(xiàn)不一樣,中斷情況下無(wú)需任何判斷,直接入棧,而多線程情況下需要加入while(flag);或if(flag) yield();操作。
下面我將用c語(yǔ)言演示通過棧實(shí)現(xiàn)this的代碼:
封裝基于順序棧的This對(duì)象:
This.h文件:
This.c文件:
創(chuàng)建String類測(cè)試This對(duì)象:
MyString.h文件:
MyString.c文件:
在main.c中調(diào)用以測(cè)試結(jié)果:

下面演示多線程下測(cè)試結(jié)果:
測(cè)試方法:讓多個(gè)線程中的多個(gè)String對(duì)象輸出自己的字符串值,字符串對(duì)象的獲取通過This棧獲取,如果正確輸出,則多線程環(huán)境下無(wú)誤。
測(cè)試正常無(wú)誤,如下視頻所示:

碼字碼了一整天,就當(dāng)復(fù)習(xí)下數(shù)據(jù)結(jié)構(gòu)和操作系統(tǒng)了。像往常一樣,此工程直接分享出來(lái),帶師門可自行測(cè)試和移植優(yōu)化:
鏈接:https://pan.baidu.com/s/15EfFJOFMp7OEawPj5oYl3w?
提取碼:ALYA?