【iOS】從實際出發理解多線程(二)--NSThread高級操做

上篇文章講了一下NSThread的基本操做,本篇講一下NSThread的一些高級用法。程序員

線程間資源共享&線程加鎖安全

在程序運行過程當中,若是存在多線程,呢麼各個線程讀寫資源就會存在前後、同時讀寫資源的操做,由於實在不一樣線程中,CPU調度過程當中咱們沒法保證哪一個線程會先讀寫資源,哪一個線程後讀寫資源。這就有可能操做數據混亂和錯誤。所以爲了防止數據讀寫混亂和錯誤的發生,咱們要將線程在讀寫數據時加鎖,這樣就能保證操做同一個數據UI小的線程只有一個,當這個線程執行完成以後解鎖,其餘的線程才能操做此數據對象。NSLock/NSConditiongLock/NSRecursivelovk/@synchronized均可以實現線程上鎖的操做網絡

1.@synchronized多線程

直接上例子:12306搶火車票函數

// 首先:開啓兩個線程同時售票
self.tickets = 20;
NSThread *t1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
t1.name = @"售票員A";
[t1 start];
    
NSThread *t2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
t2.name = @"售票員B";
[t2 start];

// 而後:將售票的方法加鎖
- (void)saleTickets{
    while (YES) {
        [NSThread sleepForTimeInterval:1.0];
        //互斥鎖 -- 保證鎖內的代碼在同一時間內只有一個線程在執行
        @synchronized (self){
            //1.判斷是否有票
            if (self.tickets > 0) {
                //2.若是有就賣一張
                self.tickets --;
                NSLog(@"還剩%d張票  %@",self.tickets,[NSThread currentThread]);
            }else{
                //3.沒有票了提示
                NSLog(@"賣完了 %@",[NSThread currentThread]);
                break;
            }
        }
    }

}

 2.NSLockoop

-(BOOL)tryLock;//嘗試加鎖,成功返回YES ;失敗返回NO ,但不會阻塞線程的運行
-(BOOL)lockBeforeDate:(NSDate *)limit;//在指定的時間之前獲得鎖。YES:在指定時間以前得到了鎖;NO:在指定時間以前沒有得到鎖。該線程將被阻塞,直到得到了鎖,或者指定時間過時。
- (void)setName:(NSString*)newName//爲鎖指定一個Name
- (NSString*)name//**返回鎖指定的**name
@property (nullable, copy) NSString *name;線程鎖名稱 

舉個例子性能

NSLock *myLock = [[NSLock alloc] init];
    static NSString *str = @"hello";
    [NSThread detachNewThreadWithBlock:^{
        NSLog(@"%d",[myLock tryLock]);
        [myLock lock];
        NSLog(@"%d",[myLock tryLock]);
        NSLog(@"%@",str);
        str = @"123";
        [myLock unlock];
    }];
    
    [NSThread detachNewThreadWithBlock:^{
        [myLock lock];
        NSLog(@"%@",str);
        str = @"11111";
        [myLock unlock];
    }];

輸出結果不加鎖以前,兩個線程輸出同樣都是hello;枷鎖以後,輸出就會改變hello和123.測試

3.NSConditionLockatom

使用此鎖,在線程沒有得到鎖的狀況下,阻塞,即暫停運行,典型用於生產者/消費者模型。spa

- (instancetype)initWithCondition:(NSInteger)condition;//初始化條件鎖
- (void)lockWhenCondition:(NSInteger)condition;//加鎖 (條件是:鎖空閒,即沒被佔用;條件成立)
- (BOOL)tryLock; //嘗試加鎖,成功返回TRUE,失敗返回FALSE
- (BOOL)tryLockWhenCondition:(NSInteger)condition;//在指定條件成立的狀況下嘗試加鎖,成功返回TRUE,失敗返回FALSE
- (void)unlockWithCondition:(NSInteger)condition;//在指定的條件成立時,解鎖
- (BOOL)lockBeforeDate:(NSDate *)limit;//在指定時間前加鎖,成功返回TRUE,失敗返回FALSE,
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//條件成立的狀況下,在指定時間前加鎖,成功返回TRUE,失敗返回FALSE,
@property (readonly) NSInteger condition;//條件鎖的條件
@property (nullable, copy) NSString *name;//條件鎖的名稱

舉個例子:

NSConditionLock *conditionLock = [[NSConditionLock alloc] init];
    [NSThread detachNewThreadWithBlock:^{
        for (int i = 0; i < 5; i++) {
            [conditionLock lock];
            NSLog(@"當前解鎖條件:%d",i);
            sleep(2);
            [conditionLock unlockWithCondition:i];
            BOOL isLocked = [conditionLock tryLockWhenCondition:2];
            if (isLocked) {
                NSLog(@"%d加鎖成功!!!!!",i);
                [conditionLock unlock];
            } else {
                NSLog(@"%d加鎖失敗!!!!!",i);
            }
            
        }
    }];

輸出結果:

4.NSRecursiveLock

此鎖可再同一線程中屢次被使用,但要保證加鎖和解鎖使用平衡,多用於遞歸函數,防止死鎖。

- (BOOL)tryLock;//嘗試加鎖,成功返回TRUE,失敗返回FALSE
- (BOOL)lockBeforeDate:(NSDate *)limit;//在指定時間前嘗試加鎖,成功返回TRUE,失敗返回FALSE
@property (nullable, copy) NSString *name;//線程鎖名稱

