8. iOS中的多線程——鎖

題記:雖然有些事情的發(fā)生可能是你預(yù)料之中的耸序,但是當(dāng)它真正的發(fā)生了的時(shí)候忍些,還是很難以接受的,還是需要一點(diǎn)時(shí)間坎怪,去緩和這種消極的情緒罢坝,盡快站起來(lái)吧!加油搅窿!花了一天半的時(shí)間嘁酿,各種查閱資料,總結(jié)了iOS中關(guān)于線程鎖的知識(shí)男应,希望我能從中學(xué)到一些闹司,也希望可以幫到同樣有需要的你!(文中如有錯(cuò)誤沐飘,還請(qǐng)?zhí)岢鲇巫黄鸾涣鳎?/em>


本文主要介紹:

  • 互斥鎖
  • 遞歸鎖
  • 讀寫(xiě)鎖
  • 自旋鎖
  • 分布鎖
  • 條件變量
  • 信號(hào)量
  • 柵欄
  • 一些常用鎖的性能。
1. 互斥鎖(Mutex)

常用薪铜,當(dāng)一個(gè)線程試圖獲取被另一個(gè)線程占用的鎖時(shí)众弓,它就會(huì)被掛起,讓出CPU隔箍,直到該鎖被釋放。

  • 互斥鎖的實(shí)現(xiàn)方式:
    • @synchronized:實(shí)現(xiàn)單例模式
    • NSLock:不能迭代加鎖脚乡,如果發(fā)生兩次lock蜒滩,而未unlock過(guò)滨达,則會(huì)產(chǎn)生死鎖問(wèn)題。

1.@synchronized 同步鎖

  • 例程:
/**
 *設(shè)置屬性值
 */
-(void)setMyTestString:(NSString *)myTestString{
    @synchronized(self) {
        // todo something
        _myTestString = myTestString;
    }
}
  • 常用于單例模式的設(shè)計(jì):

    例程:

+(instancetype)shareInstance{
    // 1.定義一個(gè)靜態(tài)實(shí)例俯艰,初值nil
    static TestSynchronized *myClass = nil;
    // 2.添加同步鎖捡遍,創(chuàng)建實(shí)例
    @synchronized(self) {
        // 3.判斷實(shí)例是否創(chuàng)建過(guò),創(chuàng)建過(guò)則退出同步鎖竹握,直接返回該實(shí)例
        if (!myClass) {
            // 4.未創(chuàng)建過(guò)画株,則新建一個(gè)實(shí)例并返回
            myClass = [[self alloc] init];
        }
    }
    return myClass;
}
此時(shí)為了保證單例模式的更加嚴(yán)謹(jǐn),需要重寫(xiě)`allocWithZone`方法啦辐,保證其他開(kāi)發(fā)者使用`alloc`和`init`方法時(shí)谓传,不再創(chuàng)建新的對(duì)象。必要的時(shí)候還需要重寫(xiě)`copyWithZone`方法防止`copy`屬性對(duì)單例模式的影響芹关。

iOS中還有一種更加輕便的方法實(shí)現(xiàn)單例模式续挟,即使用GCD中的dispatch_once函數(shù)實(shí)現(xiàn)。

例程:

+(instancetype)shareInstance{
    static TestSynchronized *myClass = nil;
    static dispatch_once_t once_token;
    dispatch_once(&once_token, ^{
        myClass = [[self alloc] init];
    });
    return myClass;
}

2.NSLock

  • 例程:
static NSLock *mylock;
-(void)viewDidLoad {
    [super viewDidLoad];
    mylock = [[NSLock alloc] init];
}
-(void)myLockTest1{
    if ([mylock tryLock]) {
        // to do something
        [mylock unlock];
    }
}
-(void)myLockTest2{
    [mylock lock];
    // to do something
    [mylock unlock];
}
2. 遞歸鎖(Recursive Lock)
  • 遞歸鎖可以被同一線程多次請(qǐng)求侥衬,而不會(huì)引起死鎖诗祸,即在多次被同一個(gè)線程進(jìn)行加鎖時(shí),不會(huì)造成死鎖轴总。這主要是用在循環(huán)或遞歸操作中直颅。

  • 可以允許同一線程多次加鎖,而不會(huì)造成死鎖怀樟。

  • 遞歸鎖會(huì)跟蹤它被lock的次數(shù)际乘。每次成功的lock都必須平衡調(diào)用unlock操作。只有所有達(dá)到這種平衡漂佩,鎖最后才能被釋放脖含,以供其它線程使用。

  • 例程:

NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^MyRecursiveLockBlk)(int value);
        MyRecursiveLockBlk = ^(int value){
            [myRecursiveLock lock];
            if (value > 0) {
                // to do something
                NSLog(@"MyRecursiveLockBlk value = %d", value);
                MyRecursiveLockBlk(value - 1);
            }
            [myRecursiveLock unlock];
        };
        MyRecursiveLockBlk(6);
    });

