iOS多線程詳解

前面已經(jīng)有一篇文章(學(xué)習(xí)GCD看我就夠了)專門介紹了GCD,下面來介紹一下另外三個與多線程相關(guān)的方法

一、pthreads(現(xiàn)在幾乎不用了)

pthread是POSIX thread的簡寫,一套通用的多線程API,適用于Unix、Linux、Windows等系統(tǒng),跨平臺、可移植,使用難度大,C語言框架,線程生命周期由程序員管理,由于iOS開發(fā)幾乎用不到,以下就簡單運用pthread開啟一個子線程,用來處理耗時操作

// 創(chuàng)建線程,并且在線程中執(zhí)行 demo 函數(shù)
- (void)pthreadDemo {
     返回值:
     - 若線程創(chuàng)建成功,則返回0
     - 若線程創(chuàng)建失敗,則返回出錯編號
     */
    pthread_t threadId = NULL;
    NSString *str = @"Hello Pthread";
    // 這邊的demo函數(shù)名作為第三個參數(shù)寫在這里可以在其前面加一個&,也可以不加,因為函數(shù)名就代表了函數(shù)的地址。
    int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));

    if (result == 0) {
        NSLog(@"創(chuàng)建線程 OK");
    } else {
        NSLog(@"創(chuàng)建線程失敗 %d", result);
    }
    // pthread_detach:設(shè)置子線程的狀態(tài)設(shè)置為detached,則該線程運行結(jié)束后會自動釋放所有資源。
    pthread_detach(threadId);
}

// 后臺線程調(diào)用函數(shù)
void *demo(void *params) {
    NSString *str = (__bridge NSString *)(params);

    NSLog(@"%@ - %@", [NSThread currentThread], str);
    return NULL;
}
二、NSThread

NSThread是基于線程使用,輕量級的多線程編程方法(相對GCD和NSOperation),一個NSThread對象代表一個線程,需要手動管理線程的生命周期,處理線程同步等問題。

創(chuàng)建線程
  • 方法一
// 1. 創(chuàng)建線程
 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test:) object:nil];
    thread.name = @"thread1"; //設(shè)置線程名
    [thread start];   // 2. 啟動線程,此方法需要我們手動開啟線程
- (void)test:(NSString *)string {
  NSLog(@"test - %@ - %@", [NSThread currentThread], string);
}

打印如下:

2017-09-29 10:28:44.203914+0800 aegewgr[9577:3142906] test - <NSThread: 0x600000461a40>{number = 3, name = thread1} - (null)

這里我們最好設(shè)置一下線程名,便于我們的調(diào)試

  • 方法二
[NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:@"分離子線程"];

該方法會自動創(chuàng)建一個子線程,并在子線程中執(zhí)行

2017-09-29 10:33:21.702512+0800 aegewgr[9617:3159015] test - <NSThread: 0x6000004621c0>{number = 4, name = (null)} - 分離子線程
  • 方法三
[self performSelectorInBackground:@selector(test:) withObject:@"后臺線程"];

該方法會開啟一條后臺線程,并在后臺線程中執(zhí)行。
上面所有的方法都還有與之對應(yīng)的通過block創(chuàng)建的方法。

另外通過線程間通信的幾個方法

[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>]

[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>]

關(guān)于進程間通信,可以看我另一篇文章Runloop的應(yīng)用與深入理解

還有一下常用的屬性和方法直接去看文檔就可以了。

三、NSOperation和NSOperationQueue

NSOperation是基于GCD開發(fā)的,但是比GCD擁有更強的可控性和代碼可讀性。NSOperation是一個抽象基類,表示一個獨立的計算單元,可以為子類提供有用且線程安全的建立狀態(tài),優(yōu)先級,依賴和取消等操作。我們使用比較多的就是它的子類NSInvocationOperation和NSBlockOperation。不過我們更多的使用是自己繼承并定制自己的操作。

使用NSInvocationOperation

    NSInvocationOperation *invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test:) object:nil];
    [invo start];
    NSLog(@"111");
- (void)test:(NSString *)string {
    sleep(1);
    NSLog(@"test - %@ - %@", [NSThread currentThread], string);
}
2017-09-29 14:19:13.242517+0800 aegewgr[10143:3388734] test - <NSThread: 0x600000078180>{number = 1, name = main} - (null)
2017-09-29 14:19:13.242967+0800 aegewgr[10143:3388734] 111

可以看到NSInvocationOperation是同步并且串行的,所以只是用NSInvocationOperation并沒有什么卵用,主要還是要和NSOperationQueue結(jié)合使用。這個放到后面再講。

使用NSBlockOperation
NSBlockOperation支持并發(fā)的實行一個或多個block,使用起來非常方便