舉個例子:

-(void)initRecycle:(int)value
{
    [self.myRecursive lock];
    if(value>0)
    {
        NSLog(@"當前的value值:%d",value);
        sleep(2);
        [self initRecycle:value-1];
    }
    [self.myRecursive unlock];
}

線性安全之原子屬性atomic

原子屬性(線程安全)與非原子屬性,是什麼意思呢?

蘋果系統在咱們聲明對象屬性時默認是atomic,也就是在讀寫這個屬性時,保證同一時間內只有一個線程能都執行,當聲明時用的是atomic,一般會生成_成員變量,若是同時重寫了getter&setter方法,_a成員變量就不自動生成。實際上原子屬性內部有一個鎖,叫作自旋鎖。

自旋鎖和互斥鎖

共同點
都可以保證線程安全
不一樣點
互斥鎖:若是其餘線程正在執行鎖定的代碼,此線程就會進入休眠狀態,等待鎖打開;而後被喚醒
自旋鎖:若是線程被鎖在外面,那麼就會用死循環的方式一直等待鎖打開!

自旋鎖是一種互斥鎖的實現方式,相比較通常的互斥鎖會在等待期間放棄cpu,自旋鎖則是不斷循環並測試鎖的狀態,會一直佔用cpu。

互斥鎖:用於保護臨界區,確保同一時間只有一個線程訪問數據。對共享資源的訪問,先對互斥量進行加鎖,若是互斥量已經上鎖,調用線程會阻塞,直到互斥量被解鎖。在完成了對共享資源的訪問後,要對互斥量進行解鎖。
臨界區:每一個進程中訪問臨界資源的那段程序稱爲臨界區,每次只容許一個進程進入臨界區,進入後不容許其餘進程進入。
自旋鎖:與互斥量相似,它不是經過休眠使進程阻塞,而是在獲取鎖以前一直處於忙等(自旋)阻塞狀態。用在如下狀況:鎖持有的時間短,並且線程並不但願在從新調度上花太多的成本。"原地打轉"。

信號量:信號量是一個計數器,能夠用來控制多個進程對共享資源的訪問。它常做爲一種鎖機制,防止某進程正在訪問共享資源時,其餘進程也訪問該資源。所以,主要做爲進程間以及同一進程內不一樣線程之間的同步手段。

不管什麼鎖,都很消耗性能,效率不高,因此在咱們平時開發過程當中,會使用nonatomic。

舉個例子

@property (strong, nonatomic) NSObject *myNonatomic;
@property (strong,    atomic) NSObject *myAtomic;

// 當咱們重寫了myAtomic的setter和getter方法時
- (void)setMyAtomic:(NSObject *)myAtomic{
      _myAtomic = myAtomic;
}
- (NSObject *)myAtomic{
    return _myAtomic;
}

// 那麼咱們就必須聲明一個_myAtomic靜態變量
@synthesize myAtomic = _myAtomic;
// 不然系統在編譯的時候找不到 _myAtomic

子線程的Runloop

在介紹子線程上的Runloop以前先來一個有意思的小插曲,咱們來介紹一下Runloop,甚至模擬一個Runloop

Runloop 運行循環
-在目前iOS開發中,幾乎用不到,在之前iOS黑暗時代,程序員會用到
目的:
保證程序不退出
監聽事件
沒有事件讓程序進入休眠
區分模式:
NSDefaultRunLoopMode - 時鐘、網絡事件
NSRunLoopCommonModes - 用戶交互

// 模擬runloop
void click(int type){
    printf("正在運行第%d",type);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        while (YES) {
            printf("請輸入選項 0 表示退出");
            int result = -1;
            scanf("%d",&result);
            if (result == 0) {
                printf("程序結束\n");
                break;
            }else{
                click(result);
            }
        }
    }
    return 0;
}
在iOS中,開闢的子線程上的Runloop是默認不開啓的,而且子線程中的Runloop開啓以後是手動沒法關閉的。那麼當咱們給子線程中重複添加不一樣任務時而且Runloop沒有開啓的狀況下,子線程沒法監聽事件(確切說是子線程的Runloop),咱們後來添加的任務就沒法執行。
可是咱們若是讓子線程Runloop一直工做又浪費資源,下面介紹一個OC中經常使用到的能夠控制子線程Runloop的例子:
首先,Runloop就是一個死循環,那麼咱們就建立一個死循環,而後聲明一個能夠判斷是否應該退出Runloop循環的屬性
@property (assign, nonatomic, getter=isFinished) BOOL finished;

// 建立子線程並添加任務
NSThread *t = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil];
[t start];
self.finished = NO;
[self performSelector:@selector(otherMethod) onThread:t withObject:nil waitUntilDone:NO];

// 在第一個任務中加入死循環
- (void)demo{
    NSLog(@"%@",[NSThread currentThread]);
    //在OC中使用比較多的,退出循環的方式
    while (!self.isFinished) {
        [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
    }
    NSLog(@"能來嗎?");
}

// 在最後添加的任務結束後結束死循環
- (void)otherMethod{
    for (int i = 0; i < 10; i ++) {
        NSLog(@"%s   %@",__FUNCTION__,[NSThread currentThread]);

    }
  //讓上面方法中的死循環結束
   self.finished = YES;
}
相關文章
相關標籤/搜索