ios之各種鎖機(jī)制的使用和理解

鎖可以分為兩大類:自旋鎖(OSSpinLock)和互斥鎖(pthread_mutex)。

相同點(diǎn):
都能保證同一時(shí)間只有一個(gè)線程訪問共享資源。都能保證線程安全毁涉。

不同點(diǎn):
互斥鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了脓斩,線程會(huì)進(jìn)入休眠狀態(tài)等待鎖艺骂。一旦被訪問的資源被解鎖志衣,則等待資源的線程會(huì)被喚醒懂昂。
自旋鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了寺谤,線程會(huì)以死循環(huán)的方式等待鎖隙畜,一旦被訪問的資源被解鎖,則等待資源的線程會(huì)立即執(zhí)行诺凡。
自旋鎖的效率高于互斥鎖东揣。

各種鎖性能:
除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高 @synchronized() 是性能最低的

OSSpinLock已經(jīng)廢除就不做介紹了
下面是常見的幾種鎖:

@synchronized()

互斥鎖@synchronized (obj):obj為該鎖的唯一標(biāo)識(shí),標(biāo)識(shí)相同則是同一把鎖腹泌。當(dāng)線程里執(zhí)行obj鎖{}代碼塊時(shí) 相當(dāng)于獲得了obj鎖 這時(shí)其他線程就不能獲得obj鎖
只有當(dāng){}代碼塊執(zhí)行完釋放了obj鎖 其他線程才能獲得這個(gè)鎖 并執(zhí)行{}中鎖住的代碼

@synchronized鎖 里套 @synchronized鎖 是不會(huì)死鎖的 因?yàn)锧synchronized底層實(shí)現(xiàn)使用的就是遞歸鎖(NSRecursiveLock)

#pragma mark - @synchronized的互斥
- (void)twoSynchronized{
    /*  @synchronized(obj)指令使用的obj為該鎖的唯一標(biāo)識(shí)嘶卧,只有當(dāng)標(biāo)識(shí)相同時(shí),才為滿足互斥 
下面兩個(gè)@synchronized鎖的標(biāo)識(shí)一樣 是同一把鎖 當(dāng)?shù)谝粋€(gè)線程獲得鎖后 鎖里面的代碼執(zhí)行完后才釋放鎖 
第二個(gè)線程才能獲取到鎖 才能繼續(xù)執(zhí)行鎖里面的內(nèi)容  所以執(zhí)行結(jié)果是:
     需要線程同步的操作1 開始
     需要線程同步的操作1 結(jié)束
     需要線程同步的操作2
     */
    NSObject *obj = [[NSObject alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized(obj) {
            NSLog(@"需要線程同步的操作1 開始");
            sleep(3);
            NSLog(@"需要線程同步的操作1 結(jié)束");
        }
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        @synchronized(obj) {
            NSLog(@"需要線程同步的操作2");
        }
    });
}
NSLock 互斥鎖

NSLock 遵循 NSLocking 協(xié)議凉袱,lock() unlock()方法是NSLocking協(xié)議的方法脸候,就不說了 ,一個(gè)加鎖一個(gè)解鎖 總是成對出現(xiàn) .
NSLock類還增加了- (BOOL)tryLock;和- (BOOL)lockBeforeDate:(NSDate *)limit;方法绑蔫。我們來看下:

tryLock嘗試獲取一個(gè)鎖,但是如果鎖不可用的時(shí)候泵额,它不會(huì)阻塞線程配深,相反,它只是返回NO嫁盲。如果獲取到鎖后要記得unlock解鎖篓叶,這樣其他線程才能獲取鎖 相當(dāng)于[lock lockBeforeDate:[NSDate date]];
lockBeforeDate:方法試圖獲取一個(gè)鎖,但是如果鎖沒有在規(guī)定的時(shí)間內(nèi)被獲得羞秤,它會(huì)讓線程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回NO)缸托。會(huì)等待時(shí)間 如果時(shí)間到了還沒有獲取到鎖則返回NO 如果獲取到了就執(zhí)行下面語句 沒獲取到 且時(shí)間還沒到就會(huì)等待著 也就是線程阻塞 不會(huì)執(zhí)行后面的代碼

看一段代碼

    NSLock *lock = [[NSLock alloc] init];
    // 線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lockBeforeDate:[NSDate date]];
        NSLog(@"需要線程同步的操作1 開始");
        sleep(2);
        NSLog(@"需要線程同步的操作1 結(jié)束");
        [lock unlock];
    });
    // 線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1); //以保證讓線程2的代碼后執(zhí)行
        if ([lock tryLock]) {//嘗試獲取鎖,如果獲取不到返回NO瘾蛋,不會(huì)阻塞該線程俐镐,如果獲取到鎖 記得unlock解鎖
            NSLog(@"鎖可用的操作");
            [lock unlock];
        }else{
            NSLog(@"鎖不可用的操作");
        }
        NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:3];
        if ([lock lockBeforeDate:date]) {//嘗試在未來的3s內(nèi)獲取鎖,并阻塞該線程哺哼,如果3s內(nèi)獲取不到恢復(fù)線程, 返回NO,不再阻塞線程
            NSLog(@"沒有超時(shí)佩抹,獲得鎖");
            [lock unlock];
        }else{
            NSLog(@"超時(shí)叼风,沒有獲得鎖");
        }
    });
    
    /**
     結(jié)果:
     需要線程同步的操作1 開始
     鎖不可用的操作
     需要線程同步的操作1 結(jié)束
     沒有超時(shí),獲得鎖
     */

