IOS底層原理 -7.多線程

練習代碼地址:

1. iOS中多線程方案

最底層API pthread: 是一套通用的跨平臺的多線程API,是基於c語言,線程的生命週期需要手動管理;
NSThread:是對pthread用OC語言的封裝,使得操作線程可以像操作對象一樣操作;
使用最多的GCD:替代NSThread的多線程API,可以充分利用多核技術;執行的子線程任務都在塊裏(block);其生命週期自行維護;
NSOperation:是基於GCD的面向對象的封裝,比起gcd使用更加簡單,同時也增加了一些實用功能;

2. GCD 同步,異步,串行,並行

2.1 從一到面試題入手認識下GCD:

下面代碼會發生什麼?

    NSLog(@"步驟1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"步驟2");
    });
    NSLog(@"步驟3");

答案是:輸出步驟1 後程序會崩潰;看一下實際運行後的截圖:
在這裏插入圖片描述
dispatch_get_main_queue()獲取的是主隊列,主隊列等同於串行隊列;先來看下iOS中多線程的四個名詞:

2.2 多線程易混淆的名詞

同步sync,異步async,並行隊列Concurrent Dispatch Queue,串行隊列Serial Dispatch Queue;

  • 並行隊列Concurrent Dispatch Queue:可以讓多個任務同時併發執行,並同時開啓新的線程,併發隊列只有在異步任務下才有效;
  • 串行隊列Serial Dispatch Queue:一個任務執行完成後才能執行下一個任務;
併發隊列 串行隊列 主隊列
sync 沒有開啓新線程,串行執行任務 沒有開啓新線程,串行執行任務 沒有開啓新線程,串行執行任務
async 有開啓新線程,併發執行任務 有開啓新線程,串行執行任務 沒有開啓新線程,串行執行任務

通過代碼對上表進行驗證下:

    dispatch_queue_t serialQue  = dispatch_queue_create("com.lym.serial", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t concurrentQue  = dispatch_queue_create("com.lym.concurrent", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"步驟0");
    dispatch_async(serialQue, ^{
        NSLog(@"串行隊列 異步任務 1");
    });
    dispatch_async(serialQue, ^{
        NSLog(@"串行隊列 異步任務 2");
    });
    dispatch_async(serialQue, ^{
        NSLog(@"串行隊列 異步任務 3");
    });
    NSLog(@"------------------------");
    dispatch_async(concurrentQue, ^{
        NSLog(@"併發隊列 異步任務 1");
    });
    dispatch_async(concurrentQue, ^{
        NSLog(@"併發隊列 異步任務 2");
    });
    dispatch_async(concurrentQue, ^{
        NSLog(@"併發隊列 異步任務 3");
    });
    NSLog(@"------------------------");
    dispatch_sync(concurrentQue, ^{
        NSLog(@"併發隊列 同步任務 1");
    });
    dispatch_sync(concurrentQue, ^{
        NSLog(@"併發隊列 同步任務 2");
    });
    dispatch_sync(concurrentQue, ^{
        NSLog(@"併發隊列 同步任務 3");
    });
    NSLog(@"------------------------");
    dispatch_sync(serialQue, ^{
        NSLog(@"串行隊列 同步任務 1");
    });
    dispatch_sync(serialQue, ^{
        NSLog(@"串行隊列 同步任務 2");
    });
    dispatch_sync(serialQue, ^{
        NSLog(@"串行隊列 同步任務 3");
    });
    NSLog(@"步驟2");

輸出結果:
在這裏插入圖片描述

2.3 面試題解析

下面再來分析下鎖住的原因:iOS中函數默認都是運行在主線程中,而主線程所在的主隊列是串行隊列;那麼上述函數中在主隊列中有一個任務(viewDidLoad)開始執行,當執行到dispatch_sync的時候串行隊列中有多了個同步任務,那麼上一個任務(viewDidLoad)等待該任務執行完才能接着執行,但是因爲是同步線程,dispatch_sync的任務需要等到上一個任務執行完成才能執行;這樣就造成兩個任務相互等待對方執行完成,從而造成死鎖;解決方式有以下兩種:
a,主隊列中異步執行:

    NSLog(@"步驟1");
    dispatch_queue_t mianQue  = dispatch_get_main_queue();
    dispatch_async(mianQue, ^{
        NSLog(@"步驟2");
    });

    NSLog(@"步驟3");
    //2019-09-28 10:16:07.384373+0800 testThread[20853:156963] 步驟1
    //2019-09-28 10:16:07.384633+0800 testThread[20853:156963] 步驟2
    //2019-09-28 10:16:07.384782+0800 testThread[20853:156963] 步驟3

b, 新隊列中執行:

    NSLog(@"步驟1");
    dispatch_queue_t que  = dispatch_queue_create("kkk", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(que, ^{//異步這裏就不再測試
        NSLog(@"步驟2");
    });
    NSLog(@"步驟3");
    
   //2019-09-28 10:16:07.384373+0800 testThread[20853:156963] 步驟1
   //2019-09-28 10:16:07.384633+0800 testThread[20853:156963] 步驟2
   //2019-09-28 10:16:07.384782+0800 testThread[20853:156963] 步驟3

**

注意:使用sync在當前串行隊列中添加任務,會是當前隊列產生死鎖;

**

3 多人線程與runloop

3.1 面試題1

     NSLog(@"步驟1");
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"步驟2");
    });
    NSLog(@"步驟3");
