天天看點

iOS多線程幾種不同方式簡單使用

1、首先說一下程序與線程

線程是指程序内的一個執行單元,也是程序内的可排程實體.

線程與程序的差別:

(1)位址空間:線程是程序内的一個執行單元;程序至少有一個線程;它們共享程序的位址空間;而程序有自己獨立的位址空間;

(2)資源擁有:程序是資源配置設定和擁有的機關,同一個程序内的線程共享程序的資源

(3)線程是處理器排程的基本機關,但程序不是.

(4)二者均可并發執行.

程序和線程都是由作業系統所體會的程式運作的基本單元,系統利用該基本單元實作系統對應用的并發性。程序和線程的差別在于:

簡而言之,一個程式至少有一個程序,一個程序至少有一個線程.

線程的劃分尺度小于程序,使得多線程程式的并發性高。

另外,程序在執行過程中擁有獨立的記憶體單元,而多個線程共享記憶體,進而極大地提高了程式的運作效率。

線程在執行過程中與程序還是有差別的。每個獨立的線程有一個程式運作的入口、順序執行序列和程式的出口。但是線程不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個線程執行控制。

從邏輯角度來看,多線程的意義在于一個應用程式中,有多個執行部分可以同時執行。但作業系統并沒有将多個線程看做多個獨立的應用,來實作程序的排程和管理以及資源配置設定。這就是程序和線程的重要差別。

程序是具有一定獨立功能的程式關于某個資料集合上的一次運作活動,程序是系統進行資源配置設定和排程的一個獨立機關.

線程是程序的一個實體,是CPU排程和分派的基本機關,它是比程序更小的能獨立運作的基本機關.線程自己基本上不擁有系統資源,隻擁有一點在運作中必不可少的資源(如程式計數器,一組寄存器和棧),但是它可與同屬一個程序的其他的線程共享程序所擁有的全部資源.

一個線程可以建立和撤銷另一個線程;同一個程序中的多個線程之間可以并發執行.

2、iOS多線程幾種不同方式的簡單使用:

(1)、使用系統方法建立多線程:

@interface NSObject (NSThreadPerformAdditions)

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
	// equivalent to the first method with kCFRunLoopCommonModes

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
	// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);

@end
           

由上面代碼可見這是NSObject類的分類,那麼NSObject的所有子類都可以使用

下面直接看代碼

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self systemThread];
    
}

//直接調用UI中封裝的方法,使用最簡單
-(void)systemThread
{
    //添加線程任務
    [self performSelectorInBackground:@selector(task1) withObject:nil];
    [self performSelectorInBackground:@selector(task2) withObject:nil];
    [self performSelectorInBackground:@selector(task3) withObject:nil];
}

-(void)task1
{
    sleep(1);
    NSLog(@"%s,%@",__func__,[NSDate date]);
}

-(void)task2
{
    sleep(2);
    NSLog(@"%s,%@",__func__,[NSDate date]);
}

-(void)task3
{
    sleep(3);
    NSLog(@"%s,%@",__func__,[NSDate date]);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
           

輸出:

2015-11-09 09:57:42.725 04-多線程總結[1061:22514] -[ViewController task1],2015-11-09 01:57:42 +0000
2015-11-09 09:57:43.675 04-多線程總結[1061:22515] -[ViewController task2],2015-11-09 01:57:43 +0000
2015-11-09 09:57:44.725 04-多線程總結[1061:22516] -[ViewController task3],2015-11-09 01:57:44 +0000
           

線程任務之間互不影響,除非發生資源競争。更新UI時将任務添加到主線程performSelectorOnMainThread

(2)NSThread

@class NSArray<ObjectType>, NSMutableDictionary, NSDate, NSNumber, NSString;

NS_ASSUME_NONNULL_BEGIN

@interface NSThread : NSObject  {
@private
    id _private;
    uint8_t _bytes[44];
}
//擷取目前線程
+ (NSThread *)currentThread;
//開辟一個子線程,添加任務,并且立即執行線程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

+ (BOOL)isMultiThreaded;

@property (readonly, retain) NSMutableDictionary *threadDictionary;

+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

+ (void)exit;

+ (double)threadPriority;
//設定線程優先級
+ (BOOL)setThreadPriority:(double)p;

@property double threadPriority NS_AVAILABLE(10_6, 4_0); // To be deprecated; use qualityOfService below

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0); // read-only after the thread is started