NSBlockOperation *blockOperation = [[NSBlockOperation alloc]init];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block 1 in thread:%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block 2 in thread:%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block 3 in thread:%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block 4 in thread:%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        sleep(1);
        NSLog(@"block 5 in thread:%@",[NSThread currentThread]);
    }];
    [blockOperation start];
    NSLog(@"123");
2017-09-29 14:32:03.710936+0800 aegewgr[10335:3439694] block 1 in thread:<NSThread: 0x60400006d740>{number = 1, name = main}
2017-09-29 14:32:03.710939+0800 aegewgr[10335:3439916] block 3 in thread:<NSThread: 0x60400027a780>{number = 4, name = (null)}
2017-09-29 14:32:03.710943+0800 aegewgr[10335:3439920] block 4 in thread:<NSThread: 0x60400027a840>{number = 5, name = (null)}
2017-09-29 14:32:03.710961+0800 aegewgr[10335:3439919] block 2 in thread:<NSThread: 0x600000270c00>{number = 3, name = (null)}
2017-09-29 14:32:04.712532+0800 aegewgr[10335:3439920] block 5 in thread:<NSThread: 0x60400027a840>{number = 5, name = (null)}
2017-09-29 14:32:04.712932+0800 aegewgr[10335:3439694] 123

注意,這里我故意讓block5 sleep了1秒才執(zhí)行。而123這個輸出也是直到block5執(zhí)行完了才執(zhí)行,所以,NSBlockOperation也是同步的,而block的執(zhí)行是并發(fā)的。至于串行隊列并發(fā)隊列與同步異步的概念可以參考我前面提到的那篇文章

自定義NSOperation

自定義NSOperation分兩種,一種是自定義非并發(fā)的NSOperation,一種是定義并發(fā)的NSOperation的。下面分別介紹。

  • 定義非并發(fā)的NSOperation

如果是自定義非并發(fā)的NSOperation,只需要重寫main方法就夠了。

#import "SerialNSOperation.h"

@implementation SerialNSOperation

- (void)main
{
    NSLog(@"main begin");
    @try {
        //在這里我們要創(chuàng)建自己的釋放池,因為這里我們拿不到主線程的釋放池
        @autoreleasepool {
            // 提供一個變量標(biāo)識,來表示需要執(zhí)行的操作是否完成了,當(dāng)然,沒開始執(zhí)行之前,為NO
            BOOL taskIsFinished = NO;
            // while 保證:只有當(dāng)沒有執(zhí)行完成和沒有被取消,才執(zhí)行自定義的相應(yīng)操作
            while (taskIsFinished == NO && [self isCancelled] == NO){
                // 自定義的操作
                NSLog(@"currentThread = %@", [NSThread currentThread]);
                sleep(10);  // 模擬耗時操作
                // 這里相應(yīng)的操作都已經(jīng)完成,后面就是要通知KVO我們的操作完成了。
                taskIsFinished = YES;
            }
        }
    }
    @catch (NSException * e) {
        NSLog(@"Exception %@", e);
    }
    NSLog(@"main end");
}

然后直接使用

SerialNSOperation *op = [[SerialNSOperation alloc]init];
[op start];
2017-09-29 15:12:59.151481+0800 aegewgr[10524:3564080] main begin
2017-09-29 15:13:09.152082+0800 aegewgr[10524:3564080] currentThread = <NSThread: 0x60400006e1c0>{number = 1, name = main}
2017-09-29 15:13:09.152299+0800 aegewgr[10524:3564080] main end

其實我感覺這個實用性不大。

  • 定義并發(fā)的NSOperation

自定義并發(fā)的NSOperation需要以下步驟:
1.start方法:該方法必須實現(xiàn),
2.main:該方法可選,如果你在start方法中定義了你的任務(wù),則這個方法就可以不實現(xiàn),但通常為了代碼邏輯清晰,通常會在該方法中定義自己的任務(wù)
3.isExecuting isFinished 主要作用是在線程狀態(tài)改變時,產(chǎn)生適當(dāng)?shù)腒VO通知
4.isAsynchronous :必須覆蓋并返回YES;

//.h
#import <Foundation/Foundation.h>

@interface ConcurrentOperation : NSOperation{
    BOOL executing;
    BOOL finished;
}
//.m
#import "ConcurrentOperation.h"

