iOS多線程知識總結(jié)

多線程基本概念

單核CPU,同一時(shí)間cpu只能處理1個(gè)線程,只有1個(gè)線程在執(zhí)行 。多線程同時(shí)執(zhí)行:是CPU快速的在多個(gè)線程之間的切換。如果線程數(shù)非常多,線程切換會(huì)消耗大量的cpu資源。相同時(shí)間內(nèi),每個(gè)線程被調(diào)度的次數(shù)會(huì)降低,線程的執(zhí)行效率降低 。

優(yōu)點(diǎn):
能適當(dāng)提高程序的執(zhí)行效率
能適當(dāng)提高資源的利用率(CPU&內(nèi)存)
線程上得任務(wù)執(zhí)行完后自動(dòng)銷毀

缺點(diǎn):
開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,每一個(gè)線程都占512KB)
如果開啟大量的線程,會(huì)占用大量的內(nèi)存空間,降低程序的性能
線程越多,cpu在調(diào)用線程上的開銷就越大
程序設(shè)計(jì)更加復(fù)雜,比如線程間的通信、多線程的數(shù)據(jù)共享

主線程:
一個(gè)程序運(yùn)行后,默認(rèn)會(huì)開啟1個(gè)線程,稱為“主線程”或“UI線程”
主線程一般用來 刷新UI界面 ,處理UI事件(比如:點(diǎn)擊、滾動(dòng)、拖拽等事件)
主線程使用注意 ?別將耗時(shí)的操作放到主線程中?耗時(shí)操作會(huì)卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種卡的壞體驗(yàn)

iOS中目前有四種實(shí)現(xiàn)多線程的方案:

1.pthread
2.NSThread
3.GCD
4.NSOperation

pthread

一套在多操作系統(tǒng)上通用的多線程API,跨平臺、可移植性強(qiáng),基于C語言。線程的生命周期需要程序員自己管理,基本不怎么使用。

簡單使用:

創(chuàng)建一個(gè)子線程并在子線程中執(zhí)行一個(gè)函數(shù)。

#import <pthread.h>

#pragma mark --pthread
- (void)pthreadDemo1{
    pthread_t PID;
    //參數(shù)1:pthread_t 線程的標(biāo)示
    //參數(shù)2:pthread_attr_t 線程的屬性
    //參數(shù)3:void*  (*)  (void *)  函數(shù)簽名, void *大約可以理解為oc中的id
    //      返回值  函數(shù)名 參數(shù)
    //參數(shù)4:給函數(shù)(參數(shù)3)的參數(shù)
    //返回值:0 成功,非0 失敗
    int result = pthread_create(&PID, NULL, task, NULL);
    if (result == 0) {
        NSLog(@"ok");
    }else{
        NSLog(@"fail");
    }
}
void * task(void *param){
    NSLog(@"task is running %@",[NSThread currentThread]);
    return NULL;
}
- (IBAction)clicked:(id)sender {
    NSLog(@"main thread:%@",[NSThread currentThread]);
    [self pthreadDemo1];
}
pthreadDemo1.png
- (void)pthreadDemo2{
    pthread_t PID;

    NSString *str = @"str";
//    char * str2 = "str2";   
//    int result = pthread_create(&PID, NULL, task, str2);
    int result = pthread_create(&PID, NULL, task, (__bridge void *)(str));
    if (result == 0) {
        NSLog(@"ok");
    }else{
        NSLog(@"fail");
    }
}
void * task(void *param){
//    printf("%s\n",param);
    
    NSString *str = (__bridge NSString *)(param);
    NSLog(@"正在運(yùn)行的線程: %@,  ->%@",[NSThread currentThread],param);
    NSLog(@"正在運(yùn)行的線程:%@,  ->%@",[NSThread currentThread],str);
    return NULL;
}

注意:ARC默認(rèn)下對OC對象進(jìn)行內(nèi)存管理,不對C變量管理,橋接的作用是讓C變量在合適的時(shí)候進(jìn)行釋放。在ARC中使用到和C語言對應(yīng)的數(shù)據(jù)類型,就應(yīng)該使用__bridge進(jìn)行橋接,在MRC中則不需要。

pthreadDemo2.png

使用pthread需要自己管理線程的生命周期,上面的代碼中創(chuàng)建了線程但是沒有銷毀。

NSThread

面向?qū)ο?,可以直接操作線程對象,基于OC語言。線程的生命周期仍然需要程序員自己進(jìn)行管理,使用頻率不高??梢酝ㄟ^[NSThread currentThread]獲取當(dāng)前線程,以此獲取線程的各種屬性,便于調(diào)試。

創(chuàng)建和啟動(dòng)線程

  • 需要手動(dòng)啟動(dòng)
    //參數(shù)1:對象
    //參數(shù)2:方法
    //參數(shù)3:參數(shù)2方法需要的參數(shù)
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task2) object:nil];
    [thread start];//線程啟動(dòng)后在thread中執(zhí)行task2方法
  • 自動(dòng)啟動(dòng)
