iOS鎖下

上篇文章介紹synchronized鎖汗侵,今天介紹的是其他常用鎖:NSLock,NSRecursiveLock ,NSCondition昆箕,NSConditionLock

鎖的概念

鎖的分類——互斥鎖鸦列,自旋鎖,讀寫鎖

自旋鎖
  • 自旋鎖是一種用于保護(hù)多線程共享資源的鎖鹏倘,與一般互斥鎖(mutex)不同之處在于當(dāng)它嘗試獲取鎖時(shí)以忙等待(busy waiting)的形式不斷地循環(huán)檢查鎖是否可用敛熬。當(dāng)上一個(gè)線程的任務(wù)沒(méi)有執(zhí)行完畢的時(shí)候(被鎖住),那么下一個(gè)線程會(huì)一直等待(不會(huì)睡眠)第股,當(dāng)上一個(gè)線程的任務(wù)執(zhí)行完畢应民,下一個(gè)線程會(huì)立即執(zhí)行。在多CPU的環(huán)境中夕吻,對(duì)持有鎖較短的程序來(lái)說(shuō)诲锹,使用自旋鎖代替一般的互斥鎖往往能夠提高程序的性能。

  • 優(yōu)點(diǎn):自旋鎖不會(huì)引起調(diào)用者睡眠涉馅,所以不會(huì)進(jìn)行線程調(diào)度归园、CPU時(shí)間片輪轉(zhuǎn)等耗時(shí)操作。所有如果能在很短的時(shí)間內(nèi)獲得鎖稚矿,自旋鎖的效率遠(yuǎn)高于互斥鎖

  • 缺點(diǎn):自旋鎖一直占用CPU庸诱,他在未獲得鎖的情況下一直運(yùn)行(自旋)占用著CPU,如果不能在很短的時(shí)間內(nèi)獲得鎖晤揣,這無(wú)疑會(huì)使CPU效率降低

總結(jié):效率高桥爽,但是一直占用CPU耗費(fèi)資源,不能實(shí)現(xiàn)遞歸調(diào)用昧识。

互斥鎖

什么是互斥鎖呢钠四?

  • 當(dāng)上一個(gè)線程的任務(wù)沒(méi)有執(zhí)行完畢的時(shí)候(被鎖住),那么下一個(gè)線程會(huì)進(jìn)入睡眠狀態(tài)等待任務(wù)執(zhí)行完畢跪楞,此時(shí)CPU可以調(diào)度其他線程缀去。當(dāng)上一個(gè)線程的任務(wù)執(zhí)行完畢,下一個(gè)線程會(huì)自動(dòng)喚醒然后執(zhí)行任務(wù)甸祭。
  • 說(shuō)到互斥后想到同步缕碎,同步是只有一個(gè)任務(wù)執(zhí)行完了,下個(gè)任務(wù)才可以執(zhí)行池户。

同步:互斥+順序

常見(jiàn)的互斥鎖:@synchronized咏雌,NSLock岳锁,pthread_mutex荸百,NSConditionLock(條件鎖)鲜戒,NSCondition(條件鎖)粒梦,NSRecursiveLock(遞歸鎖)
以上概念轉(zhuǎn)載自:https://juejin.cn/post/6999520963658268709/

鎖的歸類

條件鎖:就是條件變量擎场,當(dāng)進(jìn)程的某些資源要求不滿足時(shí)就進(jìn)入休眠书妻,也就是鎖住了啼县,當(dāng)資源被分配到了衰絮,條件鎖打開凝赛,進(jìn)程繼續(xù)運(yùn)行

  • NSCondition
  • NSConditionLock

遞歸鎖:就是同一個(gè)線程可以加鎖N次而不會(huì)引發(fā)死鎖

  • NSRecursiveLock
  • pthread_mutext(recursive)

信號(hào)量:是一種更高級(jí)的同步機(jī)制注暗,互斥鎖可以說(shuō)是semaphore在僅取值0/1時(shí)的特例坛缕。信號(hào)量可以有更多的取值空間,用來(lái)實(shí)現(xiàn)更加復(fù)雜的同步捆昏,而不單單是線程間互斥赚楚。

  • dispatch_semaphore

讀寫鎖

讀寫鎖實(shí)際是一種特殊的互斥鎖,他把對(duì)共享資源的訪問(wèn)分為讀者和寫者骗卜,讀者只有對(duì)共享資源的讀權(quán)限宠页,寫著可以對(duì)共享資源做寫處理。所以一般我們?cè)谧x的時(shí)候不會(huì)去加互斥鎖寇仓,允許多個(gè)讀者進(jìn)行訪問(wèn)举户,但是如果有遇到寫者訪問(wèn)資源,這時(shí)候必須對(duì)資源進(jìn)行加鎖遍烦,等其訪問(wèn)結(jié)束后才能繼續(xù)讀/寫
特性:

  • 多個(gè)讀者同時(shí)訪問(wèn)
  • 讀與寫互斥
  • 寫和寫互斥即單寫