此時(shí)如果將例程中的遞歸鎖換成互斥鎖:
NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];換成
NSLock *myLock = [[NSLock alloc] init];投蝉,則會(huì)發(fā)生死鎖問(wèn)題养葵。

3. 讀寫(xiě)鎖(Read-write Lock)
  • 讀寫(xiě)鎖將訪問(wèn)者分為讀出寫(xiě)入兩種,當(dāng)讀寫(xiě)鎖在讀加鎖模式下瘩缆,所有以讀加鎖方式訪問(wèn)該資源時(shí)关拒,都會(huì)獲得訪問(wèn)權(quán)限,而所有試圖以寫(xiě)加鎖方式對(duì)其加鎖的線程都將阻塞庸娱,直到所有的讀鎖釋放着绊。

  • 當(dāng)在寫(xiě)加鎖模式下,所有試圖對(duì)其加鎖的線程都將阻塞熟尉。

  • 例程:

#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property(nonatomic, copy) NSString *rwStr;
@end
@implementation ViewController
pthread_rwlock_t rwlock;
-(void)viewDidLoad {
    [super viewDidLoad];
    // 初始化讀寫(xiě)鎖
    pthread_rwlock_init(&rwlock,NULL);
    __block int i;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        i = 5;
        while (i>=0) {
            NSString *temp = [NSString stringWithFormat:@"writing == %d", i];
            [self writingLock:temp];
            i--;
        }  
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        i = 5;
        while (i>=0) {
            [self readingLock];
            i--;
        }
    });
}
// 寫(xiě)加鎖
-(void)writingLock:(NSString *)temp{
    pthread_rwlock_wrlock(&rwlock);
    // writing
    self.rwStr = temp;
    NSLog(@"%@", temp);
    pthread_rwlock_unlock(&rwlock);
}
// 讀加鎖
-(NSString *)readingLock{
    pthread_rwlock_rdlock(&rwlock);
    // reading
    NSString *str = self.rwStr;
    NSLog(@"reading == %@",self.rwStr);
    pthread_rwlock_unlock(&rwlock);
    return str;
}
@end
4. 自旋鎖(Spin Lock)
  • 自旋鎖與互斥鎖類(lèi)似

  • 但不同的是:自旋鎖是非阻塞的归露,當(dāng)一個(gè)線程無(wú)法獲取自旋鎖時(shí),會(huì)自旋斤儿,直到該鎖被釋放剧包,等待的過(guò)程中線程并不會(huì)掛起恐锦。(實(shí)質(zhì)上就是,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持疆液,調(diào)用者就一直循環(huán)在等待該自旋鎖的保持著已經(jīng)釋放了鎖)一铅。

  • 自旋鎖的使用者一般保持鎖的時(shí)間很短,此時(shí)其效率遠(yuǎn)高于互斥鎖堕油。

  • 自旋鎖保持期間是搶占失效的

    優(yōu)點(diǎn):效率高潘飘,不用進(jìn)行線程的切換

    缺點(diǎn):如果一個(gè)線程霸占鎖的時(shí)間過(guò)長(zhǎng),自旋會(huì)消耗CPU資源

  • 例程:

// 頭文件
#import <libkern/OSAtomic.h>
// 初始化自旋鎖
static OSSpinLock myLock = OS_SPINLOCK_INIT;
// 自旋鎖的使用
-(void)SpinLockTest{
    OSSpinLockLock(&myLock);
    // to do something
    OSSpinLockUnlock(&myLock);
}
5. 分布鎖(Didtributed Lock)
  • 跨進(jìn)程的分布式鎖掉缺,是進(jìn)程間同步的工具卜录,底層是用文件系統(tǒng)實(shí)現(xiàn)的互斥鎖,并不強(qiáng)制進(jìn)程休眠攀圈,而是起到告知的作用暴凑。
  • NSDistributedLock沒(méi)有實(shí)現(xiàn)NSLocking協(xié)議,所以沒(méi)有會(huì)阻塞線程的lock方法赘来,取而代之的是非阻塞的tryLock方法來(lái)獲取鎖现喳,用unlock方法釋放鎖。
  • 如果一個(gè)獲取鎖的進(jìn)程在釋放鎖之前就退出了犬辰,那么鎖就一直不能釋放嗦篱,此時(shí)可以通過(guò)breakLock強(qiáng)行獲取鎖。