@implementation ConcurrentOperation
- (id)init {
    if(self = [super init])
    {
        executing = NO;
        finished = NO;
    }
    return self;
}
- (BOOL)isAsynchronous {
    return YES;
}
- (BOOL)isExecuting {
    return executing;
}
- (BOOL)isFinished {
    return finished;
}
- (void)start {
    //第一步就要檢測是否被取消了,如果取消了,要實現(xiàn)相應(yīng)的KVO
    if ([self isCancelled]) {
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    //如果沒被取消,開始執(zhí)行任務(wù)
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
    NSLog(@"main begin");
    @try {
        @autoreleasepool {
            //在這里定義自己的并發(fā)任務(wù)
            NSLog(@"自定義并發(fā)操作NSOperation");
            NSThread *thread = [NSThread currentThread];
            NSLog(@"current Thread:%@",thread);
            //任務(wù)執(zhí)行完成后要實現(xiàn)相應(yīng)的KVO
            [self willChangeValueForKey:@"isFinished"];
            [self willChangeValueForKey:@"isExecuting"];
            executing = NO;
            finished = YES;
            [self didChangeValueForKey:@"isExecuting"];
            [self didChangeValueForKey:@"isFinished"];
        }
    }
    @catch (NSException * e) {
        NSLog(@"Exception %@", e);
    }
    NSLog(@"main end");
}
//調(diào)用
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    ConcurrentOperation *op1 = [[ConcurrentOperation alloc]init];
    ConcurrentOperation *op2 = [[ConcurrentOperation alloc]init];
    ConcurrentOperation *op3 = [[ConcurrentOperation alloc]init];
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];    

打印如下:

2017-09-29 15:34:15.158649+0800 aegewgr[10664:3638228] main begin
2017-09-29 15:34:15.158653+0800 aegewgr[10664:3638226] main begin
2017-09-29 15:34:15.158675+0800 aegewgr[10664:3638227] main begin
2017-09-29 15:34:15.158912+0800 aegewgr[10664:3638228] 自定義并發(fā)操作NSOperation
2017-09-29 15:34:15.159321+0800 aegewgr[10664:3638226] 自定義并發(fā)操作NSOperation
2017-09-29 15:34:15.159372+0800 aegewgr[10664:3638227] 自定義并發(fā)操作NSOperation
2017-09-29 15:34:15.159965+0800 aegewgr[10664:3638226] current Thread:<NSThread: 0x60400046b640>{number = 4, name = (null)}
2017-09-29 15:34:15.160014+0800 aegewgr[10664:3638228] current Thread:<NSThread: 0x60000026d140>{number = 5, name = (null)}
2017-09-29 15:34:15.160103+0800 aegewgr[10664:3638227] current Thread:<NSThread: 0x60400046b5c0>{number = 3, name = (null)}
2017-09-29 15:34:15.160799+0800 aegewgr[10664:3638226] main end
2017-09-29 15:34:15.160973+0800 aegewgr[10664:3638227] main end
2017-09-29 15:34:15.161154+0800 aegewgr[10664:3638228] main end

為了展示并發(fā)執(zhí)行,所以我這里使用了NSOperationQueue,后面我在繼續(xù)講這個。

使用NSOperationQueue

NSOperationQueue就是執(zhí)行NSOperation的隊列,我們可以將一個或多個NSOperation對象放到隊列中去執(zhí)行。NSOperationQueue有兩種不同類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上,而自定義隊列在后臺執(zhí)行。

使用起來很簡單:

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];  //主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //自定義隊列
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        //任務(wù)執(zhí)行
}];
[queue addOperation:operation];

我們可以通過設(shè)置 maxConcurrentOperationCount 屬性來控制并發(fā)任務(wù)的數(shù)量,當(dāng)設(shè)置為 1時, 那么它就是一個串行隊列。主對列默認是串行隊列,這一點和 dispatch_queue_t是相似的。前面也說過,NSOperation就是基于GCD開發(fā)的。
NSOperationQueue相對于GCD來說有以下優(yōu)點:

  • 提供了在 GCD 中不那么容易復(fù)制的有用特性。
  • 可以很方便的取消一個NSOperation的執(zhí)行
  • 可以更容易的添加任務(wù)的依賴關(guān)系
  • 提供了任務(wù)的狀態(tài):isExecuteing, isFinished.

以上就是多線程相關(guān)的所有方法了,具體使用什么方法還是看你的需求。如果我講的有什么錯誤的地方希望大家指正。

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

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

  • 歡迎大家指出文章中需要改正或者需要補充的地方,我會及時更新,非常感謝。 一. 多線程基礎(chǔ) 1. 進程 進程是指在系...
    xx_cc閱讀 7,370評論 11 70
  • 1、簡介 NSOperation是蘋果提供給我們的一套多線程解決方案。實際上NSOperation是基于GCD更高...
    WQ_UESTC閱讀 1,043評論 0 6
  • Object C中創(chuàng)建線程的方法是什么?如果在主線程中執(zhí)行代碼,方法是什么?如果想延時執(zhí)行代碼、方法又是什么? 1...
    AlanGe閱讀 1,908評論 0 17
  • 在了解GCD之前,我們首先要知道幾個概念。關(guān)于隊列和同/異步函數(shù)。為了讓讀者更簡單直觀的理解這些概念,我盡可能用最...
    fou7閱讀 936評論 1 2
  • 什么是進程? 進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序。 每個進程之間是獨立的,每個進程均運行在其專用且受保護的內(nèi)存...
    珍此良辰閱讀 1,410評論 1 5

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