- (void)test{
    NSLog(@"%s",__func__);
}
  • 執行結果是:
2019-09-28 10:56:17.360642+0800 testThread[27068:210230] 步驟1
2019-09-28 10:56:17.360920+0800 testThread[27068:210230] 步驟3
2019-09-28 10:56:17.361117+0800 testThread[27068:210325] 步驟2

從上面打印可以看出並沒有執行test()方法;

  • 分析:
    如果沒有afterDelay:那麼該方法可以在objc源碼裏找到實現,其底層最終調用了msgSend;而有afterDelay:後在Runloop.h中,其底層實現沒有開源,但是可以通過相似的開源項目GNUSetup,知道其底層是添加了一個定時器;而子線程中的定時器是依靠runloop,如果沒有獲取過,子線程是沒有runloop;所以不會去執行test方法。
  • 解決方法
     NSLog(@"步驟1");
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@"步驟2");
    });
    NSLog(@"步驟3");
- (void)test{
    NSLog(@"%s",__func__);
    CFRunLoopStop(CFRunLoopGetCurrent());
}
2019-09-28 11:03:08.085945+0800 testThread[28275:221117] 步驟1
2019-09-28 11:03:08.086135+0800 testThread[28275:221117] 步驟3
2019-09-28 11:03:08.086765+0800 testThread[28275:221198] -[ViewController test]
2019-09-28 11:03:08.087350+0800 testThread[28275:221198] 步驟2

3.2 面試題2

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"步驟0");
    NSThread *curentThres = [[NSThread alloc]initWithBlock:^{
        NSLog(@"步驟1");
    }];
    [curentThres start];
    NSLog(@"步驟2");
    [self performSelector:@selector(test) onThread:curentThres withObject:nil waitUntilDone:YES];
}

運行結果如下:

2019-09-28 11:16:04.951807+0800 testThread[30020:237237] 步驟0
2019-09-28 11:16:04.952187+0800 testThread[30020:237237] 步驟2
2019-09-28 11:16:04.954974+0800 testThread[30020:237393] 步驟1
2019-09-28 11:16:04.980856+0800 testThread[30020:237237] *** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'

