iOS多線程知識點總結

1、應用場景數據庫

  1. 異步下載數據,這是多線程技術的一個比較常見的應用場景
    編程

  2. 還有一些比較耗時的操做或者功能(客戶端與服務端的交互;從數據庫中一次性讀取大量數據等),須要在主線程以外,單獨的開闢一個新的線程(子線程/工做線程)來執行。多線程

2、iOS支持的多線程編程方法併發

  1. NSThread
    框架

  2. NSOperation & NSOperationQueue異步

  3. GCDasync

4、線程的建立函數

  1. 建立後臺線程,自動的開啓線程
    spa

//performSelectorInBackground內部會建立一個線程 專門 執行調用 -thread1Click:
[self performSelectorInBackground:@selector(thread1Click:) withObject:@"線程1"];

     2.先建立,本身手動啓動線程線程

NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"線程2"];
//設置名字
thread2.name = @"thread2";
//啓動線程
[thread2 start];
//這種方式建立線程 須要手動啓動

    3.線程一旦建立,會當即執行

[NSThread detachNewThreadSelector:@selector(thread3Click:) toTarget:self withObject:@"線程3"];

5、界面假死

      在開發中經常須要將耗時操做交給子線程/工做線程,而主線程去刷新UI界面

- (void)viewDidLoad {
    [super viewDidLoad];
#if 0
    [self func1];
    [self func2];
    [self func3];
    /*上面的寫法是把耗時操做都交給UI主線程來完成,這樣主線程只有把上面三個函數執行完成 纔會繼續向下執行
    這樣的界面會假死,影響用戶體驗。
    咱們應該把耗時的操做交給子線程來完成,異步完成
    */
#else
    //建立三個子線程 來完成 func1 func2 func3
    [NSThread detachNewThreadSelector:@selector(func1) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(func2) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(func3) toTarget:self withObject:nil]; 
#endif
}
- (void)func1 {
    for (NSInteger i = 0; i < 20; i ++) {
        NSLog(@"func1:i->%ld",i);
        [NSThread sleepForTimeInterval:0.5];
    }
    NSLog(@"func1即將結束");
}
- (void)func2 {
    for (NSInteger i = 0; i < 20; i ++) {
        NSLog(@"func2:i->%ld",i);
        [NSThread sleepForTimeInterval:0.5];
    }
    NSLog(@"func2即將結束");
}
- (void)func3 {
    for (NSInteger i = 0; i < 20; i ++) {
        NSLog(@"func3:i->%ld",i);
        [NSThread sleepForTimeInterval:0.5];
    }
    NSLog(@"func3即將結束");
}

6、監聽線程結束

    能夠創建觀察者,監聽一個線程是否結束

- (void)viewDidLoad {
    [super viewDidLoad];
//要先註冊觀察者
//註冊觀察者的時候內部會專門建立一個線程監聽其餘線程有沒有結束,
//當線程結束時,通常都會發送一個結束通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadWillEnd:) name:NSThreadWillExitNotification object:nil];
//子線程 一旦建立啓動 就會和主線程 同時異步執行
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Click:) object:@"線程1"];
thread1.name = @"thread1";
[thread1 start];
    
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"線程2"];
thread2.name = @"thread2";
[thread2 start];   
}
- (void)thread1Click:(id)obj {
    //打印當前線程和主線程
    NSLog(@"thread:%@ isMain:%d",[NSThread currentThread],[NSThread isMainThread]);
    NSLog(@"%s",__func__);
    for (NSInteger i = 0; i < 10; i ++) {
        NSLog(@"func1_i:%ld",i);
        [NSThread sleepForTimeInterval:1];
    }
    NSLog(@"thread1即將結束");
}
- (void)thread2Click:(id)obj {
    NSLog(@"thread:%@ isMain:%d",[NSThread currentThread],[NSThread isMainThread]);
    NSLog(@"%s",__func__);
    for (NSInteger i = 0; i < 10; i ++) {
        NSLog(@"func2_i:%ld",i);
        [NSThread sleepForTimeInterval:0.2];
    }
    NSLog(@"thread2即將結束");
}
//當監聽到線程結束的時候調用這個函數
//上面的兩個線程結束都會調用這個函數
- (void)threadWillEnd:(NSNotification *)nf {
    NSLog(@"thread:%@ isMain:%d_func:%s",[NSThread currentThread],[NSThread isMainThread],__func__);
    NSLog(@"obj:%@",nf.object);//誰發的通知   
}

- (void)dealloc {
    //最後要移除觀察者
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSThreadWillExitNotification object:nil];
}

7、多個線程之間的通訊

