1、關(guān)于高并發(fā)的幾個(gè)重要概念
1.1 同步和異步
首先這里說(shuō)的同步和異步是指函數(shù)/方法調(diào)用方面。
很明顯,同步調(diào)用會(huì)等待方法的返回,異步調(diào)用會(huì)瞬間返回,但是異步調(diào)用瞬間返回并不代表你的任務(wù)就完成了,他會(huì)在后臺(tái)起個(gè)線程繼續(xù)進(jìn)行任務(wù)。
1.2 并發(fā)和并行
并發(fā)和并行在外在表象來(lái)說(shuō),是差不多的。由圖所示,并行則是兩個(gè)任務(wù)同時(shí)進(jìn)行,而并發(fā)呢,則是一會(huì)做一個(gè)任務(wù)一會(huì)又切換做另一個(gè)任務(wù)。所以單個(gè)cpu是不能做并行的,只能是并發(fā)。
1.3 臨界區(qū)
臨界區(qū)用來(lái)表示一種公共資源或者說(shuō)是共享數(shù)據(jù),可以被多個(gè)線程使用,但是每一次,只能有一個(gè)線程使用它,一旦臨界區(qū)資源被占用,其他線程要想使用這個(gè)資源,就必須等待。
1.4 阻塞和非阻塞
阻塞和非阻塞通常形容多線程間的相互影響。比如一個(gè)線程占用了臨界區(qū)資源,那么其它所有需要這個(gè)資源的線程就必須在這個(gè)臨界區(qū)中進(jìn)行等待,等待會(huì)導(dǎo)致線程掛起。這種情況就是阻塞。此時(shí),如果占用資源的線程一直不愿意釋放資源,那么其它所有阻塞在這個(gè)臨界區(qū)上的線程都不能工作。
非阻塞允許多個(gè)線程同時(shí)進(jìn)入臨界區(qū)
所以阻塞的方式,一般性能不會(huì)太好。根據(jù)一般的統(tǒng)計(jì),如果一個(gè)線程在操作系統(tǒng)層面被掛起,做了上下文切換了,通常情況需要8W個(gè)時(shí)間周期來(lái)做這個(gè)事情。
1.5 死鎖、饑餓、活鎖
所謂死鎖:是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中,由于競(jìng)爭(zhēng)資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去。此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。就如同下圖中的車都想前進(jìn),卻誰(shuí)都無(wú)法前進(jìn)。
但是死鎖雖說(shuō)是不好的現(xiàn)象,但是它是一個(gè)靜態(tài)的問(wèn)題,一旦發(fā)生死鎖,進(jìn)程被卡死,cpu占有率也是0,它不會(huì)占用cpu,它會(huì)被調(diào)出去。相對(duì)來(lái)說(shuō)還是比較好發(fā)現(xiàn)和分析的。
與死鎖相對(duì)應(yīng)的是活鎖。
活鎖,指事物1可以使用資源,但它讓其他事物先使用資源;事物2可以使用資源,但它也讓其他事物先使用資源,于是兩者一直謙讓,都無(wú)法使用資源。
舉個(gè)例子,就如同你在街上遇到個(gè)人,剛好他朝著你的反方向走,與你正面碰到,你們都想讓彼此過(guò)去。你往左邊移,他也往左邊移,兩人還是無(wú)法過(guò)去。這時(shí)你往右邊移,他也往右邊移,如此循環(huán)下去。
一個(gè)線程在取得了一個(gè)資源時(shí),發(fā)現(xiàn)其他線程也想到這個(gè)資源,因?yàn)闆](méi)有得到所有的資源,為了避免死鎖把自己持有的資源都放棄掉。如果另外一個(gè)線程也做了同樣的事情,他們需要相同的資源,比如A持有a資源,B持有b資源,放棄了資源以后,A又獲得了b資源,B又獲得了a資源,如此反復(fù),則發(fā)生了活鎖。
活鎖會(huì)比死鎖更難發(fā)現(xiàn),因?yàn)榛铈i是一個(gè)動(dòng)態(tài)的過(guò)程。
饑餓是指某一個(gè)或者多個(gè)線程因?yàn)榉N種原因無(wú)法獲得所需要的資源,導(dǎo)致一直無(wú)法執(zhí)行。
1.6 并發(fā)級(jí)別
并發(fā)級(jí)別:阻塞和非阻塞(非阻塞分為無(wú)障礙、無(wú)鎖、無(wú)等待)
1.6.1 阻塞
當(dāng)一個(gè)線程進(jìn)入臨界區(qū)后,其他線程必須等待
1.6.2 無(wú)障礙
-
無(wú)障礙是一種最弱的非阻塞調(diào)度
-
自由出入臨界區(qū)
-
無(wú)競(jìng)爭(zhēng)時(shí),有限步內(nèi)完成操作
-
有競(jìng)爭(zhēng)時(shí),回滾數(shù)據(jù)
和非阻塞調(diào)度相比呢,阻塞調(diào)度是一種悲觀的策略,它會(huì)認(rèn)為說(shuō)一起修改數(shù)據(jù)是很有可能把數(shù)據(jù)改壞的。而非阻塞調(diào)度呢,是一種樂(lè)觀的策略,它認(rèn)為大家修改數(shù)據(jù)未必把數(shù)據(jù)改壞。但是它是一種寬進(jìn)嚴(yán)出的策略,當(dāng)它發(fā)現(xiàn)一個(gè)進(jìn)程在臨界區(qū)內(nèi)發(fā)生了數(shù)據(jù)競(jìng)爭(zhēng),產(chǎn)生了沖突,那么無(wú)障礙的調(diào)度方式則會(huì)回滾這條數(shù)據(jù)。
在這個(gè)無(wú)障礙的調(diào)度方式當(dāng)中,所有的線程都相當(dāng)于在拿去一個(gè)系統(tǒng)當(dāng)前的一個(gè)快照。他們一直會(huì)嘗試拿去的快照是有效的為止。
1.6.3 無(wú)鎖
是無(wú)障礙的
保證有一個(gè)線程可以勝出
與無(wú)障礙相比,無(wú)障礙并不保證有競(jìng)爭(zhēng)時(shí)一定能完成操作,因?yàn)槿绻l(fā)現(xiàn)每次操作都會(huì)產(chǎn)生沖突,那它則會(huì)不停地嘗試。如果臨界區(qū)內(nèi)的線程互相干擾,則會(huì)導(dǎo)致所有的線程會(huì)卡死在臨界區(qū),那么系統(tǒng)性能則會(huì)有很大的影響。
而無(wú)鎖增加了一個(gè)新的條件,保證每次競(jìng)爭(zhēng)有一個(gè)線程可以勝出,則解決了無(wú)障礙的問(wèn)題。至少保證了所有線程都順利執(zhí)行下去。
下面代碼是Java中典型的無(wú)鎖計(jì)算代碼
無(wú)鎖在Java中很常見(jiàn)
1
2
3
4
|
while (!atomicVar.compareAndSet(localVar, localVar+ 1 )) { localVar = atomicVar.get(); } |
1.6.4 無(wú)等待
無(wú)鎖的
要求所有的線程都必須在有限步內(nèi)完成
無(wú)饑餓的
首先無(wú)等待的前提是無(wú)鎖的基礎(chǔ)上的,無(wú)鎖它只保證了臨界區(qū)肯定有進(jìn)也有出,但是如果進(jìn)的優(yōu)先級(jí)都很高,那么臨界區(qū)內(nèi)的某些優(yōu)先級(jí)低的線程可能發(fā)生饑餓,一直出不了臨界區(qū)。那么無(wú)等待解決了這個(gè)問(wèn)題,它保證所有的線程都必須在有限步內(nèi)完成,自然是無(wú)饑餓的。
無(wú)等待是并行的最高級(jí)別,它能使這個(gè)系統(tǒng)達(dá)到最優(yōu)狀態(tài)。
無(wú)等待的典型案例:
如果只有讀線程,沒(méi)有線線程,那么這個(gè)則必然是無(wú)等待的。
如果既有讀線程又有寫線程,而每個(gè)寫線程之前,都把數(shù)據(jù)拷貝一份副本,然后修改這個(gè)副本,而不是修改原始數(shù)據(jù),因?yàn)樾薷母北荆瑒t沒(méi)有沖突,那么這個(gè)修改的過(guò)程也是無(wú)等待的。最后需要做同步的只是將寫完的數(shù)據(jù)覆蓋原始數(shù)據(jù)。
由于無(wú)等待要求比較高,實(shí)現(xiàn)起來(lái)比較困難,所以無(wú)鎖使用得會(huì)更加廣泛一些。
2. 有關(guān)并行的兩個(gè)重要定律
這兩個(gè)定律都與加速比有關(guān)
2.1 Amdahl定律
定義了串行系統(tǒng)并行化后的加速比的計(jì)算公式和理論上限
加速比定義:加速比=優(yōu)化前系統(tǒng)耗時(shí)/優(yōu)化后系統(tǒng)耗時(shí)
舉個(gè)例子:
加速比=優(yōu)化前系統(tǒng)耗時(shí)/優(yōu)化后系統(tǒng)耗時(shí)=500/400=1.25
這個(gè)定理表明:增加CPU處理器的數(shù)量并不一定能起到有效的作用 提高系統(tǒng)內(nèi)可并行化的模塊比重,合理增加并行處理器數(shù)量,才能以最小的投入,得到最大的加速比。
2.2 Gustafson定律
說(shuō)明處理器個(gè)數(shù),串行比例和加速比之間的關(guān)系
則加速比=n-F(n-1) //推導(dǎo)過(guò)程略
只要有足夠的并行化,那么加速比和CPU個(gè)數(shù)成正比