天天看點

iOS_多線程四:NSThread + performSelector + 總結一、NSThread二、performSelector三、需要手動加鎖(線程同步)(缺點)三、多線程總結

目錄

一、NSThread

1、一些類方法

2、建立方式

(1)、alloc init建立,但是需要手動開啟

(2)、初始化一個子線程,特點:自動開啟,是類方法

(3)、performSelector隐式建立 (順便說一下performSelector其他方法)

二、performSelector

1、afterDelay在子線程中未執行

2、實作:多次點選, 隻執行最後一次

三、需要手動加鎖(線程同步)(缺點)

三、多線程總結

Prioritize Work with Quality of Service Classes

一、NSThread

是iOS中輕量級得多線程,一個NSThread對象對應一條線程

1、一些類方法

[NSThread mainThread]; // 擷取主線程
[NSThread currentThread]; // 擷取目前線程
// 阻塞目前線程,設定休眠時間,兩種方式實作:
[NSThread sleepForTimeInterval:3];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
[NSThread exit]; // 立即終止主線程之外的所有線程(包括正在執行任務的)
// 注意:需要在掌控所有線程狀态的情況下調用此方法,否則可能會導緻記憶體問題。
//   threadPriority相關的都已禁用,改用qualityOfService(枚舉)代替
[NSThread threadPriority]; // 擷取目前線程優先級
[NSThread setThreadPriority:0.5]; // 設定優先級:0.0~1.0;1.0優先級最高           

複制

2、建立方式

(1)、alloc init建立,但是需要手動開啟

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(network:) object:@{@"name":@"moxiaohui"}];
[thread start];

[thread setName:@"moxiaoyan"]; // 線程名稱
thread.qualityOfService = NSQualityOfServiceUserInteractive;
//  NSQualityOfServiceUserInteractive = 0x21, // 最高優先級, 用于處理 UI 相關的任務
//  NSQualityOfServiceUserInitiated = 0x19, // 次高優先級, 用于執行需要立即傳回的任務
//  NSQualityOfServiceUtility = 0x11, // 普通優先級,主要用于不需要立即傳回的任務
//  NSQualityOfServiceBackground = 0x09, // 背景優先級,用于處理一些使用者不會感覺的任務
//  NSQualityOfServiceDefault = -1 // 預設優先級,當沒有設定優先級的時候,線程預設優先級
thread.stackSize = 8192; // 更改堆棧的大小: 必須 是4KB(1024)的倍數 && 啟動線程之前設定 (建立線程是會有開銷的)
NSUInteger size = thread.stackSize / 1024; // 所占記憶體大小
[thread cancel]; // 不會馬上退出,做了需要退出的标記
[thread isMainThread];  // 是否是主線程
[thread isFinished];  // 是否已經完成
[thread isCancelled]; // 是否已經取消
[thread isExecuting]; // 是否正在執行中


- (void)network:(NSDictionary *)info {
  NSLog(@"執行 %@", [NSThread currentThread]);
  NSLog(@"info: %@", info);
  sleep(2);
  NSLog(@"完成");
}           

複制

(2)、初始化一個子線程,特點:自動開啟,是類方法

@autoreleasepool {
  [NSThread detachNewThreadSelector:@selector(network:) toTarget:self withObject:@{@"name":@"moxiaohui"}];
}           

複制

(3)、performSelector隐式建立 (順便說一下performSelector其他方法)

二、performSelector

// 目前線程中執行
  [self performSelector:@selector(network:) withObject:@{@"name":@"moxiaohui"}]; // 同步
  [self performSelector:@selector(network:) withObject:@{@"name":@"moxiaoyan"} withObject:@{@"name":@"moxiaohui"}];  // 同步

  // 子線程中執行:(耗時操作)
  [self performSelectorInBackground:@selector(network:) withObject:@{@"name":@"moxiaohui"}]; // 異步
  // 主線程中執行:(執行更新UI之類得操作)
  [self performSelectorOnMainThread:@selector(complete) withObject:nil waitUntilDone:YES];
  // waitUntilDone: 表示後面代碼是否需要等待目前方法執行完畢
  // YES: 同步,test執行完,後面的代碼才執行
  // NO: 異步,後面的代碼先執行(哪怕比較費時),test後執行
  NSLog(@"sleep 2 s");
  sleep(2);
  NSLog(@"3");
  // 指定線程中執行
  [self performSelector:@selector(network:) onThread:[NSThread mainThread] withObject:@{@"name":@"moxiaohui"} waitUntilDone:YES];

  // cancel 某一個方法
  [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(afterDelay:) object:@{@"name":@"moxiaoyan"}];
  // cancel 目前對象所有perform方法
  [NSObject cancelPreviousPerformRequestsWithTarget:self];


- (void)afterDelay:(NSDictionary *)info {
  NSLog(@"afterDelay info:%@", info);
}

- (void)network:(NSDictionary *)info {
  NSLog(@"執行 %@", [NSThread currentThread]);
  NSLog(@"info: %@", info);
  sleep(2);
  NSLog(@"完成");
}           

複制

1、afterDelay在子線程中未執行

- (void)afterDelayNowork {
  // 模拟子線程裡執行 afterDelay 方法
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"1");
    // 解決不執行 方法1
    [self performSelector:@selector(complete) withObject:nil afterDelay:0];
    NSLog(@"2");
    // 在子線程裡擷取一下runloop
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; // 捕擷取就不會主動建立
    // 解決後面代碼不執行 方法1.1
    [runLoop run]; // 如果直接用run,在執行完任務後需要用CF架構的方法結束目前loop
    // 解決後面代碼不執行 方法2
    // [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    NSLog(@"3"); // 在loop結束之後才執行
  });
}