+ (NSArray<NSNumber *> *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0);
+ (NSArray<NSString *> *)callStackSymbols NS_AVAILABLE(10_6, 4_0);

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

@property NSUInteger stackSize NS_AVAILABLE(10_5, 2_0);

@property (readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
//下面這種方法建立的線程需要手動開啟線程
- (instancetype)init NS_AVAILABLE(10_5, 2_0) NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);

@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);
//取消線程執行
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//開始線程執行
- (void)start NS_AVAILABLE(10_5, 2_0);

- (void)main NS_AVAILABLE(10_5, 2_0);	// thread body method

@end
           

測試代碼如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self useNSThread];
}


- (void) useNSThread {
    [NSThread detachNewThreadSelector:@selector(task1) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(task2) toTarget:self withObject:nil];
    //下面的方法,需要手動開啟線程
    NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(task3) object:nil];
    [thread start];
}
           

輸出:

2015-11-09 10:30:56.826 04-多線程總結[1131:27495] -[ViewController task1],2015-11-09 02:30:56 +0000
2015-11-09 10:30:57.825 04-多線程總結[1131:27496] -[ViewController task2],2015-11-09 02:30:57 +0000
2015-11-09 10:30:58.822 04-多線程總結[1131:27497] -[ViewController task3],2015-11-09 02:30:58 +0000
           

(3)、NSOperation

NSOperation不能直接使用,隻是定義了一些基本邏輯,它可以通過下面兩個子類使用:

NSInvocationOperation繼承自NSOperation

簡單用法如下

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self useNSOperation];
}

-(void)useNSOperation
{
    //建立多個任務
    NSInvocationOperation *opt1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task1) object:nil];
    NSInvocationOperation *opt2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task2) object:nil];
    NSInvocationOperation *opt3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task3) object:nil];
    //建立一個線程隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //添加任務
    [queue addOperation:opt1];
    [queue addOperation:opt2];
    [queue addOperation:opt3];
    //添加任務
    [queue addOperationWithBlock:^{
        sleep(4);
        NSLog(@"block:%@",[NSDate date]);
    }];
}
           

輸出:

2015-11-09 10:35:04.399 04-多線程總結[1165:28878] -[ViewController task1],2015-11-09 02:35:04 +0000
2015-11-09 10:35:05.420 04-多線程總結[1165:28872] -[ViewController task2],2015-11-09 02:35:05 +0000
2015-11-09 10:35:06.439 04-多線程總結[1165:28875] -[ViewController task3],2015-11-09 02:35:06 +0000
2015-11-09 10:35:07.394 04-多線程總結[1165:28886] block:2015-11-09 02:35:07 +0000
           

NSBlockOperation繼承自NSOperation

簡單用法:

NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block operation");
    }];
    [blockOperation setCompletionBlock:^{
        NSLog(@"wancheng block");
    }];
    [blockOperation start];
           

(4)gcd

基本用法:

//如果線程隊列是同步的,那麼無論加入線程隊列的方式是同步加入還是異步加入,執行所有的任務肯定是同步執行
    //要想所有的任務都異步執行,必須線程隊列是異步隊列,同時,加入的任務方式也是異步地
    
    //建立線程隊列
    //第二個參數表示建立的隊列是同步隊列還是異步隊列
    //DISPATCH_QUEUE_SERIAL:同步隊列
    //DISPATCH_QUEUE_CONCURRENT:異步隊列
    
#if NO
    dispatch_queue_t qt = dispatch_queue_create("dzl1", DISPATCH_QUEUE_CONCURRENT);//dzl1是線程名稱
    
    //async是異步地将一個任務(Blocks)加到隊列中
    dispatch_async(qt, ^{
        [self task1];
    });
    dispatch_async(qt, ^{
        [self task2];
    });
    dispatch_async(qt, ^{
        [self task3];
    });
#endif
    
#if NO
    //線程隊列是異步地
    dispatch_queue_t qt = dispatch_queue_create("dzl2", DISPATCH_QUEUE_CONCURRENT);
    
    //sync是同步地将一個任務(Blocks)加到隊列中
    dispatch_sync(qt, ^{
        [self task1];
    });
    dispatch_sync(qt, ^{
        [self task2];
    });
    dispatch_sync(qt, ^{
        [self task3];
    });
#endif
           

異步加入輸入為:

