iOS多线程详解

前面已经有一篇文章(学习GCD看我就够了)专门介绍了GCD,下面来介绍一下另外三个与多线程相关的方法

一、pthreads(现在几乎不用了)

pthread是POSIX thread的简写,一套通用的多线程API,适用于Unix、Linux、Windows等系统,跨平台、可移植,使用难度大,C语言框架,线程生命周期由程序员管理,由于iOS开发几乎用不到,以下就简单运用pthread开启一个子线程,用来处理耗时操作

// 创建线程,并且在线程中执行 demo 函数
- (void)pthreadDemo {
     返回值:
     - 若线程创建成功,则返回0
     - 若线程创建失败,则返回出错编号
     */
    pthread_t threadId = NULL;
    NSString *str = @"Hello Pthread";
    // 这边的demo函数名作为第三个参数写在这里可以在其前面加一个&,也可以不加,因为函数名就代表了函数的地址。
    int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));

    if (result == 0) {
        NSLog(@"创建线程 OK");
    } else {
        NSLog(@"创建线程失败 %d", result);
    }
    // pthread_detach:设置子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
    pthread_detach(threadId);
}

// 后台线程调用函数
void *demo(void *params) {
    NSString *str = (__bridge NSString *)(params);

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

NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,需要手动管理线程的生命周期,处理线程同步等问题。

创建线程
  • 方法一
// 1. 创建线程
 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test:) object:nil];
    thread.name = @"thread1"; //设置线程名
    [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)

这里我们最好设置一下线程名,便于我们的调试

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

该方法会自动创建一个子线程,并在子线程中执行

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

该方法会开启一条后台线程,并在后台线程中执行。
上面所有的方法都还有与之对应的通过block创建的方法。

另外通过线程间通信的几个方法

[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 *> *)#>]

关于进程间通信,可以看我另一篇文章Runloop的应用与深入理解

还有一下常用的属性和方法直接去看文档就可以了。

三、NSOperation和NSOperationQueue

NSOperation是基于GCD开发的,但是比GCD拥有更强的可控性和代码可读性。NSOperation是一个抽象基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。我们使用比较多的就是它的子类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结合使用。这个放到后面再讲。

使用NSBlockOperation
NSBlockOperation支持并发的实行一个或多个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秒才执行。而123这个输出也是直到block5执行完了才执行,所以,NSBlockOperation也是同步的,而block的执行是并发的。至于串行队列并发队列与同步异步的概念可以参考我前面提到的那篇文章

自定义NSOperation

自定义NSOperation分两种,一种是自定义非并发的NSOperation,一种是定义并发的NSOperation的。下面分别介绍。

  • 定义非并发的NSOperation

如果是自定义非并发的NSOperation,只需要重写main方法就够了。

#import "SerialNSOperation.h"

@implementation SerialNSOperation

- (void)main
{
    NSLog(@"main begin");
    @try {
        //在这里我们要创建自己的释放池,因为这里我们拿不到主线程的释放池
        @autoreleasepool {
            // 提供一个变量标识,来表示需要执行的操作是否完成了,当然,没开始执行之前,为NO
            BOOL taskIsFinished = NO;
            // while 保证:只有当没有执行完成和没有被取消,才执行自定义的相应操作
            while (taskIsFinished == NO && [self isCancelled] == NO){
                // 自定义的操作
                NSLog(@"currentThread = %@", [NSThread currentThread]);
                sleep(10);  // 模拟耗时操作
                // 这里相应的操作都已经完成,后面就是要通知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

其实我感觉这个实用性不大。

  • 定义并发的NSOperation

自定义并发的NSOperation需要以下步骤:
1.start方法:该方法必须实现,
2.main:该方法可选,如果你在start方法中定义了你的任务,则这个方法就可以不实现,但通常为了代码逻辑清晰,通常会在该方法中定义自己的任务
3.isExecuting isFinished 主要作用是在线程状态改变时,产生适当的KVO通知
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 {
    //第一步就要检测是否被取消了,如果取消了,要实现相应的KVO
    if ([self isCancelled]) {
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    //如果没被取消,开始执行任务
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
    NSLog(@"main begin");
    @try {
        @autoreleasepool {
            //在这里定义自己的并发任务
            NSLog(@"自定义并发操作NSOperation");
            NSThread *thread = [NSThread currentThread];
            NSLog(@"current Thread:%@",thread);
            //任务执行完成后要实现相应的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");
}
//调用
    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] 自定义并发操作NSOperation
2017-09-29 15:34:15.159321+0800 aegewgr[10664:3638226] 自定义并发操作NSOperation
2017-09-29 15:34:15.159372+0800 aegewgr[10664:3638227] 自定义并发操作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

为了展示并发执行,所以我这里使用了NSOperationQueue,后面我在继续讲这个。

使用NSOperationQueue

NSOperationQueue就是执行NSOperation的队列,我们可以将一个或多个NSOperation对象放到队列中去执行。NSOperationQueue有两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。

使用起来很简单:

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];  //主队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //自定义队列
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        //任务执行
}];
[queue addOperation:operation];

我们可以通过设置 maxConcurrentOperationCount 属性来控制并发任务的数量,当设置为 1时, 那么它就是一个串行队列。主对列默认是串行队列,这一点和 dispatch_queue_t是相似的。前面也说过,NSOperation就是基于GCD开发的。
NSOperationQueue相对于GCD来说有以下优点:

  • 提供了在 GCD 中不那么容易复制的有用特性。
  • 可以很方便的取消一个NSOperation的执行
  • 可以更容易的添加任务的依赖关系
  • 提供了任务的状态:isExecuteing, isFinished.

以上就是多线程相关的所有方法了,具体使用什么方法还是看你的需求。如果我讲的有什么错误的地方希望大家指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容

  • 欢迎大家指出文章中需要改正或者需要补充的地方,我会及时更新,非常感谢。 一. 多线程基础 1. 进程 进程是指在系...
    xx_cc阅读 7,169评论 11 70
  • 1、简介 NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高...
    WQ_UESTC阅读 952评论 0 6
  • Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么? 1...
    AlanGe阅读 1,710评论 0 17
  • 在了解GCD之前,我们首先要知道几个概念。关于队列和同/异步函数。为了让读者更简单直观的理解这些概念,我尽可能用最...
    fou7阅读 876评论 1 2
  • 什么是进程? 进程是指在系统中正在运行的一个应用程序。 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存...
    珍此良辰阅读 1,241评论 1 5