- (void)viewDidLoad {
    [super viewDidLoad];
    //建立3個線程
    _thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Click:) object:@"線程1"];
    [_thread1 start];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"線程2"];
    [thread2 start];
    
    NSThread *thread3  = [[NSThread alloc] initWithTarget:self selector:@selector(thread3Click:) object:@"線程3"];
    [thread3 start];
}
- (void)thread1Click:(id) obj {
    NSLog(@"%s",__func__);
    while (1) {
        if ([[NSThread currentThread] isCancelled]) {
            //要判斷 當前這個線程 是否 被取消過(是否發送過取消信號)
            //若是被取消過,那麼咱們可讓當前函數結束那麼這個線程也就結束了
            
            //break;//結束循環
            NSLog(@"子線程1即將結束");
            //return;//返回 函數
            [NSThread exit];//線程退出
        }
        
        NSLog(@"thread1");
        [NSThread sleepForTimeInterval:0.5];
    }
    NSLog(@"子線程1即將結束");
}
- (void)thread2Click:(id) obj {
    NSLog(@"%s",__func__);
    for (NSInteger i = 0; i < 10; i++) {
        [NSThread sleepForTimeInterval:0.2];
        NSLog(@"thread2:_i:%ld",i);
    }
    NSLog(@"子線程2即將結束");
    //當 thread2 即將結束以後 通知 thread1結束
    
    [_thread1 cancel];
    // cancel 在這裏 只是給_thread1 發送了一個cancel 信號,最終thread1的取消,取決於 thread1的,若是要取消thread1,那麼須要在thread1執行調用的函數內部 進行判斷
}
- (void)thread3Click:(id) obj {
    NSLog(@"%s",__func__);
    for (NSInteger i = 0; i < 10; i++) {
        [NSThread sleepForTimeInterval:0.2];
        NSLog(@"thread3:_i:%ld",i);
    }
    NSLog(@"子線程3即將結束");
}

8、線程鎖

    當多個線程同時操做同一個資源的時候,這時若是不處理,那麼這個資源有可能就會紊亂,達不到咱們想要的效果,因此若是咱們要保證同時訪問的重要數據不紊亂,咱們須要添加線程鎖,阻塞線程,使線程同步。排隊訪問

- (void)viewDidLoad {
    [super viewDidLoad];
    //必需要先建立鎖
    _lock = [[NSLock alloc] init];
    //建立兩個線程 操做同一個資源變量
    [NSThread detachNewThreadSelector:@selector(thread1Click:) toTarget:self withObject:@"線程1"];
    [NSThread detachNewThreadSelector:@selector(thread2Click:) toTarget:self withObject:@"線程2"];   
}
- (void)thread1Click:(id)obj {
#if 0
    [_lock lock];//加鎖  
    NSLog(@"thread1開始");
    for (NSInteger i = 0 ; i < 10; i++) {
        _cnt += 2;//想讓 _cnt 連續+2
        NSLog(@"thread1_cnt:%ld",_cnt);
        [NSThread sleepForTimeInterval:0.2];
    }
    NSLog(@"thread1即將結束");   
    [_lock unlock];//解鎖  
    //訪問資源結束解鎖
#else
    // @synchronized (self){} 相似於 加鎖和解鎖過程 
    @synchronized(self) { 
    //使線程對當前對象進行操做時,同步進行,阻塞線程
    //跟加鎖原理是同樣的,執行 @synchronized(self)會判斷有沒有加鎖,加過鎖那麼阻塞,沒有加鎖就繼續執行
        NSLog(@"thread1開始");
        for (NSInteger i = 0 ; i < 10; i++) {
            _cnt += 2;//想讓 _cnt 連續+2
            NSLog(@"thread1_cnt:%ld",_cnt);
            [NSThread sleepForTimeInterval:0.2];
        }
        NSLog(@"thread1即將結束");
    }
#endif
}
- (void)thread2Click:(id)obj {
    [_lock lock];
    NSLog(@"thread2開始");
    for (NSInteger i = 0 ; i < 10; i++) {
        _cnt -= 5;//讓 _cnt連續-5
        NSLog(@"thread2_cnt:%ld",_cnt);
        [NSThread sleepForTimeInterval:0.2];
    }
    NSLog(@"thread2即將結束");
    [_lock unlock];
}

9、任務隊列

 NSThread操做線程是最基本的類,NSOperation是一個輕量級的線程,任務隊列獲得的子線程的效率要高於NSTread。

NSOperation是以任務爲導向的管理線程機制,將操做(任務)放入到線程池裏,會自動執行,弱化線程的概念(任務:能夠簡單的理解爲線程)

- (void)viewDidLoad {
    [super viewDidLoad];
    //建立一個線程池
    _queue = [[NSOperationQueue alloc] init];
    //設置 一個隊列中 容許 最大 任務的併發 個數
    _queue.maxConcurrentOperationCount = 2;
    //若是寫成1  表示 線程池中的任務 一個一個 串行執行  
    [self createInvocationOperation];
    [self createBlockOperation];
}
/*
 NSOperation 是一個抽象類  NSOperation 方法 須要有子類本身實現
 //建立任務對象 都是 NSOperation 的子類對象
 //NSBlockOperation  NSInvocationOperation
 任務 要和 任務隊列/線程池 結合使用
 */