原因是在執行performSelector的時候curentThres在執行完一個任務後已經銷燬了,解決方法也是runloop:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"步驟0");
    NSThread *curentThres = [[NSThread alloc]initWithBlock:^{
        NSLog(@"步驟1");
        //不添加下面這一句會崩潰
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [curentThres start];
    NSLog(@"步驟2");
    [self performSelector:@selector(test) onThread:curentThres withObject:nil waitUntilDone:YES];
}

4 隊列組

多個併發的任務執行完成後再執行另一個任務,最經典的就是多線程下載的應用;使用很簡單;我們直接看源碼實例:

dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t concurrentQue  = dispatch_queue_create("com.lym.concurrentGroup", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"步驟0");
    dispatch_group_async(group,concurrentQue, ^{
         [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"併發隊列組 異步任務 1");
       
    });
    dispatch_group_async(group,concurrentQue, ^{
         [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"併發隊列組 異步任務 2");
    });
    dispatch_group_async(group,concurrentQue, ^{
         [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"併發隊列組 異步任務 3");
    });
    dispatch_group_notify(group, concurrentQue, ^{
        NSLog(@"全部執行完成");
    });
    NSLog(@"步驟2");
2019-09-28 11:31:12.132972+0800 testThread[32679:259275] 步驟0
2019-09-28 11:31:12.133151+0800 testThread[32679:259275] 步驟2
2019-09-28 11:31:14.135128+0800 testThread[32679:259330] 併發隊列組 異步任務 2
2019-09-28 11:31:14.135159+0800 testThread[32679:259331] 併發隊列組 異步任務 3
2019-09-28 11:31:14.135147+0800 testThread[32679:259329] 併發隊列組 異步任務 1
2019-09-28 11:31:14.135595+0800 testThread[32679:259331] 全部執行完成

5 線程安全問題

5.1 異常例子

#import "LYMBaseThreadTast.h"

@interface LYMBaseThreadTast ()
@property(nonatomic,assign)NSInteger cont;

@end

@implementation LYMBaseThreadTast
/*****售票問題******/
-(void)scaleTicket{
    int count = (int)_cont;
    count = count - 1;;
    sleep(.3);
    self.cont = count;
    NSLog(@"還剩%ld currenthread:%@",_cont,[NSThread currentThread]);
    
}
-(void)scaleTicketsTask{
    self.cont = 20;
    dispatch_queue_t scaleTicketQue  = dispatch_queue_create("com.lym.scaleTicket", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(scaleTicketQue, ^{
        for (int i = 10; i > 0; i--) {
            [self scaleTicket];
        }
        
    });
    dispatch_async(scaleTicketQue, ^{
        for (int i = 5; i > 0; i--) {
            [self scaleTicket];
        }
    });
    dispatch_async(scaleTicketQue, ^{
        for (int i = 5; i > 0; i--) {
            [self scaleTicket];
        }
    });
}
/*****end****/
@end

輸出:

2019-09-28 11:58:44.783825+0800 testThread[36523:296048] 還剩19 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.783831+0800 testThread[36523:296045] 還剩19 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.783932+0800 testThread[36523:296046] 還剩19 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.784113+0800 testThread[36523:296046] 還剩18 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.784113+0800 testThread[36523:296048] 還剩18 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.784116+0800 testThread[36523:296045] 還剩18 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.784242+0800 testThread[36523:296046] 還剩17 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.784246+0800 testThread[36523:296048] 還剩17 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.784254+0800 testThread[36523:296045] 還剩16 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.784345+0800 testThread[36523:296046] 還剩15 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.785462+0800 testThread[36523:296048] 還剩14 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.785538+0800 testThread[36523:296045] 還剩13 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.785745+0800 testThread[36523:296046] 還剩12 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.785979+0800 testThread[36523:296048] 還剩11 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.786492+0800 testThread[36523:296045] 還剩10 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.786959+0800 testThread[36523:296045] 還剩9 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.787282+0800 testThread[36523:296045] 還剩8 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.791496+0800 testThread[36523:296045] 還剩7 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.791644+0800 testThread[36523:296045] 還剩6 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.791770+0800 testThread[36523:296045] 還剩5 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}

從上面的例子裏可以看到,當多個線程同時讀寫一個變量的時候就會出現異常

5.2 異常例子 解決方法:

5.2.1 自旋鎖 OSSpinLock

將上述方法修改如下:

 #import "LYMOSSpinLockThreadTast.h"
#import <libkern/OSAtomic.h>


@interface LYMOSSpinLockThreadTast ()
@property(nonatomic,assign)OSSpinLock lock;
@end
@implementation LYMOSSpinLockThreadTast
-(instancetype)init{
    if (self=[super init]) {
        self.lock =  OS_SPINLOCK_INIT;
    }
    return self;
}
/*****售票問題 自旋鎖解決方式******/
-(void)scaleTicket{
    OSSpinLockLock(&_lock);//自旋鎖加鎖
    [super scaleTicket];
    OSSpinLockUnlock(&_lock);//自旋鎖解鎖
}
/*****end****/
@end
/*****end****/

輸出:

2019-12-01 13:00:45.190729+0800  還剩19 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.191271+0800  還剩18 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.191511+0800 還剩17 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193041+0800 還剩16 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193231+0800 還剩15 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193416+0800 還剩14 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193586+0800 還剩13 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193772+0800  還剩12 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.194042+0800  還剩11 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.198458+0800 還剩10 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.199288+0800  還剩9 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199431+0800  還剩8 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199560+0800 還剩7 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199700+0800 還剩6 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199816+0800  還剩5 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.203833+0800  還剩4 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204001+0800  還剩3 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204128+0800 還剩2 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204258+0800  還剩1 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204381+0800  還剩0 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}