[NSThread detachNewThreadSelector:@selector(task2) toTarget:self withObject:nil];

或者:

//隱式創(chuàng)建并自動(dòng)啟動(dòng)線程
[self performSelectorInBackground:@selector(task2) withObject:nil];

后面這兩種創(chuàng)建線程并自動(dòng)啟動(dòng)線程的方法有一個(gè)缺點(diǎn):沒有辦法對線程的一些屬性進(jìn)行設(shè)置。

線程的狀態(tài)

  1. NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task3) object:nil];線程新建狀態(tài)。在內(nèi)存中創(chuàng)建出一個(gè)線程對象。

  2. [thread start];線程就緒狀態(tài)。線程對象被放入可調(diào)度線程池,池中還有別的其他線程對象。等待CPU調(diào)度。

  3. CPU調(diào)度當(dāng)前線程,線程進(jìn)入運(yùn)行狀態(tài)。當(dāng)CPU調(diào)度其他線程,線程回到就緒狀態(tài)。

  4. [NSThread sleepForTimeInterval:1];線程阻塞狀態(tài)。當(dāng)調(diào)用了sleep方法,或者在等待同步鎖。線程對象移出可調(diào)度線程池

  5. 當(dāng)sleep時(shí)間到時(shí)或得到了同步鎖,回到就緒狀態(tài)。

  6. 線程死亡。線程任務(wù)執(zhí)行完畢,自然死亡;強(qiáng)制退出,手動(dòng)殺死。線程一旦死亡了,就不能再次開啟任務(wù)。


- (void)stateDemo{
    //新建狀態(tài)
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task3) object:nil];
    //就緒狀態(tài)
    [thread start];
}
- (void)task3{
    //運(yùn)行
    NSLog(@"正在運(yùn)行的線程:%@",[NSThread currentThread]);
    //阻塞
    NSLog(@"線程即將進(jìn)入阻塞狀態(tài)");
    [NSThread sleepForTimeInterval:1];
//    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];//線程休眠時(shí)間
    //從阻塞->就緒->運(yùn)行。被cpu調(diào)用,進(jìn)入運(yùn)行狀態(tài)
    NSLog(@"線程喚醒");
    //線程死亡??梢宰匀凰劳鲆部梢员皇謩?dòng)殺死,當(dāng)線程任務(wù)執(zhí)行完畢,自動(dòng)進(jìn)入死亡狀態(tài)
    //手動(dòng)殺死
    [NSThread exit];
    NSLog(@"dead");//這句話將不會(huì)被打印出來
 
    //一旦線程死亡,就不能再次開啟任務(wù)
}
stateDemo.png

常用屬性、方法

更多的屬性、方法請參閱文檔

名字屬性。
@property (nullable, copy) NSString *name;
設(shè)置線程名:
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task4) object:nil];
thread.name = @"thread";

獲得當(dāng)前線程
+ (NSThread *)currentThread;
獲得主線程
+ (NSThread *)mainThread;
是否為主線程
- (BOOL)isMainThread; 
線程優(yōu)先級
+ (double)threadPriority;
設(shè)置優(yōu)先級:
- (void)attrDemo2{
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task4) object:nil];
    thread.name = @"thread";
    thread.threadPriority = 0;
    [thread start];    
    NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(task4) object:nil];
    thread2.name = @"thread2";
    [thread2 start];
}
- (void)task4{
    for (int i = 0; i<20; i++) {
        NSLog(@"當(dāng)前線程:%@,%d",[NSThread currentThread],i);
    }
}

線程優(yōu)先級取值范圍0.0-1.0,默認(rèn)為0.5,值越大優(yōu)先級越高。設(shè)置優(yōu)先級并不意味著優(yōu)先級高的線程要比優(yōu)先級低的線程先運(yùn)行(運(yùn)行反映的結(jié)果就是,打印結(jié)果里面顯示的第一條是哪個(gè)thread),只是更可能被CPU執(zhí)行到。先執(zhí)行哪個(gè)線程是由cpu調(diào)度決定的。


線程間通信

常用方法:NSThread類
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

GCD

旨在替代NSThread等的線程技術(shù),充分利用多核,基于C語言。線程的生命周期是自動(dòng)管理的,不需要程序員手動(dòng)管理線程的創(chuàng)建/銷毀/復(fù)用過程。經(jīng)常使用。

任務(wù)、隊(duì)列

任務(wù)和隊(duì)列是GCD中的兩個(gè)核心概念。
任務(wù)就是要執(zhí)行什么操作,在GCD中通過block來指定要執(zhí)行的代碼。隊(duì)列用來存放任務(wù)。