上面都是一些概念俭嘁,接下來(lái)就開始詳細(xì)分析常用的鎖

NSLock,NSRecursiveLock

簡(jiǎn)單的鎖服猪,不能夠遞歸嵌套也不能多線程

- (void)testRecursive{
    NSLock *lock = [[NSLock alloc] init];
    for (int i= 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                // [lock lock];
                    if (value > 0) {
                        NSLog(@"current value = %d",value);
                        testMethod(value - 1);
                    }
               // [lock unlock];
            };
            [lock lock];
            testMethod(10);
            [lock unlock];
        });
    }
}

輸出:


image.png

這邊我們的鎖直接加在testMethod(10);等線程這邊遞歸完成后再繼續(xù)下個(gè)線程供填,但是有時(shí)候我們可能會(huì)加在遞歸函數(shù)里面,如上面的注釋罢猪,那就會(huì)造成死鎖近她,為什么會(huì)造成死鎖呢?在testMethod剛進(jìn)去加鎖坡脐,但是testMethod還沒(méi)出來(lái)又嵌套進(jìn)入到testMethod里又加鎖泄私,就造成了死鎖,解決辦法就是使用嵌套鎖NSRecursiveLock/synchronized

- (void)testRecursive{
    NSLock *lock = [[NSLock alloc] init];
    for (int i= 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                [self.recursiveLock lock];
                    if (value > 0) {
                        NSLog(@"current value = %d -- %@",value, [NSThread currentThread]);
                        testMethod(value - 1);
                    }
                [self.recursiveLock unlock];
            };
            testMethod(10);
        });
    }
}

輸出結(jié)果:
 current value = 10 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 9 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 8 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 7 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 6 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 5 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 4 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 3 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 2 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 1 -- <NSThread: 0x281bff740>{number = 3, name = (null)}

這邊使用NSRecursiveLock嘗試解決备闲,但是很遺憾還是崩,只是崩潰的不一樣捅暴,最后輸出的結(jié)果如上圖恬砂,為什么呢?因?yàn)?code>NSRecursiveLock不支持多線程蓬痒,如果只開一個(gè)線程就不會(huì)崩泻骤,但是換成synchronized就OK

- (void)testRecursive{
    NSLock *lock = [[NSLock alloc] init];
    for (int i= 0; i<2; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                @synchronized (self) {
                    if (value > 0) {
                        NSLog(@"current value = %d -- %@",value, [NSThread currentThread]);
                        testMethod(value - 1);
                    }
                }
            };
            testMethod(10);
        });
    }
}
輸出結(jié)果:
current value = 10 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 9 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 8 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 7 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 6 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 5 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 4 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 3 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 2 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 1 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 10 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 9 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 8 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 7 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 6 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 5 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 4 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 3 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 2 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 1 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}

接下來(lái)我們來(lái)看看NSLockNSRecursiveLock的源碼,因?yàn)?code>NSLock和NSRecursiveLock是在Foundation框架梧奢,這個(gè)是不開源的但是swiftFoundation框架是開源的狱掂,我們可以看看swiftFoundation框架

image.png

通過(guò)源碼就知道NSLock就是對(duì)mutex的封裝
image.png

這個(gè)也是對(duì)mutex的封裝,但是在初始化跟NSlock有所不同亲轨,pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))添加了嵌套屬性

NSCondition和NSConditionLock

NSCondition條件鎖主要運(yùn)用在生產(chǎn)著消費(fèi)者模型

- (void)testConditon{
    
    _testCondition = [[NSCondition alloc] init];
    //創(chuàng)建生產(chǎn)-消費(fèi)者
    for (int i = 0; i < 50; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_producer];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_consumer];
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_consumer];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_producer];
        });
    }
}

- (void)producer{
    [_testCondition lock]; // 操作的多線程影響
    self.ticketCount = self.ticketCount + 1;
    NSLog(@"生產(chǎn)一個(gè) 現(xiàn)有 count %zd",self.ticketCount);
    [_testCondition signal]; // 信號(hào)
    [_testCondition unlock];
}

- (void)consumer{
 
     [_testCondition lock];  // 操作的多線程影響
    if (self.ticketCount == 0) {
        NSLog(@"等待 count %zd",self.ticketCount);
        [_testCondition wait];
    }
    //注意消費(fèi)行為趋惨,要在等待條件判斷之后
    self.ticketCount -= 1;
    NSLog(@"消費(fèi)一個(gè) 還剩 count %zd ",self.ticketCount);
     [_testCondition unlock];
}