自旋鎖實現大致是一個循環不斷去判斷是不是完成任務,就是說線程會一直佔用cpu資源即:忙等;假如這個等待的線程是級別比較高的線程,那麼線程調度時候就會導致其他線程無法執行;及線程等級反轉。
所以蘋果已經不推薦使用自旋鎖;因此推薦使用os_unfair_lock:

#import "LYMOSUnfairLockThreadTast.h"
#import <os/lock.h>

@interface LYMOSUnfairLockThreadTast ()
@property (assign, nonatomic) os_unfair_lock ticketLock;
@end

@implementation LYMOSUnfairLockThreadTast
/*****售票問題 os_unfair_lock  #import <os/lock.h>******/
-(instancetype)init{
    if (self=[super init]) {
        self.ticketLock =  OS_UNFAIR_LOCK_INIT;
    }
    return self;
}
-(void)scaleTicket{
    os_unfair_lock_lock(&_ticketLock);
    [super scaleTicket];
    os_unfair_lock_unlock(&_ticketLock);//解鎖
    
}
/*****end****/
@end

5.2.2 pthread_mutex_t 互斥鎖

#import "LYMOSPThreadTast.h"
#import <pthread.h>

@interface LYMOSPThreadTast ()
@property(nonatomic,assign)pthread_mutex_t mutex_lock;//互斥鎖
@end
@implementation LYMOSPThreadTast
/*****售票問題 os_unfair_lock  #import <os/lock.h>******/
-(instancetype)init{
    if (self=[super init]) {
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);//普通鎖
        pthread_mutex_init(&(_mutex_lock), &attr);//第二個參數默認可以爲NULL
    }
    return self;
}
-(void)scaleTicket{
    pthread_mutex_lock(&(_mutex_lock));
    [super scaleTicket];
    pthread_mutex_unlock(&(_mutex_lock));//解鎖
    
}
-(void)dealloc{
    pthread_mutex_destroy(&(_mutex_lock));
}
/*****end****/
@end

上述代碼中加鎖的代碼中如果有調用了使用同一把鎖加鎖的代碼,就會造成死鎖,這時候應該使用遞歸鎖pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//遞歸鎖

在加鎖與解鎖成對的線程中,遞歸鎖允許對同一把鎖重複加鎖

5.2.2 NSCondition(條件鎖),NSConditionLock(條件鎖的封裝)

//
//  LYMNSCordition.m
//  testThread
//
//  Created by ymluo on 2019/12/6.
//  Copyright © 2019 ymluo. All rights reserved.
//

#import "LYMNSCordition.h"

@interface LYMNSCordition()
@property (strong, nonatomic)NSCondition  *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation LYMNSCordition

- (instancetype)init
{
    if (self = [super init]) {
        //當內部的d條件值等於1的時候,才能加鎖
        self.condition = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    //    [[[NSThread alloc] initWithTarget:self selector:@selector(_one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    
    
}

- (void)__two
{
     [_condition lock];
    NSLog(@"__fnc:%s 2",__func__);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"__fnc:%s 2  sleep",__func__);
        sleep(2);
        [self.condition signal];
        NSLog(@"__fnc:%s 2  sleep untile",__func__);
    });
    [_condition wait];
    [_condition unlock];
    NSLog(@"__fnc:%s 2 end function",__func__);
}