GCD使用的兩個(gè)步驟:
1.定制任務(wù):確定想做的事情
2.將任務(wù)添加到隊(duì)列中,指定運(yùn)行方式。
GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行。任務(wù)的取出遵循隊(duì)列的FIFO原則,先進(jìn)先出。

    //創(chuàng)建任務(wù)
    //dispatch_block_t的定義typedef void (^dispatch_block_t)(void);任務(wù)實(shí)際上是一個(gè)Block
    dispatch_block_t task = ^{
        NSLog(@"task %@",[NSThread currentThread]);
    };
    //獲取隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);  
    //把任務(wù)放到隊(duì)列中
    //參數(shù)1:隊(duì)列  參數(shù)2:任務(wù)
    dispatch_async(queue, task);    
    //簡化
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"task %@",[NSThread currentThread]);
    });

不需要管理線程的生命周期;線程能夠復(fù)用

    for (int i = 0; i < 20; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"task %@ %d",[NSThread currentThread] , i);
        });
    }
GCD復(fù)用線程.png
  • 隊(duì)列
    隊(duì)列的類型:并發(fā)隊(duì)列、串行隊(duì)列。
    并發(fā)隊(duì)列:可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開啟多個(gè)線程“同時(shí)”執(zhí)行任務(wù))。并發(fā)功能只有在異步時(shí)才有效。
    串行隊(duì)列:無論是同步異步,任務(wù)都是一個(gè)接一個(gè)地執(zhí)行。
    無論是串行隊(duì)列還是并發(fā)隊(duì)列,隊(duì)列里面的任務(wù)取出都遵循FIFO原則。并發(fā)隊(duì)列取出任務(wù)就分發(fā)到可用的線程里,取出的動(dòng)作很快,就相當(dāng)于是所有任務(wù)都是一起執(zhí)行的。
    全局并發(fā)隊(duì)列:供整個(gè)應(yīng)用使用,GCD有函數(shù)可以獲得,不需要手動(dòng)創(chuàng)建
    主隊(duì)列:它是特殊的串行隊(duì)列,又叫全局串行隊(duì)列,代表著主線程。

隊(duì)列的創(chuàng)建:
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);第一個(gè)參數(shù)是唯一標(biāo)識符,也可以當(dāng)做是隊(duì)列的名稱,用于debug。第二個(gè)參數(shù)是隊(duì)列的屬性,表示創(chuàng)建的是并行隊(duì)列還是串行隊(duì)列,DISPATCH_QUEUE_SERIAL或者NULL為串行,DISPATCH_QUEUE_CONCURRENT為并行。
dispatch_get_main_queue()獲得主隊(duì)列
dispatch_get_global_queue(long identifier, unsigned long flags);獲得全局并發(fā)隊(duì)列.參數(shù)1為隊(duì)列的優(yōu)先級。參數(shù)2暫時(shí)沒有用,填0即可。

全局隊(duì)列 、 并發(fā)隊(duì)列的區(qū)別:
全局隊(duì)列沒有名稱,無論 MRC & ARC 都不需要考慮釋放.
并發(fā)隊(duì)列有名字,和 NSThread 的 name 屬性作用類似;如果在 MRC 開發(fā)時(shí),需要使用 dispatch_release(q); 釋放相應(yīng)的對象;dispatch_barrier 必須使用自定義的并發(fā)隊(duì)列;開發(fā)第三方框架時(shí),建議使用并發(fā)隊(duì)列

  • 任務(wù)
    任務(wù)的執(zhí)行方式:同步、異步。同步異步的區(qū)別是,是否會(huì)阻塞當(dāng)前線程。
    同步執(zhí)行會(huì)阻塞當(dāng)前線程,等到block任務(wù)執(zhí)行完畢,然后當(dāng)前線程再繼續(xù)往下運(yùn)行。異步執(zhí)行不會(huì)阻塞當(dāng)前線程。通常的表現(xiàn)是:同步是在當(dāng)前線程中執(zhí)行,異步是在另一條線程中執(zhí)行,但也有例外。
    通過打斷點(diǎn)來看同步異步的運(yùn)行過程來理解會(huì)更直觀。

看下面的例子:
串行隊(duì)列,同步執(zhí)行

- (void)demo1{
    //創(chuàng)建串行隊(duì)列
    //參數(shù)1:隊(duì)列的名字  參數(shù)2:隊(duì)列的屬性
    dispatch_queue_t serialQueue =  dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10 ;i++ ){
        dispatch_sync(serialQueue, ^{
            NSLog(@"serialQueue %@ %d",[NSThread currentThread],i);
        });
    }
    //用途:在多個(gè)線程時(shí),要確保在一個(gè)線程執(zhí)行完任務(wù)再去執(zhí)行另一個(gè)線程上的任務(wù)    
}
串行隊(duì)列,同步執(zhí)行-多任務(wù).png

運(yùn)行結(jié)果顯示:不開線程,在當(dāng)前線程下執(zhí)行(阻塞當(dāng)前線程);任務(wù)是有序執(zhí)行的。