NSConditionLock -- 條件鎖

@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition; //會(huì)阻塞線程的
- (BOOL)tryLock;//是不會(huì)阻塞線程的
- (BOOL)tryLockWhenCondition:(NSInteger)condition;//是不會(huì)阻塞線程的
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;//會(huì)阻塞線程的
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//會(huì)阻塞線程的

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

我們發(fā)現(xiàn)條件condition是NSInteger類型的
當(dāng)condition相等的時(shí)候才能獲得鎖 也就是加鎖

需要注意的是- (void)unlockWithCondition:(NSInteger)condition;
這個(gè)方法有兩個(gè)作用1棍苹、就是解鎖 2无宿、是改變鎖的條件

看下面代碼:

//主線程中
    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
    
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lockWhenCondition:1];
        NSLog(@"線程1");
        sleep(2);
        [lock unlock];
    });
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);//以保證讓線程2的代碼后執(zhí)行
        if ([lock tryLockWhenCondition:0]) {
            NSLog(@"線程2");
            [lock unlockWithCondition:2];
            NSLog(@"線程2解鎖成功");
        } else {
            NSLog(@"線程2嘗試加鎖失敗");
        }
    });
    
    //線程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);//以保證讓線程2的代碼后執(zhí)行
        if ([lock tryLockWhenCondition:2]) {
            NSLog(@"線程3");
            [lock unlock];
            NSLog(@"線程3解鎖成功");
        } else {
            NSLog(@"線程3嘗試加鎖失敗");
        }
    });
    
    //線程4
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(3);//以保證讓線程2的代碼后執(zhí)行
        if ([lock tryLockWhenCondition:2]) {
            NSLog(@"線程4");
            [lock unlockWithCondition:1];
            NSLog(@"線程4解鎖成功");
        } else {
            NSLog(@"線程4嘗試加鎖失敗");
        }
    });

2019-02-21 10:47:29.227968+0800 LJConditionLock[2191:320996] 線程2  (滿足了條件,所以先執(zhí)行了線程2 枢里,然后改變條件為2)
2019-02-21 10:47:29.228109+0800 LJConditionLock[2191:320996] 線程2解鎖成功
2019-02-21 10:47:30.227654+0800 LJConditionLock[2191:320995] 線程3 (線程2改變條件為2 正好等于線程3的條件)
2019-02-21 10:47:30.227843+0800 LJConditionLock[2191:320995] 線程3解鎖成功
2019-02-21 10:47:31.226466+0800 LJConditionLock[2191:320997] 線程4
2019-02-21 10:47:31.226634+0800 LJConditionLock[2191:320997] 線程4解鎖成功
2019-02-21 10:47:31.226645+0800 LJConditionLock[2191:320994] 線程1

從結(jié)果看 NSConditionLock 還可以實(shí)現(xiàn)任務(wù)之間的依賴

NSRecursiveLock--遞歸鎖

允許同一線程多次加鎖孽鸡,而不會(huì)造成死鎖
看下面的代碼:

NSLock *lock = [[NSLock alloc] init];
 
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 
    static void (^RecursiveMethod)(int);
 
    RecursiveMethod = ^(int value) {
 
        [lock lock];
        if (value > 0) {
 
            NSLog(@"value = %d", value);
            sleep(2);
            // 遞歸調(diào)用
            RecursiveMethod(value - 1);
        }
        [lock unlock];
    };
 
    RecursiveMethod(5);
});

這段代碼是一個(gè)典型的死鎖情況。在我們的線程中栏豺,RecursiveMethod是遞歸調(diào)用的彬碱。所以每次進(jìn)入這個(gè)block時(shí),都會(huì)去加一次鎖冰悠,而從第二次開始堡妒,由于鎖已經(jīng)被使用了且沒有解鎖,所以它需要等待鎖被解除溉卓,這樣就導(dǎo)致了死鎖皮迟,線程被阻塞住了。
遞歸鎖允許同一線程多次加鎖桑寨,而不會(huì)造成死鎖伏尼,就是用來解決這類遞歸或者循環(huán)加鎖問題的

同樣的NSRecursiveLock也有兩個(gè)方法 :

  • (BOOL)tryLock;
  • (BOOL)lockBeforeDate:(NSDate *)limit;
    是跟NSLock一樣的用法

pthread_mutex 互斥鎖

和NSLock


#import <pthread/pthread.h>

#define Lock() pthread_mutex_lock(&_lock)
#define Unlock() pthread_mutex_unlock(&_lock)

