多線程的鎖與信號(hào)量

多線程中涩惑,鎖大部分可以分成兩種,互斥鎖與自旋鎖桑驱。

  • 互斥鎖 Mutex
    互斥鎖也稱互斥量 竭恬,屬于sleep-waiting類型的鎖,當(dāng)線程訪問被鎖資源時(shí)熬的,調(diào)用者線程會(huì)休眠痊硕,此時(shí)cpu可以調(diào)度其他線程工作。直到被鎖資源釋釋放鎖押框。此時(shí)會(huì)喚醒休眠線程岔绸。互斥鎖的加鎖與解鎖操作回設(shè)計(jì)系統(tǒng)線程的調(diào)度與上下文切換橡伞。

  • 自旋鎖 Spinlock
    屬于busy-wait 類型的鎖,調(diào)用者線程反復(fù)檢查鎖變量是否可用盒揉。由于線程在這一過程中保持執(zhí)行,因此是一種忙等待兑徘。一旦獲取了自旋鎖刚盈,線程會(huì)一直保持該鎖,直至顯式釋放自旋鎖挂脑。自旋鎖會(huì)比互斥鎖更加消耗CPU藕漱,自旋鎖的效率也會(huì)比互斥鎖更高欲侮。

  • 死鎖
    兩個(gè)運(yùn)行單元都在等待對(duì)方停止運(yùn)行,以獲取系統(tǒng)資源肋联,但是沒有一方提前退出時(shí)威蕉,就稱為死鎖

  • 加鎖與解鎖
    加鎖其實(shí)就是獲得鎖,獲得這個(gè)這個(gè)資源的訪問權(quán)限橄仍,解鎖就是釋放鎖韧涨,其他線程就可以訪問。

iOS的鎖

在iOS中沙兰,實(shí)現(xiàn)鎖有多種方式氓奈,一般有

  • @synchronized 同步代碼塊
@synchronized(self) {//入?yún)elf為所要保護(hù)的對(duì)象,內(nèi)部其實(shí)是一個(gè)遞歸鎖
// task
}

@synchronized可以很方便就給對(duì)象加鎖鼎天,不用再額外聲明鎖舀奶,手動(dòng)加鎖和釋放鎖。需要保護(hù)的代碼寫在block即可斋射,但是效率較慢育勺。

  • NSLock 對(duì)象鎖
  NSLock *mutexLock = [[NSLock alloc] init];
  [mutexLock lock];
  //task
  [mutexLock unlock];
  • NSRecursiveLock 遞歸鎖
    NSLock在遞歸中容易產(chǎn)生死鎖,使用NSRecursiveLock可以避免這個(gè)問題罗岖,NSRecursiveLock可以在被同一線程重復(fù)獲取時(shí)不會(huì)產(chǎn)生死鎖涧至。

  • NSConditionLock 條件鎖
    滿足預(yù)設(shè)的條件,就可以獲取鎖

  • OSSpinLock 自旋鎖
    一般的互斥鎖沒有拿到鎖之前桑包,線程都會(huì)休眠南蓬,而自旋鎖不會(huì)引起調(diào)用者線程休眠,調(diào)用者就一直循環(huán)在那里看是否該自旋鎖的保持者已經(jīng)釋放了鎖哑了。

  • pthread_mutex C語言實(shí)現(xiàn)的互斥鎖

從效率上看赘方,自然是OSSpinLock自旋鎖的效率最高,執(zhí)行耗時(shí)最小弱左,但是OSSpinLock會(huì)出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)的問題窄陡,目前OSSpinLock的內(nèi)部實(shí)現(xiàn)已經(jīng)被os_unfair_lock代替。等待os_unfair_lock鎖的其他線程會(huì)處于休眠狀態(tài)拆火,而并非忙等跳夭。

需要注意的是,iOS中的鎖目前按照功能來分基本是兩種们镜,自旋鎖和互斥鎖币叹,其他的鎖基本本質(zhì)是互斥鎖,大體都是封裝pthread_mutex而來憎账。

typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock);

image.png

優(yōu)先級(jí)反轉(zhuǎn)

在 iOS 中,高優(yōu)先級(jí)high priority線程始終會(huì)在低優(yōu)先級(jí)(low priority )線程前執(zhí)行套硼,一個(gè)線程不會(huì)受到比它更低優(yōu)先級(jí)low priority線程的干擾。具體來說胞皱,如果一個(gè)低優(yōu)先級(jí)的線程獲得鎖并訪問共享資源邪意,這時(shí)一個(gè)高優(yōu)先級(jí)high priority的線程也嘗試獲得這個(gè)鎖九妈,它會(huì)處于 spin lock 的忙等狀態(tài)從而占用大量 CPU。此時(shí)低優(yōu)先級(jí)線程無法與高優(yōu)先級(jí)線程爭奪 CPU 時(shí)間片雾鬼,從而導(dǎo)致任務(wù)遲遲完不成萌朱、無法釋放 lock。除非開發(fā)者能保證訪問鎖的線程全部都處于同一優(yōu)先級(jí)策菜,否則 iOS 系統(tǒng)中所有類型的自旋鎖都不能再使用了晶疼。