串行隊(duì)列,異步執(zhí)行

- (void)demo2{
    dispatch_queue_t serialQueue =  dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10 ;i++ ){
        dispatch_async(serialQueue, ^{
            NSLog(@"serialQueue %@ %d",[NSThread currentThread],i);
        });
    }    
}
串行隊(duì)列,異步執(zhí)行-多任務(wù).png

運(yùn)行結(jié)果顯示:另外開一個(gè)線程(不會(huì)阻塞當(dāng)前線程);任務(wù)是有序執(zhí)行的。
修改demo1 demo2不進(jìn)行循環(huán)創(chuàng)建,通過打斷點(diǎn)來看同步異步的區(qū)別:異步,當(dāng)代碼塊執(zhí)行時(shí)demo2:方法已經(jīng)退出了;同步,demo1:一直等到代碼塊執(zhí)行完才退出

并行隊(duì)列,異步執(zhí)行

- (void)demo3{
    //創(chuàng)建并行隊(duì)列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    for (int i =0; i<10; i++) {
        dispatch_async(concurrentQueue, ^{
            NSLog(@"concurrent %@ %d",[NSThread currentThread],i);
        });
    }
}

運(yùn)行結(jié)果顯示:開多個(gè)線程(不會(huì)阻塞當(dāng)前線程),任務(wù)無序執(zhí)行。效率最大。每次開啟多少個(gè)線程是不固定的(線程數(shù)由GCD來決定)

并行隊(duì)列,同步執(zhí)行

- (void)demo4{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    for (int i =0; i<10; i++) {
        dispatch_sync(concurrentQueue, ^{
            NSLog(@"concurrent %@ %d",[NSThread currentThread],i);
        });
    }
}

運(yùn)行結(jié)果顯示:不開線程,在當(dāng)前線程下執(zhí)行(當(dāng)前線程不一定是主線程);任務(wù)是有序執(zhí)行的。這種情況等同于,串行隊(duì)列同步執(zhí)行

主隊(duì)列,異步執(zhí)行

- (void)demo5{
    //得到主隊(duì)列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    for (int i =0; i<10; i++) {
        dispatch_async(mainQueue, ^{
            NSLog(@"mainQueue %@",[NSThread currentThread]);
        });
    }
}
主隊(duì)列,異步執(zhí)行.png

主隊(duì)列是特殊的串行隊(duì)列,永遠(yuǎn)在主線程執(zhí)行。運(yùn)行結(jié)果顯示:任務(wù)順序執(zhí)行,由于是異步,因此并不會(huì)阻塞當(dāng)前線程(主線程)。

主隊(duì)列,同步執(zhí)行

- (void)demo6{
     NSLog(@"開始");
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_sync(mainQueue, ^{
        NSLog(@"mainQueue %@",[NSThread currentThread]);//這個(gè)任務(wù)要等待-demo6執(zhí)行完成才能繼續(xù)執(zhí)行
    });
    NSLog(@"結(jié)束");
}

運(yùn)行的結(jié)果顯示,只會(huì)打印出“開始”這一句,然后后面主線程就卡死了。程序死鎖。
原因:同步執(zhí)行會(huì)阻塞當(dāng)前線程。當(dāng)前-demo6方法在主線程中執(zhí)行,它把block任務(wù)放到主隊(duì)列中執(zhí)行,即執(zhí)行到dispatch_sync()的時(shí)候,主線程就被阻塞了。主線程要等到任務(wù)執(zhí)行完成之后才會(huì)繼續(xù)往下執(zhí)行,而block任務(wù)要等到主線程中的-demo6方法執(zhí)行完之后才能執(zhí)行。由此造成死鎖,主線程卡死。

總結(jié):


線程間通信

dispatch_async(dispatch_get_global_queue(0, 0), ^{  //也叫后臺執(zhí)行
    // 執(zhí)行耗時(shí)的異步操作...
      dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主線程,執(zhí)行UI刷新操作
        });
});

隊(duì)列組

隊(duì)列組可以將很多隊(duì)列添加到一個(gè)組里,當(dāng)這個(gè)組里所有的任務(wù)都執(zhí)行完了,隊(duì)列組會(huì)通過一個(gè)方法通知我們。
假設(shè)現(xiàn)在有這樣一個(gè)需求:分別異步執(zhí)行兩個(gè)耗時(shí)操作;等兩個(gè)異步操作都執(zhí)行完畢,再回到主線程執(zhí)行操作。這時(shí)可以使用隊(duì)列組來高效實(shí)現(xiàn)。