6. 條件變量(Condition Variable)
  • 使用情況:如果一個(gè)線程需要等待某一條件出現(xiàn)才能繼續(xù)執(zhí)行幌缝,而這個(gè)條件是由別的線程產(chǎn)生的灸促,這個(gè)時(shí)候就用到條件變量。常見(jiàn)的情況是:生產(chǎn)者-消費(fèi)者問(wèn)題涵卵。

  • 條件變量可以讓一個(gè)線程等待某一條件浴栽,當(dāng)條件滿足時(shí),會(huì)收到通知轿偎。在獲取條件變量并等待條件發(fā)生的過(guò)程中典鸡,也會(huì)產(chǎn)生多線程的競(jìng)爭(zhēng),所以條件變量通常和互斥鎖一起工作坏晦。

    • NSCondition:是互斥鎖和條件鎖的結(jié)合萝玷,即一個(gè)線程在等待signal而阻塞時(shí),可以被另一個(gè)線程喚醒昆婿,由于操作系統(tǒng)實(shí)現(xiàn)的差異球碉,即使沒(méi)有發(fā)送signal消息,線程也有可能被喚醒仓蛆,所以需要增加謂詞變量來(lái)保證程序的正確性睁冬。
    • NSConditionLock:與NSCondition的實(shí)現(xiàn)機(jī)制不一樣,當(dāng)定義的條件成立的時(shí)候會(huì)獲取鎖多律,反之痴突,釋放鎖搂蜓。

    NSCondition的例程:

    // 創(chuàng)建鎖
     NSCondition *condition = [[NSCondition alloc] init];
    static int count = 0;
    // 生產(chǎn)者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while(count<20)
        {
            [condition lock];
            // 生產(chǎn)
            count ++;
            NSLog(@"生產(chǎn) = %d",count);
            [condition signal];
            [condition unlock];
        }
    });
    // 消費(fèi)者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (count>0)
        {
            [condition lock];
            // 消耗
            count --;
            NSLog(@"消耗剩余 = %d",count);
            [condition unlock];
        }
    });

NSConditionLock的例程:

    // 創(chuàng)建鎖
    NSConditionLock *condLock = [[NSConditionLock alloc] initWithCondition:ConditionHASNOT];
    static int count = 0;
    // 生產(chǎn)者
    while(true)
    {
        [condLock lock];
        // 生產(chǎn)
        count ++;
        [condLock unlockWithCondition:ConditionHAS];
    }
    // 消費(fèi)者
    while (true)
    {
        [condLock lockWhenCondition:ConditionHAS];
        // 消耗
        count --;
        [condLock unlockWithCondition:(count<=0 ? ConditionHASNOT : ConditionHAS)];
    }
7. 信號(hào)量(Semaphore)
  • 信號(hào)量:可以是一種特殊的互斥鎖狼荞,可以是資源的計(jì)數(shù)器
  • 可以使用GCD中的Dispatch Semaphore實(shí)現(xiàn)辽装,Dispatch Semaphore是持有計(jì)數(shù)的信號(hào),該計(jì)數(shù)是多線程編程中的計(jì)數(shù)類(lèi)型信號(hào)相味。計(jì)數(shù)為0時(shí)等待拾积,計(jì)數(shù)大于等于1時(shí),減1為不等待丰涉。
8. 柵欄/屏障(Barrier)
  • 柵欄必須單獨(dú)執(zhí)行拓巧,不能與其他任務(wù)并發(fā)執(zhí)行,柵欄只對(duì)并發(fā)隊(duì)列有意義一死。
  • 柵欄只有等待當(dāng)前隊(duì)列所有并發(fā)任務(wù)都執(zhí)行完畢后肛度,才會(huì)單獨(dú)執(zhí)行,帶起執(zhí)行完畢投慈,再按照正常的方式繼續(xù)向下執(zhí)行承耿。

iOS中線程鎖的性能對(duì)比:

點(diǎn)擊這里,參考網(wǎng)址

  • No1.自旋鎖OSSpinLock耗時(shí)最少
  • No2.pthread_mutex
  • No3.NSLock/NSCondition/NSRecursiveLock 耗時(shí)接近
  • No4.@synchronized
  • No5.NSConditionLock
  • 柵欄的性能并沒(méi)有很好伪煤,在實(shí)際開(kāi)發(fā)中也很少用到(筆者在最近一次面試中就遇到加袋,問(wèn)柵欄的性能怎么樣?當(dāng)時(shí)并不知道柵欄在實(shí)際應(yīng)用中的性能并不是很理想抱既,又被問(wèn)到蘋(píng)果官方常使用的鎖是什么职烧?應(yīng)該是自旋鎖,--然而筆者當(dāng)時(shí)還是不知道防泵。蚀之。。)

