單線程讀單線程寫(xiě)一個(gè)變量是否需要加鎖,剛畢業(yè)的時(shí)候我會(huì)有這樣的想法:一個(gè)線程只讀并沒(méi)有改變變量的值并不會(huì)有兩個(gè)線程同時(shí)寫(xiě)一個(gè)變量產(chǎn)生競(jìng)態(tài),所以不用加鎖,但是工作中長(zhǎng)者給我指導(dǎo)都是多線程必須加鎖,所以我也沒(méi)有深究這個(gè)問(wèn)題,從來(lái)沒(méi)有想過(guò)為什么。
過(guò)了一段時(shí)間后,了解到原子性這個(gè)概念,了解到雖然一個(gè)線程讀一個(gè)線程寫(xiě),但是因?yàn)閷?duì)一個(gè)線程的寫(xiě)和讀并非是原子的,讀線程可能讀到另外一個(gè)線程寫(xiě)到一半的值,所以要加鎖來(lái)保護(hù)。但是上面的答案直到我看到下面的文章再一次被推翻了。
學(xué)了C然后C++,然后MFC/Windows,然后是C#,其中數(shù)據(jù)類(lèi)型很多,由基本類(lèi)型衍生的typedef類(lèi)型也N多。熟知基本數(shù)據(jù)類(lèi)型是我們正確表達(dá)實(shí)際問(wèn)題中各種數(shù)據(jù)的前提,因此我分類(lèi)總結(jié)了一下C/C++/Windows /C#基本數(shù)據(jù)類(lèi)型,以便日后查閱。