當(dāng)消費(fèi)者消費(fèi)到?jīng)]有物品時(shí)就會(huì)處于等待,而生產(chǎn)者每生產(chǎn)一個(gè)產(chǎn)品都會(huì)通知一下消費(fèi)者惦蚊,讓那些處于等待狀態(tài)的消費(fèi)者及時(shí)消費(fèi)器虾。
底層是對(duì)cond的封裝
NSConditionLock 的使用

NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        [conditionLock lockWhenCondition:1];
        NSLog(@"線程 1");
        [conditionLock unlockWithCondition:0];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        [conditionLock lockWhenCondition:2];
        sleep(0.1);
        NSLog(@"線程 2");
        [conditionLock unlockWithCondition:1];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       [conditionLock lock];
       NSLog(@"線程 3");
       [conditionLock unlock];
    });

輸出:
線程 3
線程 2
線程 1

通過(guò)lockWhenCondition控制線程的執(zhí)行順序讯嫂,這邊預(yù)先設(shè)置condition為2,因?yàn)榫€程2的條件是[conditionLock lockWhenCondition:2]滿足條件會(huì)比線程1優(yōu)先執(zhí)行兆沙,執(zhí)行完[conditionLock unlockWithCondition:1];設(shè)置為1欧芽,這樣線程1就滿足條件,等待cpu調(diào)度
葛圃。

讀寫鎖

之前介紹的讀寫鎖千扔,多個(gè)線程可以同時(shí)對(duì)資源進(jìn)行讀操作不用加鎖,但是寫操作是需要加鎖库正。這樣我們就可以通過(guò)GCD的柵欄函數(shù)來(lái)實(shí)現(xiàn)

- (id)init{
    self = [super init];
    if (self){
        // 創(chuàng)建一個(gè)并發(fā)隊(duì)列:
        self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
        // 創(chuàng)建數(shù)據(jù)字典:
        self.dataCenterDic = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)getObjc {
    for (int i=0; i<5; i++) {
        dispatch_async(self.concurrent_queue, ^{
            // 多個(gè)線程讀取數(shù)據(jù)
            [self objectForKey:@"a"];
        });
    }
}

#pragma mark - 讀數(shù)據(jù)
- (id)objectForKey:(NSString *)key{
    __block id obj;
    // 同步讀取指定數(shù)據(jù):
    dispatch_sync(self.concurrent_queue, ^{
        obj = [self.dataCenterDic objectForKey:key];
    });
    return obj;
}

#pragma mark - 寫數(shù)據(jù)
- (void)setObject:(id)obj forKey:(NSString *)key{
    // 異步柵欄調(diào)用設(shè)置數(shù)據(jù):
    dispatch_barrier_async(self.concurrent_queue, ^{
        [self.dataCenterDic setObject:obj forKey:key];
    });
}

注意:同步讀取指定數(shù)據(jù)之所以要加dispatch_sync是為了滿足讀和寫的互斥

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昏鹃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诀诊,更是在濱河造成了極大的恐慌洞渤,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件属瓣,死亡現(xiàn)場(chǎng)離奇詭異载迄,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)抡蛙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門护昧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人粗截,你說(shuō)我怎么就攤上這事惋耙。” “怎么了熊昌?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵绽榛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我婿屹,道長(zhǎng)灭美,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任昂利,我火速辦了婚禮届腐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蜂奸。我一直安慰自己犁苏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布扩所。 她就那樣靜靜地躺著围详,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碌奉。 梳的紋絲不亂的頭發(fā)上短曾,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天寒砖,我揣著相機(jī)與錄音,去河邊找鬼嫉拐。 笑死哩都,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的婉徘。 我是一名探鬼主播漠嵌,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盖呼!你這毒婦竟也來(lái)了儒鹿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤几晤,失蹤者是張志新(化名)和其女友劉穎约炎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蟹瘾,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡圾浅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了憾朴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狸捕。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖众雷,靈堂內(nèi)的尸體忽然破棺而出灸拍,到底是詐尸還是另有隱情,我是刑警寧澤砾省,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布鸡岗,位于F島的核電站,受9級(jí)特大地震影響纯蛾,放射性物質(zhì)發(fā)生泄漏纤房。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一翻诉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捌刮,春花似錦碰煌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至俄认,卻和暖如春个少,著一層夾襖步出監(jiān)牢的瞬間洪乍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工夜焦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留壳澳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓茫经,卻偏偏與公主長(zhǎng)得像巷波,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子卸伞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容