- (void)__one
{
    [_condition lock];
    NSLog(@"__fnc:%s 1",__func__);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"__fnc:%s 1  sleep",__func__);
        sleep(2);
        [self.condition signal];
        NSLog(@"__fnc:%s 1  sleep untile",__func__);
    });
    [_condition wait];
    [_condition unlock];
    [self __two];
}
@end

// 輸出

2019-12-06 09:01:21.794134+0800  __fnc:-[LYMNSCordition __one] 1
2019-12-06 09:01:21.806155+0800  __fnc:-[LYMNSCordition __one]_block_invoke 1  sleep
2019-12-06 09:01:23.806536+0800  __fnc:-[LYMNSCordition __one]_block_invoke 1  sleep untile
2019-12-06 09:01:23.806533+0800  __fnc:-[LYMNSCordition __two] 2
2019-12-06 09:01:23.808100+0800  __fnc:-[LYMNSCordition __two]_block_invoke 2  sleep
2019-12-06 09:01:25.809708+0800  __fnc:-[LYMNSCordition __two]_block_invoke 2  sleep untile
2019-12-06 09:01:25.809708+0800  __fnc:-[LYMNSCordition __two] 2 end function

//
//  LYMNsconditionLock.m
//  testThread
//
//  Created by ymluo on 2019/12/6.
//  Copyright © 2019 ymluo. All rights reserved.
//

#import "LYMNsconditionLock.h"
@interface LYMNsconditionLock()
@property (strong, nonatomic)NSConditionLock  *conditionLock;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation LYMNsconditionLock