2015-11-09 11:16:33.872 04-多線程總結[1540:41715] -[ViewController task1],2015-11-09 03:16:33 +0000
2015-11-09 11:16:34.870 04-多線程總結[1540:41672] -[ViewController task2],2015-11-09 03:16:34 +0000
2015-11-09 11:16:35.874 04-多線程總結[1540:41675] -[ViewController task3],2015-11-09 03:16:35 +0000
           

同步加入輸出為:

2015-11-09 11:17:14.431 04-多線程總結[1555:42141] -[ViewController task1],2015-11-09 03:17:14 +0000
2015-11-09 11:17:16.440 04-多線程總結[1555:42141] -[ViewController task2],2015-11-09 03:17:16 +0000
2015-11-09 11:17:19.444 04-多線程總結[1555:42141] -[ViewController task3],2015-11-09 03:17:19 +0000
           

擷取系統線程隊列:

//标準用法
    //系統自帶的線程隊列
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"this is global queue");
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"this is main queue");
    });
           

輸出:

2015-11-09 11:45:14.676 04-多線程總結[1686:46905] this is global queue
2015-11-09 11:45:14.683 04-多線程總結[1686:46797] this is main queue
           

線程組:

//線程組的作用,用來監視一組任務的執行完成狀态
    dispatch_group_t gt = dispatch_group_create();
    //建立一個線程隊列
    dispatch_queue_t qt = dispatch_queue_create("dzl", DISPATCH_QUEUE_SERIAL);
    dispatch_group_async(gt, qt, ^{
        [self task1];
    });
    dispatch_group_async(gt, qt, ^{
        [self task2];
    });
    dispatch_group_async(gt, qt, ^{
        [self task3];
    });
    
    //所有線上程組中的任務執行完成以後,調用以下方法
    dispatch_group_notify(gt, dispatch_get_global_queue(2, 0), ^{
        NSLog(@"gt中的所有任務已經執行完成");
    });
           

輸出:

2015-11-09 11:49:26.947 04-多線程總結[1746:48790] -[ViewController task1],2015-11-09 03:49:26 +0000
2015-11-09 11:49:28.955 04-多線程總結[1746:48790] -[ViewController task2],2015-11-09 03:49:28 +0000
2015-11-09 11:49:31.961 04-多線程總結[1746:48790] -[ViewController task3],2015-11-09 03:49:31 +0000
2015-11-09 11:49:31.962 04-多線程總結[1746:48793] gt中的所有任務已經執行完成
           

中斷分組任務:

dispatch_queue_t qt = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(qt, ^{
        [self task1];
    });
    dispatch_async(qt, ^{
        [self task2];
    });
    
    //中斷:有一個前提,線程隊列(即qt)必須是自己建立的:不能用系統自帶的線程隊列
    dispatch_barrier_async(qt, ^{
        NSLog(@"++++++++++++++");
    });
    
    dispatch_async(qt, ^{
        [self task3];
    });
    dispatch_async(qt, ^{
        NSLog(@"---%@",[NSDate date]);
    });
           

輸出:

2015-11-09 11:51:57.235 04-多線程總結[1769:49646] -[ViewController task1],2015-11-09 03:51:57 +0000
2015-11-09 11:51:58.238 04-多線程總結[1769:49643] -[ViewController task2],2015-11-09 03:51:58 +0000
2015-11-09 11:51:58.243 04-多線程總結[1769:49643] ++++++++++++++
2015-11-09 11:51:58.244 04-多線程總結[1769:49646] ---2015-11-09 03:51:58 +0000
2015-11-09 11:52:01.287 04-多線程總結[1769:49643] -[ViewController task3],2015-11-09 03:52:01 +0000
           

延時任務:

//DISPATCH_TIME_NOW  開始時間
    //接着的參數表示從目前時間向後延時多長時間見
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5ull);
    //第一個參數是執行該任務的時間點
    //第二個參數是在哪個線程隊列中執行
    //第三個參數是任務
    dispatch_after(time, dispatch_get_main_queue(), ^{
        //5秒後背景變為紫色
        self.view.backgroundColor = [UIColor purpleColor];
    });
           

dispatch_apply:

dispathc_apply 是dispatch_sync 和dispatch_group的關聯API.它以指定的次數将指定的Block加入到指定的隊列中。并等待隊列中操作全部完成。輸出 順序不确定,因為它是并行執行的(dispatch_get_global_queue是并行隊列),實際上,如果 dispatch_apply 換成串行隊列上,則會依次輸出index,但這樣會違背了我們想并行提高執行效率的初衷。