- (void)complete {
  NSLog(@"4");
  // 解決後面代碼不執行 方法1.2
  CFRunLoopStop(CFRunLoopGetCurrent()); // 需要手動管理`為子線程建立的RunLoop`的生命周期
}           

複制

總結:

performSelector: withObject: afterDelay:

1. 在子線程中不work:

因為預設是在目前RunLoop中添加計時器延時執行,而子線程的RunLoop預設不開啟,是以不work

2. 會讓目前函數後面的代碼先執行:

因為該方法是異步的,會先入棧,等線程空閑了才執行

3. runloop run方法後代碼不執行:

解決方法1:在執行完任務後需要用CF架構的方法結束目前loop

解決方法2:用runUntilDate方法,在後續時間結束目前loop

2、實作:多次點選, 隻執行最後一次

- (void)testClickAction {
  // 實作:多次點選, 隻執行最後一次
  [self clickAction];
  [self clickAction];
  [self clickAction];
  [self clickAction];
}
- (void)clickAction {
  [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(afterDelay:) object:nil];
  [self performSelector:@selector(afterDelay:) withObject:nil afterDelay:2]; // 2s後執行
}           

複制

三、需要手動加鎖(線程同步)(缺點)

很多大神舉的例子哈,我借鑒一下:多視窗買票的情況,不加鎖,資料會錯亂

#pragma mark - 多視窗買票
- (void)MultiWindowTicket {
  self.totalTicketCount = 20;
  _thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
  _thread1.name = @"視窗1";
  _thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
  _thread2.name = @"視窗2";
  _thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
  _thread3.name = @"視窗3";
  [_thread1 start];
  [_thread2 start];
  [_thread3 start];
}

- (void)saleTicket {
  while (YES) { // 模拟還有票會持續`-1`的操作
//    @synchronized (self) { // 互斥鎖:swift 用 objc_sync_enter(self) 和 objc_sync_exit(self)
      if (self.totalTicketCount > 0) {
        self.totalTicketCount--;
        NSLog(@"買了一張,還剩:%ld %@", (long)self.totalTicketCount, [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.5];
      } else {
        NSLog(@"票買完了");
        break;
      }
//    }
  }
}
// 執行結果:
// 買了一張,還剩:7 <NSThread: 0x600003a41a00>{number = 7, name = 視窗1}
// 買了一張,還剩:5 <NSThread: 0x600003a41a80>{number = 9, name = 視窗3}
// 買了一張,還剩:6 <NSThread: 0x600003a41a40>{number = 8, name = 視窗2}
// 買了一張,還剩:3 <NSThread: 0x600003a41a40>{number = 8, name = 視窗2}
// 買了一張,還剩:4 <NSThread: 0x600003a41a80>{number = 9, name = 視窗3}
// 買了一張,還剩:4 <NSThread: 0x600003a41a00>{number = 7, name = 視窗1}
// 買了一張,還剩:2 <NSThread: 0x600003a41a40>{number = 8, name = 視窗2}
// 買了一張,還剩:2 <NSThread: 0x600003a41a80>{number = 9, name = 視窗3}
// 買了一張,還剩:1 <NSThread: 0x600003a41a00>{number = 7, name = 視窗1}
// 買了一張,還剩:0 <NSThread: 0x600003a41a40>{number = 8, name = 視窗2}
// 票買完了 <NSThread: 0x600003a41a80>{number = 9, name = 視窗3}
// 票買完了 <NSThread: 0x600003a41a00>{number = 7, name = 視窗1}
// 票買完了 <NSThread: 0x600003a41a40>{number = 8, name = 視窗2}           

複制

從執行結果可以看的出來,會有多個視窗在售出一張票後,結算的剩餘票數是一樣的(也就是說他們把同一張票賣給了多個人)

是以NSThread是線程不安全的,需要程式猿自己手動加鎖,保持線程同步!!!

三、多線程總結

GCD、NSOperation、NSThread的優缺點

GCD NSOperation NSThread
實作 C OC OC(Pthread基于C實作)
線程安全 安全 安全 不安全(需要手動加鎖,導緻性能低!!!)
生命周期 自動管理 自動管理 程式猿管理
輕量級别
性能
其他 跟Block結合代碼簡潔 多了些實用功能 (如:順序設定、未執行前取消...) 簡單易用(無需做過多設定), 更直覺操作線程對象

Quality of Service:

NSOperation GCD 說明
User-Interactive Main thread 使用者互動:重新整理頁面、動畫...
User-Initiated High 使用者啟動:打開/儲存文檔、點選...
Default Default GCD全局隊列
Utilize Low 不需要立即得到結果的, 通常有進度條:下載下傳、導入...
Background Background 背景運作,使用者不可見:同步、備份

每個QoS類型都存在一個全局并發隊列,擷取方式如下:

// 第一個參數傳入Quality of service的類型,獲得對應的Queue隊列
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);           

複制

  • GCD 和 NSOperation的差別 

底層實作、依賴關系、KVO、優先級、繼承、效率

GCD設定的優先級是queue的,NSOperation設定的是自身的

(GCD無法設定在執行的block的優先級)

參考官網:

Prioritize Work with Quality of Service Classes

Demo github 位址