- (instancetype)init
{
    if (self = [super init]) {
        //當內部的d條件值等於1的時候,才能加鎖
        self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__third) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(_one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    
  
}


/**
 因爲加鎖的內部條件是condition等於1 所以該線程的方法會等待線程2的__two執行完畢
 */
- (void)_one
{
    [self.conditionLock lockWhenCondition:2];
    NSLog(@"__fnc:%s 2",__func__);
    
    sleep(1);
    
    [self.conditionLock unlockWithCondition:3];
}
/**
 因爲加鎖的內部條件是condition等於1 所以該線程的方法會最先執行
 */
- (void)__two
{
    [self.conditionLock lockWhenCondition:1];
    NSLog(@"__fnc:%s 1",__func__);
    dispatch_async(dispatch_get_main_queue(), ^{
            
    });
     sleep(1);
    
    [self.conditionLock unlockWithCondition:2];
}
/**
 因爲加鎖的內部條件是condition等於1 所以該線程的方法會等到 1和2都執行完畢且condition的值等於開始執行下一個 方法
 */
- (void)__third{
    [self.conditionLock lockWhenCondition:3];
   NSLog(@"__fnc:%s 3",__func__);
    
    sleep(1);
    
    [self.conditionLock unlockWithCondition:3];
    
}
@end



2019-12-06 09:07:16.887674+0800  __fnc:-[LYMNsconditionLock __two] 1
2019-12-06 09:07:17.893224+0800  __fnc:-[LYMNsconditionLock _one] 2
2019-12-06 09:07:18.898025+0800  __fnc:-[LYMNsconditionLock __third] 3

5.3 dispatch_semaphore 信號量

信號量可以控制同時最大併發數,也可當做線程鎖使用;

  • 1: 線程數控制:
#pragma mark - 控制同時執行線程數(控制最大併發數)
- (void)otherTestMaxThred{
    for (NSInteger i = 30; i > 0; i--) {
        NSThread *thread = [[NSThread currentThread]initWithTarget:self selector:@selector(__thestThred:) object:@(i)];
        [thread start];
    }
}
- (void)__thestThred:(id)obj{
//    如果信號量的值 > 0,那麼就讓信號量的值減一,且繼續向下執行;
//    如果 <= o,則等待(線程休眠等待),直到dispatch_semaphore_signal()讓信號量的值 +1,就讓信號量的值 -1,然後繼續執行代碼。
    dispatch_semaphore_wait(_dispatchSemapthore, DISPATCH_TIME_FOREVER);
    sleep(2);
    NSLog(@"thread:%@ i:%@",[NSThread currentThread],obj);
//    讓信號量的值 +1;
    dispatch_semaphore_signal(_dispatchSemapthore);
}
  • 2 同步鎖
#import "LYMSemphoreTest.h"


@interface LYMSemphoreTest ()

@property (nonatomic, strong) dispatch_semaphore_t dispatchSemapthore;  //!<信號量

@end
@implementation LYMSemphoreTest
-(instancetype)init{
    if (self = [super init]) {
        self.dispatchSemapthore = dispatch_semaphore_create(1);
    }
    return self;
}
-(void)scaleTicket{
    //    如果信號量的值 > 0,那麼就讓信號量的值減一,且繼續向下執行;
    //    如果 <= o,則等待(線程休眠等待),直到dispatch_semaphore_signal()讓信號量的值 +1,就讓信號量的值 -1,然後繼續執行代碼。
    dispatch_semaphore_wait(_dispatchSemapthore, DISPATCH_TIME_FOREVER);
    [super scaleTicket];
    //    讓信號量的值 +1;
    dispatch_semaphore_signal(_dispatchSemapthore);
    
}
@end

2019-12-08 11:43:20.307289+0800 還剩19 currenthread:<NSThread: 0x60000201cd40>{number = 3, name = (null)}
2019-12-08 11:43:20.307705+0800 還剩18 currenthread:<NSThread: 0x60000202cec0>{number = 4, name = (null)}
。。。。。
2019-12-08 11:43:20.320493+0800 還剩1 currenthread:<NSThread: 0x60000201cd40>{number = 3, name = (null)}
2019-12-08 11:43:20.320789+0800 還剩0 currenthread:<NSThread: 0x60000201cd40>{number = 3, name = (null)}
  • 3 子線程多任務同步
/**
 線程中等待子線程執行返回後繼續執行任務
 */
- (void)otherTestThred{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
         [self __thestThred1:@(0)];
    });
   
}
- (void)__thestThred1:(id)obj{
     NSLog(@"===begin thread:%@ i:%@",[NSThread currentThread],obj);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"thread 1:%@ i:%@",[NSThread currentThread],obj);
        sleep(2);
        NSLog(@"thread 2:%@ i:%@",[NSThread currentThread],obj);
        //    讓信號量的值 +1;
        dispatch_semaphore_signal(self.dispatchSemapthore);
    });
    dispatch_semaphore_wait(_dispatchSemapthore, DISPATCH_TIME_FOREVER);
    NSLog(@"===end thread:%@ i:%@",[NSThread currentThread],obj);

輸出:

2019-12-08 11:48:44.393387+0800 ===begin thread:<NSThread: 0x6000021eee40>{number = 3, name = (null)} i:0
2019-12-08 11:48:44.393922+0800  thread 1<NSThread: 0x6000021d5c00>{number = 4, name = (null)} i:0
2019-12-08 11:48:46.396430+0800 thread 2<NSThread: 0x6000021d5c00>{number = 4, name = (null)} i:0
2019-12-08 11:48:46.396631+0800 ===end thread:<NSThread: 0x6000021eee40>{number = 3, name = (null)} i:0

5.4 @synchronized

是對mutex遞歸鎖的封裝,參數self,在底層中將其地址作爲key去取出對應的鎖;

 @synchronized (self) {
        // Task
    }

6 鎖性能問題

性能由高到低依次如下:
os_unfair_lock:性能最高,
OSSpinLock:預計線程等待時間很短的時候;cpu資源足夠;很少發生競爭;
dispatch_semaphore:
pthread_mutex:
dispatch_queue(DISPATCH_QUEUE_SERIAL);//同步隊列
NSLock
NSCondition
pthread_mutex():recursive//遞歸鎖
NSRecuriseiveLock
NSConditionLock
@synchronized