> 劃重點(diǎn):自旋鎖是線程不安全的在 ibireme 的 不再安全的 OSSpinLock有解釋?zhuān)M(jìn)一步的ibireme在文中也有提到蘋(píng)果在新系統(tǒng)中已經(jīng)優(yōu)化了 pthread_mutex 的性能捷泞,所以它看上去和 OSSpinLock 差距并沒(méi)有那么大足删,所以筆者覺(jué)得不妨多了解了解pthread_mutex
  • pthread_mutex
    __block pthread_mutex_t theLock;
    pthread_mutex_init(&theLock, NULL);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            pthread_mutex_lock(&theLock);
            NSLog(@"需要線程同步的操作1 開(kāi)始");
            sleep(3);
            NSLog(@"需要線程同步的操作1 結(jié)束");
            pthread_mutex_unlock(&theLock);
        
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            pthread_mutex_lock(&theLock);
            NSLog(@"需要線程同步的操作2");
            pthread_mutex_unlock(&theLock);
        
    });

c語(yǔ)言定義下多線程加鎖方式。

  1. pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t attr);
    初始化鎖變量mutex肚邢。attr為鎖屬性壹堰,NULL值為默認(rèn)屬性。
  2. pthread_mutex_lock(pthread_mutex_t* mutex);加鎖
  3. pthread_mutex_tylock(pthread_mutex_t* mutex);加鎖骡湖,但是與2不一樣的是當(dāng)鎖已經(jīng)在使用的時(shí)候贱纠,返回為EBUSY,而不是掛起等待响蕴。
  4. pthread_mutex_unlock(pthread_mutex_t* mutex);釋放鎖
  5. pthread_mutex_destroy(pthread_mutex_t* *mutex);使用完后釋放

代碼執(zhí)行操作結(jié)果如下:

2016-06-30 21:13:32.440 SafeMultiThread[31429:548869] 需要線程同步的操作1 開(kāi)始
2016-06-30 21:13:35.445 SafeMultiThread[31429:548869] 需要線程同步的操作1 結(jié)束
2016-06-30 21:13:35.446 SafeMultiThread[31429:548866] 需要線程同步的操作2

  • pthread_mutex(recursive)
    __block pthread_mutex_t theLock;
    //pthread_mutex_init(&theLock, NULL);
    
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&lock, &attr);
    pthread_mutexattr_destroy(&attr);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        static void (^RecursiveMethod)(int);
        
        RecursiveMethod = ^(int value) {
            
            pthread_mutex_lock(&theLock);
            if (value > 0) {
                
                NSLog(@"value = %d", value);
                sleep(1);
                RecursiveMethod(value - 1);
            }
            pthread_mutex_unlock(&theLock);
        };
        
        RecursiveMethod(5);
    });

這是pthread_mutex為了防止在遞歸的情況下出現(xiàn)死鎖而出現(xiàn)的遞歸鎖谆焊。作用和NSRecursiveLock遞歸鎖類(lèi)似。
如果使用pthread_mutex_init(&theLock, NULL);初始化鎖的話浦夷,上面的代碼會(huì)出現(xiàn)死鎖現(xiàn)象辖试,但是改成使用遞歸鎖的形式辜王,則沒(méi)有問(wèn)題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末罐孝,一起剝皮案震驚了整個(gè)濱河市呐馆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌莲兢,老刑警劉巖汹来,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異改艇,居然都是意外死亡收班,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)谒兄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)摔桦,“玉大人,你說(shuō)我怎么就攤上這事承疲×诟” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵纪隙,是天一觀的道長(zhǎng)赊豌。 經(jīng)常有香客問(wèn)我,道長(zhǎng)绵咱,這世上最難降的妖魔是什么碘饼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮悲伶,結(jié)果婚禮上艾恼,老公的妹妹穿的比我還像新娘。我一直安慰自己麸锉,他們只是感情好钠绍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著花沉,像睡著了一般柳爽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碱屁,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天磷脯,我揣著相機(jī)與錄音,去河邊找鬼娩脾。 笑死赵誓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俩功,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼幻枉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了诡蜓?” 一聲冷哼從身側(cè)響起熬甫,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎万牺,沒(méi)想到半個(gè)月后罗珍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體洽腺,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脚粟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蘸朋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片核无。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖藕坯,靈堂內(nèi)的尸體忽然破棺而出团南,到底是詐尸還是另有隱情,我是刑警寧澤炼彪,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布吐根,位于F島的核電站,受9級(jí)特大地震影響辐马,放射性物質(zhì)發(fā)生泄漏拷橘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一喜爷、第九天 我趴在偏房一處隱蔽的房頂上張望冗疮。 院中可真熱鬧,春花似錦檩帐、人聲如沸术幔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)诅挑。三九已至,卻和暖如春泛源,著一層夾襖步出監(jiān)牢的瞬間拔妥,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工俩由, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留毒嫡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像兜畸,于是被迫代替她去往敵國(guó)和親努释。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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