CPU調(diào)度

一般來說線程是程序執(zhí)行的最小單元,一個(gè)線程包括:獨(dú)有ID又憨,程序計(jì)數(shù)器翠霍,寄存器集合,堆棧蠢莺。同一進(jìn)程可以有多個(gè)線程寒匙,它們共享進(jìn)程的全局變量和堆數(shù)據(jù)。CPU的核心在同一個(gè)時(shí)刻只能執(zhí)行一條線程躏将,如果要執(zhí)行的線程超過CPU的核心數(shù)時(shí)锄弱,就需要線程調(diào)度,簡單來說就是:一個(gè) CPU 核心輪流讓各個(gè)線程分別執(zhí)行一段時(shí)間祸憋。上一點(diǎn)所述会宪,在線程調(diào)度里面,高優(yōu)先級(jí)的線程會(huì)優(yōu)先獲得CPU時(shí)間片蚯窥。線程調(diào)度還有其他算法掸鹅,如FIFO,先排隊(duì)的線程獲得運(yùn)行的CPU時(shí)間片拦赠。CPU調(diào)度使得等待的線程可以運(yùn)行河劝,這樣的切換同時(shí)也會(huì)伴隨著上下文的切換(寄存器數(shù)據(jù)、棧等)矛紫,過多的上下文切換會(huì)帶來資源開銷。

公平鎖與非公平鎖

上述所說替換自旋鎖OSSpinLockos_unfair_lock牌里,其實(shí)是非公平鎖颊咬,平時(shí)使用的鎖基本都是公平鎖,這一類鎖有著FIFO的特性,
多個(gè)線程情況下排隊(duì)牡辽,先到先獲得鎖喳篇。如果進(jìn)入等待的順序?yàn)?2345,則最后等待結(jié)束被執(zhí)行的順序也是12345态辛。但是如果使用的是非公平鎖麸澜,前面的任務(wù)馬上要執(zhí)行完畢,若釋放鎖的時(shí)候奏黑,正好一個(gè)新的線程6來訪問資源炊邦,而此時(shí)位于隊(duì)列頭的線程1還沒有被喚醒(因?yàn)榫€程上下文切換是需要不少開銷的)编矾,此時(shí)后來的線程6則優(yōu)先獲得鎖,成功打破公平馁害,成為非公平鎖窄俏。但是如果線程6來訪問時(shí),鎖不是剛好釋放碘菜,或者線程1已經(jīng)被喚醒凹蜈,線程6還是得進(jìn)入線程隊(duì)列中休眠等待CPU的喚醒執(zhí)行。

信號(hào)量(Semaphore)

信號(hào)量是一個(gè)同步對(duì)象忍啸,用于保持在0至指定最大值之間的一個(gè)計(jì)數(shù)值仰坦。當(dāng)線程完成一次對(duì)該semaphore對(duì)象的等待(wait)時(shí),該計(jì)數(shù)值(- 1)加鎖计雌;當(dāng)線程完成一次對(duì)semaphore對(duì)象的釋放(release)時(shí)悄晃,計(jì)數(shù)值(+ 1)釋放鎖。簡單來說白粉,信號(hào)量為0的時(shí)候传泊,線程會(huì)阻塞,一直等待該信號(hào)量對(duì)象的計(jì)數(shù)值變成大于0的狀態(tài)鸭巴。

如在OC中眷细,
關(guān)于信號(hào)量主要有三個(gè)函數(shù):

  • dispatch_semaphore_create(long value);
    創(chuàng)建信號(hào)量,參數(shù)為設(shè)置信號(hào)量的初始值
  • dispatch_semaphore_signal(dispatch_semaphore_t dsema);
    發(fā)送當(dāng)前信號(hào)量鹃祖,參數(shù)為當(dāng)前創(chuàng)建的信號(hào)量
  • dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
    等待信號(hào)量溪椎,第一個(gè)為當(dāng)前等待的信號(hào)量,第二個(gè)參數(shù)為超時(shí)時(shí)間恬口。當(dāng)?shù)却龝r(shí)間超過超時(shí)時(shí)間就不會(huì)繼續(xù)等待了校读。

信號(hào)量是一個(gè)整型值,在創(chuàng)建的時(shí)候會(huì)有一個(gè)初始值祖能。當(dāng)執(zhí)行dispatch_semaphore_signal發(fā)送信號(hào)的時(shí)候信號(hào)量會(huì)加1歉秫,dispatch_semaphore_wait在信號(hào)量小于或等于0的時(shí)候會(huì)一直等待,直到超時(shí)养铸,并且會(huì)阻塞該線程雁芙,當(dāng)信號(hào)量大于0時(shí)會(huì)繼續(xù)執(zhí)行并對(duì)信號(hào)量執(zhí)行減1操作。

  dispatch_queue_t queue = 
  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  dispatch_semaphore_t lock = dispatch_semaphore_create(0);//信號(hào)量初始為0
  dispatch_async(queue, ^{
      // task1
      dispatch_semaphore_signal(lock);// 使信號(hào)量+1并返回
  });
  dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);//堵塞當(dāng)前線程钞螟,等待task1結(jié)束兔甘,信號(hào)量加1,釋放鎖鳞滨。
  // task2 
  //只有當(dāng)task1執(zhí)行完,信號(hào)量+1之后,才會(huì)執(zhí)行這里