#pragma mark - block任務
- (void)createBlockOperation {
    //block代碼塊就是一個 任務
    NSBlockOperation *blockOp1 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 10; i++) {
            NSLog(@"block任務:%ld",i);
            [NSThread sleepForTimeInterval:0.3];
        }
    }];  
    [blockOp1 setCompletionBlock:^{
        //block 任務完成以後 會回調 這個block
        NSLog(@"block任務完成");
    }];
    //放在線程池中
    [_queue addOperation:blockOp1];
    
}
#pragma mark - 任務
//第一種任務
- (void)createInvocationOperation {
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation1:) object:@"任務1"];
    //[op1 start]; 任務 默認start 相對於主線程是同步
    //把任務 放入 線程池
    [_queue addOperation:op1];//一旦放入 這個 任務 就會異步啓動   
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation2:) object:@"任務2"];
    //把任務 放入 線程池
    [_queue addOperation:op2];    
    NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation3:) object:@"任務3"];
    //把任務 放入 線程池
    [_queue addOperation:op3];
}
- (void)operation3:(id)obj {
    NSLog(@"obj:%@",obj);
    for (NSInteger i = 0; i < 10; i++) {
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"op3:i->%ld",i);
    }
    NSLog(@"任務3即將結束");
}
- (void)operation2:(id)obj {
    NSLog(@"obj:%@",obj);
    for (NSInteger i = 0; i < 10; i++) {
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"op2:i->%ld",i);
    }
    NSLog(@"任務2即將結束");
}
- (void)operation1:(id)obj {
    NSLog(@"obj:%@",obj);
    for (NSInteger i = 0; i < 10; i++) {
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"op1:i->%ld",i);
    }
    NSLog(@"任務1即將結束");
}

10、GCD

 GCD 全稱Grand Central Dispatch(隊列調度),是一套低層API,提供了⼀種新的方法來進⾏併發程序編寫。從基本功能上講,GCD有點像NSOperationQueue,他們都容許程序將任務切分爲多個單一任務,而後提交⾄至⼯工做隊列來併發地或者串⾏行地執⾏行。
GCD是C實現,⽐NSOpertionQueue更底層更高效,而且它不是Cocoa框架的一部分,併發任務會像NSOperationQueue那樣基於系統負載來合適地併發進⾏,串⾏行隊列同一時間只執行單一任務
 GCD的API很大程度上基於block

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //[self createMainQueue];
    
    //[self createPrivateQueue];
    
    [self createGlobalQueue];
}
#pragma mark - 全局隊列
/*
 3.全局隊列
 // 並行隊列(全局)不須要咱們建立,經過dispatch_get_global_queue()方法得到
 // 三個可用隊列
 // 第一個參數是選取按個全局隊列,通常採用DEFAULT,默認優先級隊列
 // 第二個參數是保留標誌,目前的版本沒有任何用處(不表明之後版本),直接設置爲0就能夠了
 // DISPATCH_QUEUE_PRIORITY_HIGH
 // DISPATCH_QUEUE_PRIORITY_DEFAULT
 // DISPATCH_QUEUE_PRIORITY_LOW
 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 */
- (void)createGlobalQueue {
    //全局隊列 內部任務 異步/並行 執行
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (NSInteger i = 0; i < 10; i++) {
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"全局隊列任務1:%ld",i);
        }
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (NSInteger i = 0; i < 10; i++) {
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"全局隊列任務2:%ld",i);
        }
    });
}
#pragma mark - 私有隊列
/*
 2.建立私有隊列 用戶隊列/串行隊列
 // C接口,建立一個私有隊列 ,隊列名是一個C字符串,沒有特別的要求,Apple建議用倒裝的標識符來表示(這個名字,更多用於調試)
 私有隊列內部也是串行操做
 */
- (void)createPrivateQueue {
    //建立一個私有隊列
    //私有隊列 相對於主線程 異步
    //私有隊列內部的任務 是 串行執行
    //下面函數的第一個參數 就是一個標籤字符串 標識隊列
    dispatch_queue_t queue = dispatch_queue_create("com.1507", NULL);
    
    //增長任務
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 10; i++) {
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"私有隊列任務1");
        }
    });
    //增長任務 這兩個任務 是 ?
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 10; i++) {
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"私有隊列任務2");
        }
    });
    //私有隊列 至關於 NSOperationQueue 隊列 內部最大併發個數是1
}
#pragma mark - 主線程隊列
- (void)createMainQueue {
    //主線程隊列 只須要獲取 --》通常都是在子線程獲取 在子線程獲取纔有意義
    //1.獲取主線程隊列 -->主線程隊列內部 都是 串行
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    //dispatch_async 相對於 當前線程 異步給 queue 增長一個任務
    dispatch_async(queue, ^{
        //給主線程 增長一個任務
        for (NSInteger i = 0; i < 10; i++) {
            NSLog(@"主線程任務1_i:%ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
    //增長任務 是異步 的 主線程 隊列內部 是串行執行任務的
    dispatch_async(dispatch_get_main_queue(), ^{
        for (NSInteger i = 0; i < 10; i++) {
            NSLog(@"主線程任務2_i:%ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
}
相關文章
相關標籤/搜索