測試代碼:

NSMutableArray *_dataArray = [[NSMutableArray alloc]init];
    for (int i = 0; i<20000; i++) {
        [_dataArray addObject:[NSString stringWithFormat:@"model %d",i]];
    }
    //處理互相之間不影響的相似操作,和下标的次序沒有關系

    NSLog(@"--%@",[NSDate date]);
    dispatch_apply(_dataArray.count, dispatch_get_global_queue(0, 0), ^(size_t n) {
        NSLog(@"%@",_dataArray[n]);
    });
    NSLog(@"==%@",[NSDate date]);


    NSLog(@"--%@",[NSDate date]);
    for (NSString *str in _dataArray) {
        NSLog(@"%@",str);
    }
    NSLog(@"==%@",[NSDate date]);
           

輸出:

2015-11-09 12:09:28.899 04-多線程總結[1868:53819] --2015-11-09 04:09:28 +0000
2015-11-09 12:09:28.900 04-多線程總結[1868:53819] model 0
2015-11-09 12:09:28.900 04-多線程總結[1868:53819] model 2
2015-11-09 12:09:28.900 04-多線程總結[1868:53926] model 1
2015-11-09 12:09:28.900 04-多線程總結[1868:53819] model 3
2015-11-09 12:09:28.900 04-多線程總結[1868:53926] model 4
2015-11-09 12:09:28.900 04-多線程總結[1868:53819] model 5
2015-11-09 12:09:28.900 04-多線程總結[1868:53819] model 7
2015-11-09 12:09:28.901 04-多線程總結[1868:53819] model 8
2015-11-09 12:09:28.901 04-多線程總結[1868:53819] model 9
2015-11-09 12:09:28.900 04-多線程總結[1868:53926] model 6
2015-11-09 12:09:28.901 04-多線程總結[1868:53819] model 10
......
......
......
2015-11-09 12:10:23.760 04-多線程總結[1868:53819] model 19997
2015-11-09 12:10:23.760 04-多線程總結[1868:53926] model 19996
2015-11-09 12:10:23.760 04-多線程總結[1868:53819] model 19998
2015-11-09 12:10:23.760 04-多線程總結[1868:53926] model 19999
2015-11-09 12:10:23.761 04-多線程總結[1868:53819] ==2015-11-09 04:10:23 +0000
2015-11-09 12:10:23.762 04-多線程總結[1868:53819] --2015-11-09 04:10:23 +0000
2015-11-09 12:10:23.762 04-多線程總結[1868:53819] model 0
2015-11-09 12:10:23.763 04-多線程總結[1868:53819] model 1
2015-11-09 12:10:23.763 04-多線程總結[1868:53819] model 2
2015-11-09 12:10:23.763 04-多線程總結[1868:53819] model 3
2015-11-09 12:10:23.763 04-多線程總結[1868:53819] model 4
2015-11-09 12:10:23.763 04-多線程總結[1868:53819] model 5
2015-11-09 12:10:23.763 04-多線程總結[1868:53819] model 6
2015-11-09 12:10:23.763 04-多線程總結[1868:53819] model 7
2015-11-09 12:10:23.763 04-多線程總結[1868:53819] model 8
......
......
......
2015-11-09 12:11:26.234 04-多線程總結[1868:53819] model 19992
2015-11-09 12:11:26.234 04-多線程總結[1868:53819] model 19993
2015-11-09 12:11:26.234 04-多線程總結[1868:53819] model 19994
2015-11-09 12:11:26.234 04-多線程總結[1868:53819] model 19995
2015-11-09 12:11:26.234 04-多線程總結[1868:53819] model 19996
2015-11-09 12:11:26.234 04-多線程總結[1868:53819] model 19997
2015-11-09 12:11:26.234 04-多線程總結[1868:53819] model 19998
2015-11-09 12:11:26.234 04-多線程總結[1868:53819] model 19999
2015-11-09 12:11:26.234 04-多線程總結[1868:53819] ==2015-11-09 04:11:26 +0000
           

dispatch_apply 完成兩萬次執行時間:55s

for 循環完成兩萬次執行時間:63s

我測試用的是虛拟機,真機dispatch_apply大概六七秒,for将近十秒,根據不同機器測出的資料肯定不一樣,但是總的說明dispatch_apply比for循環快,但是dispatch_apply的執行時無序的。