說(shuō)明:
(1)類(lèi)型修飾符signed和unsigned用于修飾字符型和整形。
(2)類(lèi)型修飾符short和long用于修飾字符型和整形。
(3)當(dāng)用signed和unsigned、short和long修飾int整形時(shí),int可省略。
(4)其中bool和wchar_t是C++特有的。
(5)除上表以外,C/C++都可以自定義枚舉enum、聯(lián)合union和struct結(jié)構(gòu)體類(lèi)型。
(6)以上sizeof通過(guò)Windows XP 32位平臺(tái)測(cè)試,其中某些類(lèi)型數(shù)據(jù)的字節(jié)數(shù)和數(shù)值范圍由操作系統(tǒng)和編譯平臺(tái)決定。比如16位機(jī)上,sizeof(int) = 2,而32位機(jī)上sizeof(int) = 4;32位機(jī)上sizeof(long) = 4,而64位機(jī)上sizeof(long) = 8。除此之外,注意64位機(jī)上的pointer占8byte。
(7)void的字面意思是“無(wú)類(lèi)型”,不能用來(lái)定義變量。void真正發(fā)揮的作用在于:<1> <wbr style="box-sizing: border-box; outline: 0px; overflow-wrap: break-word;">對(duì)函數(shù)返回和函數(shù)參數(shù)的限定,例如自定義既不帶參數(shù)也無(wú)返回值的函數(shù)void MyFunc(void);<2>定義無(wú)類(lèi)型通用指針void *,指向任何類(lèi)型的數(shù)據(jù)。
(8)標(biāo)準(zhǔn)C++庫(kù)及STL還提供了通用數(shù)據(jù)結(jié)構(gòu):字符串類(lèi)string;向量類(lèi)模板vector;雙端隊(duì)列類(lèi)模板deque;鏈表類(lèi)模板list;容器適配器堆棧類(lèi)stack(實(shí)現(xiàn)先進(jìn)后出的操作);容器適配器隊(duì)列類(lèi)queue(實(shí)現(xiàn)先進(jìn)先出的操作);集合類(lèi)set;多重集合類(lèi)multiset;映射類(lèi)map;多重映射類(lèi)multimap;位集合bitset;迭代器iterator (類(lèi)似指針的功能,對(duì)容器的內(nèi)容進(jìn)行訪問(wèn))。
(9)在標(biāo)準(zhǔn)c++中,int的定義長(zhǎng)度要依靠你的機(jī)器的字長(zhǎng),也就是說(shuō),如果你的機(jī)器是32位的,int的長(zhǎng)度為32位,如果你的機(jī)器是64位的,那么int的標(biāo)準(zhǔn)長(zhǎng)度就是64位,而vc中__int64是為在32機(jī)位機(jī)器長(zhǎng)實(shí)現(xiàn)64位長(zhǎng)度的整形數(shù)。
(10)關(guān)于32位平臺(tái)下的int和long
long從字面上看,應(yīng)該是64位才更合理,把long當(dāng)成32位實(shí)在是一個(gè)歷史的包袱。像C#那樣新起爐灶的程序語(yǔ)言,由于沒(méi)有需要支持老代碼的問(wèn)題,就把long當(dāng)作64位來(lái)處理了。
在32位平臺(tái)下,long是相對(duì)short而言,long(short)類(lèi)型是long(short) <wbr style="box-sizing: border-box; outline: 0px; overflow-wrap: break-word;">int類(lèi)型的簡(jiǎn)稱(chēng),sizeof(long) = sizeof(int) = 4。int和long的范圍雖然一樣,但輸入輸出格式不同,printf int的格式為%d,而printf long的格式為%ld。
考慮到程序的可移植性,還是要將他們區(qū)分開(kāi)來(lái)。但當(dāng)要求的數(shù)值范圍為4byte時(shí),建議使用int類(lèi)型,因?yàn)榈谝话娴腃語(yǔ)言只有一種類(lèi)型,那就是int。
(11)在Win32 API及MFC中為了使類(lèi)型名稱(chēng)在語(yǔ)意上更明了,對(duì)以上基本類(lèi)型進(jìn)行了大量的typedef。例如WINDEF.H中的BYTE,WORD,DWORD。
(12)計(jì)算機(jī)內(nèi)部?jī)?nèi)存的基本單位是1byte(8個(gè)電子開(kāi)關(guān))!
提出問(wèn)題:如果有一個(gè)類(lèi)型為int的全局變量a, 線程A對(duì)a僅進(jìn)行讀操作,線程B對(duì)a僅進(jìn)行寫(xiě)操作,那么兩個(gè)線程在操作a時(shí)是否需要加鎖來(lái)保持同步呢?
這個(gè)不能簡(jiǎn)單判斷一定要加鎖或是不加鎖。要分情況討論。
情況一
如果線程A讀取a的目的僅為了顯示給界面,或者a滿(mǎn)足一定條件后執(zhí)行某些操作,而在執(zhí)行這些操作過(guò)程中對(duì)a是否發(fā)生了變化并不關(guān)心;一定間隔時(shí)間后又同樣執(zhí)行上述操作。這種情況下,就不需要加鎖。
理由是,線程A對(duì)a進(jìn)行讀取時(shí)是完整的讀取的,同樣線程B對(duì)a寫(xiě)也是完成寫(xiě)的;不存在對(duì)a讀一半或?qū)懸话氲膯?wèn)題??赡苡钟行碌囊蓡?wèn),這樣且不是說(shuō)對(duì)a的操作是原子了,那樣的話何必還需要原子變量的類(lèi)型了,而且對(duì)a的操作也不是一條指令能完成的啊。其實(shí)這個(gè)疑問(wèn)和“不存在對(duì)a讀一半或?qū)懸话搿钡恼f(shuō)法并不矛盾。因?yàn)椤安淮嬖趯?duì)a讀一半或?qū)懸话搿辈⒉皇钦f(shuō)對(duì)a的操作就是原子操作。對(duì)寫(xiě)舉個(gè)例子,a = a + 1, 這個(gè)操作毫無(wú)疑問(wèn)肯定不是原子操作。這個(gè)語(yǔ)句簡(jiǎn)單的轉(zhuǎn)換為類(lèi)似機(jī)器語(yǔ)言,第一步,把a(bǔ)讀取到一個(gè)寄存器中,第二步將寄存器中的值加1,第三步將寄存器中的新值寫(xiě)入a中。既然需要三步操作了,顯然就不是原子操作了,那么“不存在對(duì)a讀一半或?qū)懸话搿钡囊馑际鞘裁茨??它的意思就是說(shuō)第二步是一次執(zhí)行成功的。換句話說(shuō),線程B要寫(xiě)a,做了三步操作中任何一步結(jié)束,線程A去讀a,a仍是一個(gè)有效值,只不過(guò)a并不一定是一個(gè)最新的值。情況一中線程A并不關(guān)心a是不是最新的值,只關(guān)心a是否由線程B寫(xiě)入的一個(gè)合法值就可以了,至于最新值可以通過(guò)循環(huán)去不斷刷新。 也許有人還有疑問(wèn),為什么第二步是一次執(zhí)行成功的呢?難得就不可能執(zhí)行一半就被其他線程搶去CPU了嗎?這個(gè)不會(huì),因?yàn)橛辛俗止?jié)對(duì)齊,一個(gè)讀周期或是一個(gè)寫(xiě)周期僅需要一個(gè)總線周期,在這個(gè)總線周期內(nèi)就把這個(gè)整型變量給處理了, 一個(gè)總線周期結(jié)束前CPU不會(huì)被搶占,就是中斷發(fā)生也不會(huì)導(dǎo)致一個(gè)總線周期執(zhí)行一半時(shí)CPU被搶占(CPU是在現(xiàn)行指令結(jié)束后響應(yīng)中斷,即運(yùn)行到最后一個(gè)指令周期中的最后一個(gè)總線周期中的最后一個(gè)T狀態(tài)時(shí)CPU才采樣INTR線來(lái)查看是否有中斷請(qǐng)求)。
情況二
線程A對(duì)a僅進(jìn)行讀操作,然后對(duì)a進(jìn)行判斷后執(zhí)行相應(yīng)的操作,如果這個(gè)過(guò)程中由于線程B對(duì)a進(jìn)行寫(xiě)操作導(dǎo)致a發(fā)生變化而影響這些操作,這種情況下,就需要加鎖。
加鎖的目的是為了保護(hù)數(shù)據(jù),如果不需要保護(hù)的情況下數(shù)據(jù)就本身就可以一致,就沒(méi)有必要加鎖, 啰嗦了。
轉(zhuǎn)載文章
分析很透徹的C/C++ 基本類(lèi)型及是否需要多線程鎖
單線程讀單線程寫(xiě)一個(gè)變量是否一定要加鎖