數(shù)值為N的信號(hào)量允許N個(gè)線程并發(fā)訪問洞焙。

如果信號(hào)量是一個(gè)任意的整數(shù),通常被稱為計(jì)數(shù)信號(hào)量(Counting semaphore),或一般信號(hào)量(general semaphore)澡匪;如果信號(hào)量只有二進(jìn)制的0或1熔任,稱為二進(jìn)制信號(hào)量(binary semaphore)。在linux系統(tǒng)中仙蛉,二進(jìn)制信號(hào)量(binary semaphore)又稱互斥量(Mutex)笋敞。

所以說,互斥量和信號(hào)量本質(zhì)上一樣荠瘪,都是用來表示對(duì)資源的訪問權(quán)夯巷,但是互斥量表示資源某個(gè)時(shí)刻最多只能被一個(gè)線程占用,也就是資源計(jì)數(shù)最多是1哀墓,而信號(hào)量的資源計(jì)數(shù)可以超過1趁餐,即同時(shí)被多個(gè)線程占用。

兩者對(duì)比的話篮绰,簡單來說后雷,

  • 互斥量就是N個(gè)線程,爭奪一把鎖吠各,
  • 信號(hào)量就是N個(gè)線程臀突,爭奪M把鎖,當(dāng)信號(hào)量的計(jì)數(shù)值只有0贾漏、1(二進(jìn)制信號(hào)量)候学,那么M = 1,這時(shí)候纵散,和互斥量的性質(zhì)是一樣的梳码。

信號(hào)量死鎖問題

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self testSemaphore];
}

/// 主線程運(yùn)行
- (void)testSemaphore
{
    NSLog(@"1");
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
            dispatch_async(dispatch_get_main_queue(), ^{//主線程
                sleep(1);
                NSLog(@"2");
                dispatch_semaphore_signal(semaphore);
            });
    NSLog(@"3");
    //堵塞當(dāng)前線程
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"4");
}

這時(shí)候輸出

2021-05-15 23:28:12.156395+0800 TestProject[10564:1787212] 1
2021-05-15 23:28:12.156641+0800 TestProject[10564:1787212] 3

就死鎖了,兩個(gè)運(yùn)行單元出現(xiàn)互相等待的情況伍掀。因?yàn)閐ispatch_semaphore_wait卡主了主線程掰茶,而dispatch_async(dispatch_get_main_queue()又是在主線程中運(yùn)行,需要等里面的block運(yùn)行結(jié)束蜜笤,信號(hào)量+1濒蒋,釋放鎖后,wait才會(huì)結(jié)束把兔。解決這個(gè)問題啊胶,可以把避免在主線程中執(zhí)行Block,或者wait的時(shí)間可以手動(dòng)設(shè)置短一些垛贤。

參考文章

https://zh.wikipedia.org/wiki/%E4%BF%A1%E8%99%9F%E6%A8%99
https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市趣倾,隨后出現(xiàn)的幾起案子聘惦,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件善绎,死亡現(xiàn)場(chǎng)離奇詭異黔漂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)禀酱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門炬守,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人剂跟,你說我怎么就攤上這事减途。” “怎么了曹洽?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵鳍置,是天一觀的道長。 經(jīng)常有香客問我送淆,道長税产,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任偷崩,我火速辦了婚禮辟拷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘阐斜。我一直安慰自己衫冻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布智听。 她就那樣靜靜地躺著羽杰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪到推。 梳的紋絲不亂的頭發(fā)上考赛,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音莉测,去河邊找鬼颜骤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛捣卤,可吹牛的內(nèi)容都是我干的忍抽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼董朝,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼鸠项!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起子姜,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤祟绊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牧抽,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嘉熊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扬舒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阐肤。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖讲坎,靈堂內(nèi)的尸體忽然破棺而出孕惜,到底是詐尸還是另有隱情,我是刑警寧澤衣赶,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布诊赊,位于F島的核電站,受9級(jí)特大地震影響府瞄,放射性物質(zhì)發(fā)生泄漏碧磅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一遵馆、第九天 我趴在偏房一處隱蔽的房頂上張望鲸郊。 院中可真熱鬧,春花似錦货邓、人聲如沸秆撮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽职辨。三九已至,卻和暖如春戈二,著一層夾襖步出監(jiān)牢的瞬間舒裤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工觉吭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腾供,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓鲜滩,卻偏偏與公主長得像伴鳖,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子徙硅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345