-(void)demo{
    NSLog(@"begin");
    //創(chuàng)建組
    dispatch_group_t group = dispatch_group_create();
    //開啟異步任務(wù),參數(shù)1:隊(duì)列組;參數(shù)2:隊(duì)列;參數(shù)3:任務(wù)
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:arc4random_uniform(5)];
        NSLog(@"下載 文件1.zip %@",[NSThread currentThread]);
    }); 
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:arc4random_uniform(5)];
        NSLog(@"下載 文件2.zip %@",[NSThread currentThread]);
    });
    //完成隊(duì)列組的任務(wù)后進(jìn)行通知,參數(shù)1:隊(duì)列組;參數(shù)2:隊(duì)列;參數(shù)3:任務(wù)
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"下載完成 %@",[NSThread currentThread]);
    });
}

一次性執(zhí)行

被調(diào)用多次,但只會(huì)執(zhí)行一次。它是線程安全的

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//    for (int i = 0; i < 20; i++) {
//        [self demo];
//    }    
    //多線程測試
        for (int i = 0; i < 20; i++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self demo];
            });
        }  
}
-(void)demo{
    NSLog(@"這句話執(zhí)行多次");
    static dispatch_once_t onceToken;
    NSLog(@"%ld",onceToken);
    dispatch_once(&onceToken, ^{
        NSLog(@"once");
    });
}

dispatch_once_t 實(shí)際上被定義為long類型,demo方法中打印出onceToken的值中可以看出,在代碼第一次被調(diào)用的時(shí)候,onceToken的值為0(即使在多線程下可能會(huì)有多個(gè)0值打印出來),此時(shí)block里面的代碼會(huì)被執(zhí)行而且只會(huì)執(zhí)行一次。當(dāng)代碼再次被調(diào)用,onceToken的值為非0,block代碼不會(huì)被執(zhí)行。onceToken值相當(dāng)于是一個(gè)flag,用來標(biāo)記block里面的代碼是否已經(jīng)被執(zhí)行過。

單例模式

OC里面實(shí)現(xiàn)單例在此不進(jìn)行詳述,用到了 GCD 的 dispatch_once方法。
實(shí)現(xiàn)單例還可以用互斥鎖,但相比GCD性能要差得多(沒有得到鎖的線程一直在等待。)

@implementation Singleton
+(instancetype)configSync{
    static Singleton *instance;    
    @synchronized(self) {
        if(instance == nil){
            instance = [[Singleton alloc] init];
        }
    }    
    return instance;    
}
@end

NSOperation

面向?qū)ο?,基于GCD封裝,比GCD多一些簡單的功能,基于OC語言。線程的生命周期是自動(dòng)管理的,經(jīng)常使用。
NSOperationNSOperationQueue就相當(dāng)于是GCD 的任務(wù)和隊(duì)列。

NSOperation實(shí)現(xiàn)多線程的具體步驟
1.將需要執(zhí)行的任務(wù)封裝到一個(gè)NSOperation對象中
2.將NSOperation對象添加到NSOperationQueue中
3.系統(tǒng)會(huì)自動(dòng)將NSOperationQueue中的NSOperation取出來
4.NSOperation封裝的任務(wù)被放到一條新線程中執(zhí)行

任務(wù)NSOperation

NSOperation是一個(gè)抽象類,不具備封裝任務(wù)的能力,不可以直接使用,它只是約束子類都具有共同的屬性和方法。因此必須使用它的子類:
1.NSInvocationOperation
2.NSBlockOperation
3.自定義子類,繼承NSOperation,實(shí)現(xiàn)內(nèi)部相應(yīng)的方法

子類創(chuàng)建operation使用的方法雖不盡相同,但最后都需要調(diào)用start方法來啟動(dòng)執(zhí)行任務(wù)。默認(rèn)情況下,調(diào)用start方法后不會(huì)新開一個(gè)線程執(zhí)行任務(wù),而是在當(dāng)前線程同步執(zhí)行,只有將NSOperation放入一個(gè)NSOperationQueue中,才會(huì)異步執(zhí)行操作。

  • NSInvocationOperation比較少用
 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
 //創(chuàng)建operation
 NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo1) object:nil];//任務(wù)在主線程(當(dāng)前線程)運(yùn)行
  //啟動(dòng)任務(wù)
  [op start];
}

 - (void)demo1{
    NSLog(@"任務(wù)1當(dāng)前線程----%@",[NSThread currentThread]);//在主線程運(yùn)行
}
  • NSBlockOperation
 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//創(chuàng)建operation
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"當(dāng)前線程----%@",[NSThread currentThread]);
    }];//還是主線程
    //額外的任務(wù)在子線程執(zhí)行
    [op addExecutionBlock:^{
        NSLog(@"當(dāng)前線程----%@",[NSThread currentThread]);
    }];
    [op start];
}

紅框 并發(fā)執(zhí)行