7 IO 操作安全問題

多讀單寫:pthread_rwlock 等待的鎖會進入 休眠

@interface LYMIOSafeTest ()
//@property (nonatomic, strong) dispatch_semaphore_t dispatchSemapthore;  //!<信號量
//@property (nonatomic, copy) NSString *filePath;  //!<<#註釋#>
@property (nonatomic, assign) pthread_rwlock_t rwLock;  //!<信號量
@property (nonatomic, copy) NSString *filePath;  //!<<#註釋#>

@end
@implementation LYMIOSafeTest

-(instancetype)init{
    if (self = [super init]) {
        pthread_rwlock_init(&(_rwLock), NULL);
        NSString *documetPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        self.filePath = [documetPath stringByAppendingPathComponent:@"testFile.txt"];
    }
    return self;
}

-(void)otherTest{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(queue, ^{
            [self __writeileThred:@(i)];
        });
        dispatch_async(queue, ^{
            [self __readFileThred:@(i)];
        });
    }
}
- (void)__readFileThred:(id)obj{
    pthread_rwlock_rdlock(&_rwLock);
    if ([[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
        NSString *resultStr = [NSString stringWithContentsOfFile:_filePath encoding:NSUTF8StringEncoding error:nil];
        NSLog(@"__readFileThred=====:str= %@",resultStr);
    }
    sleep(1);
    pthread_rwlock_unlock(&_rwLock);
}
- (void)__writeileThred:(id)obj{
   pthread_rwlock_wrlock(&_rwLock);
    // 字符串讀取的方法
    if ([obj integerValue] == 0) {
        if ([[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
            [[NSFileManager defaultManager] removeItemAtPath:_filePath error:nil];
        }
    }
    if (![[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
        [[NSFileManager defaultManager] createFileAtPath:_filePath contents:nil attributes:nil];
    }
    NSFileHandle *handler = [NSFileHandle fileHandleForUpdatingAtPath:_filePath];
    [handler seekToEndOfFile];
    [handler writeData:[[NSString stringWithFormat:@"\n線程開始寫入數據:%@",obj] dataUsingEncoding:NSUTF8StringEncoding]];
    NSLog(@"__writeileThred thread:%@",[NSThread currentThread]);
    sleep(1);
    pthread_rwlock_unlock(&_rwLock);
}
-(void)dealloc{
    pthread_rwlock_destroy(&_rwLock);
}

輸出:

2019-12-08 14:33:15.376561+0800  __writeileThred thread:<NSThread: 0x600002b4ddc0>{number = 3, name = (null)}
2019-12-08 14:33:16.382122+0800  __readFileThred=====:str= 
線程開始寫入數據:0
2019-12-08 14:33:16.382244+0800  __readFileThred=====:str= 
線程開始寫入數據:0
2019-12-08 14:33:17.385887+0800  __writeileThred thread:<NSThread: 0x600002b7d640>{number = 4, name = (null)}
2019-12-08 14:33:18.392534+0800  __writeileThred thread:<NSThread: 0x600002b4dfc0>{number = 5, name = (null)}
2019-12-08 14:33:19.395925+0800  __readFileThred=====:str= 
線程開始寫入數據:0
線程開始寫入數據:1
線程開始寫入數據:2
2019-12-08 14:33:20.401841+0800  __writeileThred thread:<NSThread: 0x600002b4df40>{number = 6, name = (null)}
2019-12-08 14:33:21.409091+0800  __readFileThred=====:str= 
線程開始寫入數據:0
線程開始寫入數據:1
線程開始寫入數據:2
線程開始寫入數據:3
2019-12-08 14:33:22.413197+0800  __writeileThred thread:<NSThread: 0x600002b7c640>{number = 7, name = (null)}
2019-12-08 14:33:23.417572+0800  __readFileThred=====:str= 
線程開始寫入數據:0
線程開始寫入數據:1
線程開始寫入數據:2
線程開始寫入數據:3
線程開始寫入數據:4

從輸出中可以看出讀可以多個同時執行,而寫同時只能存在一個;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章