@implementation LJpthread_mutexVC
{
    pthread_mutex_t _lock; // 聲明
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // 初始化
    pthread_mutex_init(&_lock,NULL);
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        Lock();
        NSLog(@"任務(wù)1開始");
        sleep(2);
        NSLog(@"任務(wù)1結(jié)束");
        Unlock();
    });
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        sleep(1);
        Lock();
        NSLog(@"任務(wù)2");
        Unlock();
    });
}

1、申明一個(gè)互斥鎖尉尾,pthread_mutex_t _lock;
2爆阶、初始化它,pthread_mutex_init(& _lock,NULL);
3沙咏、使用_lock之前一定要初始化辨图,否則不生效
4、獲得鎖肢藐,pthread_mutex_lock(& _lock);
5故河、解鎖,pthread_mutex_unlock(& _lock);

dispatch_semaphore信號(hào)量作為鎖

/* dispatch_semaphore 只有三個(gè)方法
 dispatch_semaphore_create(long value); // 創(chuàng)建信號(hào)量 參數(shù)是信號(hào)數(shù)量 必須要大于等于0
 dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); // 等待獲取信號(hào) 當(dāng)前信號(hào)量大于等于1的時(shí)候 才能獲得 獲得之后信號(hào)量減1
 dispatch_semaphore_signal(dispatch_semaphore_t dsema); // 釋放信號(hào)量 使信號(hào)量加1
 
 信號(hào)量相當(dāng)于停車場 有n個(gè)車位 wait 相當(dāng)于來了一輛車 如果有空閑車位 就停進(jìn)去 空閑車位數(shù)減一吆豹,沒有就等待 signal相當(dāng)于開走一輛 空閑車位數(shù)加一
 在訪問共同資源時(shí)鱼的,可以控制有幾個(gè)線程來同時(shí)訪問 當(dāng)信號(hào)量為1時(shí) 就相當(dāng)于鎖的作用
*/
@interface LJSemaphoreVC ()
{
    dispatch_semaphore_t sem;
}
/** 售票員01 */
@property (nonatomic, strong) NSThread *thread01;
/** 售票員02 */
@property (nonatomic, strong) NSThread *thread02;
/** 售票員03 */
@property (nonatomic, strong) NSThread *thread03;
/** 票的總數(shù) */
@property (nonatomic, assign) NSInteger ticketCount;
@end

@implementation LJSemaphoreVC

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    

    sem = dispatch_semaphore_create(1);
    self.ticketCount = 100;
    self.thread01 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread01.name = @"售票員01";
    
    self.thread02 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread02.name = @"售票員02";
    
    self.thread03 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread03.name = @"售票員03";
    
    [self.thread01 start];
    //    sleep(1);
    [self.thread02 start];
    [self.thread03 start];
}
- (void)saleTicket
{
    
    while (1) {
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 信號(hào)量減1 如果信號(hào)量 > 0 獲得信號(hào) 執(zhí)行下面代碼 否則等待
            // 先取出總數(shù)
            NSInteger count = self.ticketCount;
            if (count > 0) {
                self.ticketCount = count - 1;
                NSLog(@"%@賣了一張票,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
            }else{
                NSLog(@"票賣完了");
                break; // 跳出循環(huán)
            }
        dispatch_semaphore_signal(sem); // 操作完成 信號(hào)量加1 釋放信號(hào)
    }
}

還有讀寫鎖pthread_rwlock痘煤、條件鎖NSCondition等就不一一介紹了凑阶,主要是理解鎖的機(jī)制和原理,能夠知其所以然衷快。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宙橱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌养匈,老刑警劉巖哼勇,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異呕乎,居然都是意外死亡积担,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門猬仁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帝璧,“玉大人,你說我怎么就攤上這事湿刽〉乃福” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵诈闺,是天一觀的道長渴庆。 經(jīng)常有香客問我,道長雅镊,這世上最難降的妖魔是什么襟雷? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮仁烹,結(jié)果婚禮上耸弄,老公的妹妹穿的比我還像新娘。我一直安慰自己卓缰,他們只是感情好计呈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著征唬,像睡著了一般捌显。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上总寒,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天扶歪,我揣著相機(jī)與錄音,去河邊找鬼偿乖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哲嘲,可吹牛的內(nèi)容都是我干的贪薪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼眠副,長吁一口氣:“原來是場噩夢啊……” “哼画切!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起囱怕,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤霍弹,失蹤者是張志新(化名)和其女友劉穎毫别,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體典格,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岛宦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耍缴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砾肺。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖防嗡,靈堂內(nèi)的尸體忽然破棺而出变汪,到底是詐尸還是另有隱情,我是刑警寧澤蚁趁,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布裙盾,位于F島的核電站,受9級特大地震影響他嫡,放射性物質(zhì)發(fā)生泄漏番官。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一涮瞻、第九天 我趴在偏房一處隱蔽的房頂上張望鲤拿。 院中可真熱鬧,春花似錦署咽、人聲如沸近顷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窒升。三九已至,卻和暖如春慕匠,著一層夾襖步出監(jiān)牢的瞬間饱须,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工台谊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蓉媳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓锅铅,卻偏偏與公主長得像酪呻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子盐须,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

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