addExecutionBlock方法給operation添加額外的任務(wù),這時(shí)operation中的所有任務(wù)并發(fā)執(zhí)行(當(dāng)前線程和其他子線程)
只要NSBlockOperation封裝的任務(wù)數(shù)大于1,就會(huì)異步執(zhí)行?

  • 自定義子類
    當(dāng)以上兩個(gè)子類無法滿足需求時(shí),又或者需要封裝任務(wù)等等,就需要自定義operation。
    自定義operation需要繼承NSOperation類,并實(shí)現(xiàn)main 方法,因?yàn)樵谡{(diào)用start方法的時(shí)候,內(nèi)部會(huì)調(diào)用main方法完成相關(guān)邏輯。重寫main方法的注意點(diǎn):
    1.自己創(chuàng)建自動(dòng)釋放池(因?yàn)槿绻钱惒讲僮?,無法訪問主線程的自動(dòng)釋放池)【這個(gè)不太理解】
    2.經(jīng)常通過-(BOOL)isCancelled方法檢測操作是否被取消,對取消作出響應(yīng)
    具體舉例下面用到再細(xì)說。

隊(duì)列NSOperationQueue

NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但因?yàn)槭峭綀?zhí)行,會(huì)占用當(dāng)前線程。但如果把NSOperation添加到NSOperationQueue中,就可以異步執(zhí)行任務(wù)。

  • NSOperationQueue隊(duì)列類型
    1.主隊(duì)列 [NSOperationQueue mainQueue]添加到主隊(duì)列中的任務(wù)都會(huì)放到主線程中執(zhí)行
    2.其他隊(duì)列(包括串行、并發(fā)),使用alloc init方式創(chuàng)建。任務(wù)只要添加到隊(duì)列,系統(tǒng)就會(huì)自動(dòng)異步執(zhí)行任務(wù)(自動(dòng)調(diào)用start方法)。
    用maxConcurrentOperationCount(最大并發(fā)數(shù))屬性控制是串行還是并發(fā)隊(duì)列

  • 添加任務(wù)的方法:

 - (void)addOperation:(NSOperation *)op;
 - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
 - (void)operationQueueDemo1{
    //將NSOperation添加到NSOperationQueue中,系統(tǒng)會(huì)自動(dòng)**異步執(zhí)行**任務(wù)
    //創(chuàng)建隊(duì)列
    NSOperationQueue *opqueue = [[NSOperationQueue alloc]init];
    //創(chuàng)建operation
    NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo1) object:nil];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)2當(dāng)前線程----%@",[NSThread currentThread]);
    }];
    
    [op2 addExecutionBlock:^{
        NSLog(@"任務(wù)3當(dāng)前線程----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)4當(dāng)前線程----%@", [NSThread currentThread]);
    }];
    
    CustomOperation *op4 = [[CustomOperation alloc] init];//自定義operation
    
    // 添加任務(wù)到隊(duì)列中
    [opqueue addOperation:op];//一旦添加,就會(huì)執(zhí)行(自動(dòng)start),并發(fā),且每添加一個(gè)operation開一條子線程
    [opqueue addOperation:op2];
    [opqueue addOperation:op3];
    [opqueue addOperation:op4];//添加自定義operation,自動(dòng)調(diào)用start,start調(diào)用main
    //添加任務(wù)的另一種更簡潔的方式,和上面是等價(jià)的
    [opqueue addOperationWithBlock:^{
        NSLog(@"block添加任務(wù),block當(dāng)前線程----%@",[NSThread currentThread]);
    }];
}

ps:在這里使用了自定義operation

 #import "CustomOperation.h"
@implementation CustomOperation
//封裝自定義任務(wù),自動(dòng)調(diào)用這個(gè)方法
 - (void)main
{
    NSLog(@"自定義operation");
}
@end
  • 隊(duì)列最大并發(fā)數(shù)
    @property NSInteger maxConcurrentOperationCount;這個(gè)屬性用來設(shè)置最多可以讓多少個(gè)任務(wù)同時(shí)執(zhí)行。所以,把maxConcurrentOperationCount的值設(shè)為1,那么隊(duì)列就是串行的了!
 - (void)operationQueueDemo2{
    NSOperationQueue *opqueue = [[NSOperationQueue alloc]init];    
//    opqueue.maxConcurrentOperationCount = 2;
    //串行隊(duì)列
    opqueue.maxConcurrentOperationCount = 1;
    
    [opqueue addOperationWithBlock:^{
        NSLog(@"block添加任務(wù),任務(wù)1當(dāng)前線程----%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    [opqueue addOperationWithBlock:^{
        NSLog(@"block添加任務(wù),任務(wù)2當(dāng)前線程----%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    [opqueue addOperationWithBlock:^{
        NSLog(@"block添加任務(wù),任務(wù)3當(dāng)前線程----%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    [opqueue addOperationWithBlock:^{
        NSLog(@"block添加任務(wù),任務(wù)4當(dāng)前線程----%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    [opqueue addOperationWithBlock:^{
        NSLog(@"block添加任務(wù),任務(wù)5當(dāng)前線程----%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
}

maxConcurrentOperationCount=2時(shí)運(yùn)行結(jié)果:



maxConcurrentOperationCount=1時(shí)運(yùn)行結(jié)果:



任務(wù)執(zhí)行在哪個(gè)線程不是固定的。比如當(dāng)前最大并發(fā)數(shù)為2,在同一時(shí)間并發(fā)執(zhí)行任務(wù)的線程的確有2個(gè),但并不是固定一直都是這兩個(gè)線程去處理任務(wù)。當(dāng)一個(gè)線程執(zhí)行完任務(wù)后,由系統(tǒng)來決定銷毀它另開線程還是繼續(xù)使用這個(gè)線程來執(zhí)行任務(wù)
  • 隊(duì)列的暫停、恢復(fù)、取消
    暫停、恢復(fù):suspended屬性
    要執(zhí)行suspended=yes時(shí)候,隊(duì)列掛起,但如果有任務(wù)還沒執(zhí)行完,那么這個(gè)任務(wù)將會(huì)繼續(xù)執(zhí)行到完成。而隊(duì)列中余下的任務(wù)就會(huì)被掛起不執(zhí)行。
    當(dāng)suspended屬性設(shè)為No的時(shí)候,再繼續(xù)執(zhí)行余下的任務(wù)。

隊(duì)列取消:- (void)cancelAllOperations;
cancelAllOperations取消隊(duì)列所有的任務(wù),取消了就不會(huì)再恢復(fù)。This method calls the cancel method on all operations currently in the queue.同樣,如果有任務(wù)還沒執(zhí)行完,那么這個(gè)任務(wù)將會(huì)繼續(xù)執(zhí)行到完成。
如果自定義operation中有多個(gè)耗時(shí)的操作,建議在main方法中在每個(gè)耗時(shí)操作后判斷任務(wù)是否已經(jīng)被取消了,如果取消了,余下的耗時(shí)操作將不再執(zhí)行

#import "CustomOperation.h"
@implementation CustomOperation
 - (void)main
{
    for (NSInteger i = 0; i<1000; i++) {
        NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
    }
    if (self.isCancelled) return;
    
    for (NSInteger i = 0; i<1000; i++) {
        NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
    }
    if (self.isCancelled) return;
    
    for (NSInteger i = 0; i<1000; i++) {
        NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
    }
    if (self.isCancelled) return;
}
@end
  • 依賴
    NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序。[op1 addDependency:op2]可以讓任務(wù)op1在任務(wù)op2之后執(zhí)行。
    這種任務(wù)依賴是可以跨隊(duì)列的。不能添加相互依賴,會(huì)死鎖,比如 A依賴B,B依賴A。
 - (void)operationQueueDemo4{
    NSOperationQueue *opqueue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)1----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)2----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)3----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)4----%@", [NSThread  currentThread]);
    }];
    //監(jiān)聽任務(wù)是否執(zhí)行完畢,這個(gè)block在任務(wù)完成之后執(zhí)行,而且它不在主線程上,也與它監(jiān)聽的任務(wù)不一定在同一個(gè)線程上
    op4.completionBlock = ^{
        NSLog(@"op4執(zhí)行完畢---%@", [NSThread currentThread]);
    };
    
    // 設(shè)置依賴,而且這種依賴是可以跨隊(duì)列的
    [op2 addDependency:op3];
    
    //添加完依賴再把任務(wù)加到隊(duì)列上
    [opqueue addOperation:op];
    [opqueue addOperation:op2];
    [opqueue addOperation:op3];
    [opqueue addOperation:op4];
}

線程間通信

[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        .......
        // 回到主線程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            ......
        }];
}];

其他

線程同步:

為了防止多個(gè)線程可能會(huì)訪問同一塊資源,引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題,所采取的措施。

  • 互斥鎖:給需要同步的代碼塊加一個(gè)互斥鎖,就可以保證每次只有一個(gè)線程訪問此代碼塊。
    售票問題:
@property (nonatomic , assign)int tickets;

 - (void)viewDidLoad {
    [super viewDidLoad];
    self.tickets = 5;
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(sellTicket) object:nil];
    thread.name = @"窗口1";
    [thread start];    
    NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(sellTicket) object:nil];
    thread2.name = @"窗口2";
    [thread2 start];
}
 - (void)sellTicket{
    while (YES) {
        //模擬網(wǎng)絡(luò)延時(shí)
        [NSThread sleepForTimeInterval:1];
        if (self.tickets > 0) {
            self.tickets = self.tickets - 1;
            NSLog(@"%@ 剩余票數(shù)%d",[NSThread currentThread],self.tickets);
            continue;
        }
        NSLog(@"賣完了");
        break;
    }
}

運(yùn)行結(jié)果:產(chǎn)生數(shù)據(jù)錯(cuò)亂問題


解決方法:使用互斥鎖(同步鎖)
格式:@synchronized(鎖對象){需要鎖定的代碼}
優(yōu)缺點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題。但是需要消耗大量的CPU資源。沒有得到鎖的線程一直在等待。
鎖可以是任意對象,默認(rèn)鎖是開著的。

@property (nonatomic , strong) NSObject *obj;
 - (void)viewDidLoad {
    [super viewDidLoad];
    self.tickets = 5;
    self.obj = [[NSObject alloc]init];//obj要初始化,鎖才有效
    ......(創(chuàng)建線程)
}
 - (void)sellTicket{
    while (YES) {
        //模擬網(wǎng)絡(luò)延時(shí)
        [NSThread sleepForTimeInterval:1];
        //鎖是一個(gè)任意對象(任意對象都有一把鎖),默認(rèn)鎖是開著的     
        @synchronized(self.obj) {
            if (self.tickets > 0) {
                self.tickets = self.tickets - 1;
                NSLog(@"%@ 剩余票數(shù)%d",[NSThread currentThread],self.tickets);
                continue;
            }
            NSLog(@"賣完了");
            break;
        }
    }
}

運(yùn)行結(jié)果:是線程安全的


注意:鎖定一份代碼只能用一把鎖,使用多把鎖無效。如果把上面的代碼改成下面這樣,同樣會(huì)產(chǎn)生數(shù)據(jù)錯(cuò)亂問題。因?yàn)楫?dāng)前while循環(huán)中每次循環(huán)都創(chuàng)建一把新的鎖,鎖默認(rèn)都是開著的。

while (YES) {
    [NSThread sleepForTimeInterval:1];
    NSObject *obj = [[NSObject alloc]init];  
    @synchronized(obj) {
        ......
        }
        NSLog(@"賣完了");
        break;
    }
}

atomic 和nonatomic:
nonatomic非原子屬性:多個(gè)線程可以同時(shí)賦值同時(shí)讀取。非線程安全的,適合內(nèi)存小的移動(dòng)設(shè)備。開發(fā)中建議把屬性聲明為nonatomic
atomic原子屬性:多個(gè)線程中只有一個(gè)線程能夠?qū)ψ兞抠x值(為setter加鎖),但多個(gè)線程可以同時(shí)讀取。線程安全,但需要消耗大量資源。原子屬性有自旋鎖
互斥鎖:如果發(fā)現(xiàn)其他線程正在執(zhí)行鎖定的代碼,線程休眠(就緒狀態(tài)),等其他線程開鎖后,線程被喚醒。
自旋鎖:如果發(fā)現(xiàn)其他線程正在執(zhí)行鎖定代碼,線程會(huì)用死循環(huán)的方式一致等待鎖定代碼完成。自旋鎖適合執(zhí)行不耗時(shí)的操作。

不使用互斥鎖,如果把tickets聲明為atomic,再執(zhí)行第一段“售票問題”代碼的話會(huì)怎么樣呢?@property (nonatomic , assign)int tickets;
運(yùn)行結(jié)果顯示,自旋鎖仍然會(huì)有線程安全問題。因?yàn)樗粚etter加鎖,對Getter不加鎖。

延時(shí)執(zhí)行:

方式1:NSTimer

 NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(task) userInfo:nil repeats:NO];

方式2:

[self performSelector:@selector(task) withObject:nil afterDelay:1];

方法3:GCD

    //參數(shù)1:延時(shí)的時(shí)間 dispatch_time生成時(shí)間 納秒為計(jì)時(shí)單位 精度高
    //參數(shù)2:隊(duì)列
    //參數(shù)3:任務(wù)
    //異步執(zhí)行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"task");
});

相關(guān)文章:
Cocoa深入學(xué)習(xí):NSOperationQueue、NSRunLoop和線程安全
iOS多線程你看我就夠

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • NSThread 第一種:通過NSThread的對象方法 NSThread *thread = [[NSThrea...
    攻城獅GG閱讀 965評論 0 3
  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項(xiàng)。當(dāng)然也會(huì)給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 699評論 0 0
  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項(xiàng)。當(dāng)然也會(huì)給出幾種多線程的案...
    伯恩的遺產(chǎn)閱讀 275,656評論 251 2,328
  • 時(shí)間:20170621地點(diǎn):YY語言 今天正式啟動(dòng)共讀活動(dòng),按照永澄老大的一貫風(fēng)格,做一件事之間先要想清楚why-...
    山影_S閱讀 676評論 1 1
  • 文字讀取指的是應(yīng)用對用戶輸入的內(nèi)容進(jìn)行匹配識別,若內(nèi)容中包含相應(yīng)的關(guān)鍵字,則應(yīng)用會(huì)觸發(fā)與該關(guān)鍵字相關(guān)的相應(yīng)提示。最...
    _Ammy閱讀 790評論 3 3

友情鏈接更多精彩內(nèi)容