iOS开发多线程

iOS开发中,多线程是一个重要的知识点,在进行下面内容之前,有必要先讲一下进程和线程的概念。
进程:系统中正在运行的一个应用程序,每个进程之间都是独立的,每个进程都运行在其专有的空间之内,并且这个空间是受保护的,也就是一个进程是不能去访问另一个进程的独有空间的。进程例如:XCode,Safari,QQ等运行的时候都是一个单独的进程。
进程的五态模型:新建、就绪、运行、阻塞、终止。
线程:线程是进程的基本执行单元,进程的所有任务都在线程中执行,换句话说一个进程想要执行任务必须至少有一个线程,也就是我们常说的主线程,在iOS开发中我们也称之为UI线程。一个进程可以同时开启几条线程,而一个线程只能同时在一个进程内。
多线程的使用场景:
——网络请求
——文件处理
——图片加载
——数据存储
——任务执行
任务执行的方式:
串行:多个任务按照一定顺序执行。
并行:多个任务同时执行。

多线程的优缺点:
优点:1、简化了编程模型 缺点:1、增加程序设计复杂性
2、更加的轻量级 2、占用内存空间
3、提高了执行效率 3、增大cpu调度开销
4、提高了资源的利用率

iOS开发多线程的实现方案:
pThread、NSThread、GCD、NSOperation

一、多线程之pThread

pThread是很多操作系统上都用到的多线程的API,移植性特别强,在iOS平台上也是可以的,不过它是基于C语言框架的,在iOS开发过程中用的很少。

废话少说上代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 50)];
    [btn setTitle:@"pThread" forState:UIControlStateNormal];
    [btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(clickPthread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
}

- (void)clickPthread{
    NSLog(@"我在线程%@中执行",[NSThread currentThread]);
    /*
     参数一:表示传入线程对象的地址,不能为空
     参数二:表示线程的一些属性,可以为空
     参数三:表示指向某个函数的指针,这个不能为空
     参数四:表示函数需要接收的参数,也可以为空
     */
    pthread_t pthread;
    pthread_create(&pthread, NULL, run, NULL);
}

void *run(void *data){
    NSLog(@"我在%@线程中执行",[NSThread currentThread]);
    for (int i = 0; i < 4; i ++) {
        NSLog(@"%d",i);
        sleep(1);
    }
    return NULL;
}

控制台打印结果:

// 2547进程的唯一标示码,178761和179079是线程的唯一标示码
2017-02-23 15:16:55.770 pThreadTest[2547:178761] 我在线程<NSThread: 0x600000065a40>{number = 1, name = main}中执行
2017-02-23 15:16:55.770 pThreadTest[2547:179079] 我在<NSThread: 0x608000068a00>{number = 3, name = (null)}线程中执行
2017-02-23 15:16:55.770 pThreadTest[2547:179079] 0
2017-02-23 15:16:56.771 pThreadTest[2547:179079] 1
2017-02-23 15:16:57.777 pThreadTest[2547:179079] 2
2017-02-23 15:16:58.779 pThreadTest[2547:179079] 3

二、多线程之NSThread

面向对象的,我们可以直接来操控线程的对象。
废话少说上代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 150, 100, 50)];
    [btn setTitle:@"NSThread" forState:UIControlStateNormal];
    [btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(clickNSThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
}

- (void)clickNSThread{
    NSLog(@"我在%@线程中执行",[NSThread currentThread]);
//    //1.通过alloc init的方式创建并执行线程
//    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(runThread1) object:nil];
//    [thread1 start];
//    
//    //2.通过detachNewThreadSelector 方式创建并执行线程
//    [NSThread detachNewThreadSelector:@selector(runThread1) toTarget:self withObject:nil];
    
    //3.通过performSelectorInBackground 方式创建线程
    [self performSelectorInBackground:@selector(runThread1) withObject:nil];
}

- (void)runThread1{
    NSLog(@"我在%@线程中执行",[NSThread currentThread]);
    for (int i = 0; i < 4; i ++) {
        NSLog(@"%d",i);
        sleep(1);
        if (i == 3) {
            //回到主线程执行
            [self performSelectorOnMainThread:@selector(runMainThread) withObject:nil waitUntilDone:YES];
        }
    }
}

- (void)runMainThread{
    NSLog(@"我在%@线程中执行",[NSThread currentThread]);
}

控制台打印结果:

2017-02-23 15:48:49.561 pThreadTest[2642:199011] 我在<NSThread: 0x60000007f9c0>{number = 1, name = main}线程中执行
2017-02-23 15:48:49.562 pThreadTest[2642:199229] 我在<NSThread: 0x600000270cc0>{number = 3, name = (null)}线程中执行
2017-02-23 15:48:49.562 pThreadTest[2642:199229] 0
2017-02-23 15:48:50.628 pThreadTest[2642:199229] 1
2017-02-23 15:48:51.702 pThreadTest[2642:199229] 2
2017-02-23 15:48:52.776 pThreadTest[2642:199229] 3
2017-02-23 15:48:53.852 pThreadTest[2642:199011] 我在<NSThread: 0x60000007f9c0>{number = 1, name = main}线程中执行

设置thread的属性:

    //1.通过alloc init的方式创建并执行线程(用的比较多,因为可以拿到线程对象,设置他的属性)
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(runThread1) object:nil];
    //设置thread的name
    [thread1 setName:@"name_thread1"];
    //设置thread的优先级
    [thread1 setThreadPriority:0.2];
    [thread1 start];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(runThread1) object:nil];
    [thread2 setName:@"name_thread2"];
    [thread2 setThreadPriority:0.5];
    [thread2 start];

控制台打印结果:

2017-02-23 16:05:12.834 pThreadTest[2722:210010] 我在<NSThread: 0x60000007ee80>{number = 4, name = name_thread2}线程中执行
2017-02-23 16:05:12.834 pThreadTest[2722:210010] 0
2017-02-23 16:05:12.834 pThreadTest[2722:210009] 我在<NSThread: 0x60000007f0c0>{number = 3, name = name_thread1}线程中执行
2017-02-23 16:05:12.835 pThreadTest[2722:210009] 0
2017-02-23 16:05:13.835 pThreadTest[2722:210010] 1
2017-02-23 16:05:13.835 pThreadTest[2722:210009] 1
2017-02-23 16:05:14.837 pThreadTest[2722:210010] 2
2017-02-23 16:05:14.837 pThreadTest[2722:210009] 2
2017-02-23 16:05:15.837 pThreadTest[2722:210010] 3
2017-02-23 16:05:15.838 pThreadTest[2722:210009] 3

从控制台的打印结果可以看到thread1的name和thread2的name;同时也能发现thread2执行的每一步要优先thread1执行。但是这个优先执行跟我们所说的串行不同,不是说thread2完全执行完毕再执行thread1,而是cpu在分配资源的时候优先分配给thread2,使其优先执行。

NSThread锁的使用:
直接上代码:

//
//  TicketManager.h
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface TicketManager : NSObject

- (void)startToSale;
@end

//
//  TicketManager.m
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import "TicketManager.h"
#define Total 50

@interface TicketManager()

@property (nonatomic,assign) int tickets;//剩余票数
@property (nonatomic,assign) int saleCount;//卖出票数

@property (nonatomic,strong) NSThread *threadBJ;
@property (nonatomic,strong) NSThread *threadSH;

@property (nonatomic,strong) NSCondition *ticketCondition;

@end

@implementation TicketManager

- (instancetype)init{
    if (self = [super init]) {
        
        self.ticketCondition = [[NSCondition alloc] init];
        self.tickets = Total;
        
        self.threadBJ = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
        [self.threadBJ setName:@"BJ_thread"];
        self.threadSH = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
        [self.threadSH setName:@"SH_thread"];
    }
    return self;
}

- (void)sale{
    while (true) {
        //需要加锁,不然会发生资源抢占
        [self.ticketCondition lock];
//下面的加锁方式也可以
//        @synchronized (self) {
            //tickets>0 说明还有票可以卖
            if (self.tickets > 0) {
                [NSThread sleepForTimeInterval:0.5];
                self.tickets --;
                self.saleCount = Total - self.tickets;
                NSLog(@"%@: 当前余票: %d,售出: %d",[NSThread currentThread].name,self.tickets,self.saleCount);
            }
//        }
        [self.ticketCondition unlock];

    }
}

- (void)startToSale{
    [self.threadBJ start];
    [self.threadSH start];
}

@end

//控制器里面调用
//
//  ViewController.m
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import "ViewController.h"
#import <pthread.h>
#import "TicketManager.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    TicketManager *manager = [[TicketManager alloc] init];
    [manager startToSale];
}

三、多线程之GCD

GCD是苹果为了多核并行运算提出的一套解决方案。他可以合理的利用更多的CPU内核,并且能够自动管理线程的生命周期,比如创建线程,任务调度,销毁线程等都不需要我们手动管理,你只需要告诉GCD干什么就行了。
同步&异步:区别会不会阻塞当前线程,同步会阻塞当前线程,而异步则不会。

最基本用法代码:

- (void)viewDidLoad {
    UIButton *btn2 = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 50)];
    [btn2 setTitle:@"GCD" forState:UIControlStateNormal];
    [btn2 setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [btn2 addTarget:self action:@selector(clickGCD) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn2];
}
- (void)clickGCD{

    NSLog(@"执行GCD在线程%@",[NSThread currentThread]);
    /*
     dispatch_get_global_queue 全局并发队列
     参数1:设置优先级,优先级越高越优先执行
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
     
     */
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //执行耗时操作
        NSLog(@"start task1 在线程%@",[NSThread currentThread]);
        //dispatch_get_main_queue()主线程队列
        dispatch_async(dispatch_get_main_queue(), ^{
            //回到主线程刷新UI
            NSLog(@"刷新UI在线程%@",[NSThread currentThread]);
        });
    });
}

控制台打印结果:

2017-02-23 16:55:33.964 pThreadTest[2851:246460] 执行GCD在线程<NSThread: 0x6000000656c0>{number = 1, name = main}
2017-02-23 16:55:33.965 pThreadTest[2851:246949] start task1 在线程<NSThread: 0x60800006c9c0>{number = 3, name = (null)}
2017-02-23 16:55:33.965 pThreadTest[2851:246460] 刷新UI在线程<NSThread: 0x6000000656c0>{number = 1, name = main}

GCD的串行队列:
代码:

/*
     创建队列
     参数一:char类型的
     参数二:选择串行还是并行
     DISPATCH_QUEUE_SERIAL串行
     DISPATCH_QUEUE_CONCURRENT并行
     
     */
    dispatch_queue_t queue = dispatch_queue_create("com.GCD.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 1");
    });
    dispatch_async(queue, ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 2");
    });
    dispatch_async(queue, ^{
        NSLog(@"start task 3");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 3");
    });

控制台打印结果:

2017-02-23 17:17:19.370 pThreadTest[2910:261786] start task 1
2017-02-23 17:17:21.376 pThreadTest[2910:261786] end task 1
2017-02-23 17:17:21.376 pThreadTest[2910:261786] start task 2
2017-02-23 17:17:23.379 pThreadTest[2910:261786] end task 2
2017-02-23 17:17:23.379 pThreadTest[2910:261786] start task 3
2017-02-23 17:17:25.383 pThreadTest[2910:261786] end task 3
//由此看出三个任务是依次执行的,并且线程标识码261786是一致的,说明为了保证依次执行,串行队列是在同一个线程执行的任务。

GCD的并行队列:
代码:

/*
     创建队列
     参数一:char类型的
     参数二:选择串行还是并行
     DISPATCH_QUEUE_SERIAL串行
     DISPATCH_QUEUE_CONCURRENT并行
     
     */
    dispatch_queue_t queue = dispatch_queue_create("com.GCD.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 1");
    });
    dispatch_async(queue, ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 2");
    });
    dispatch_async(queue, ^{
        NSLog(@"start task 3");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 3");
    });

控制台打印结果:

2017-02-23 17:20:19.570 pThreadTest[2925:264132] start task 2
2017-02-23 17:20:19.570 pThreadTest[2925:264134] start task 1
2017-02-23 17:20:19.570 pThreadTest[2925:264131] start task 3
2017-02-23 17:20:21.573 pThreadTest[2925:264134] end task 1
2017-02-23 17:20:21.573 pThreadTest[2925:264132] end task 2
2017-02-23 17:20:21.573 pThreadTest[2925:264131] end task 3
//由此看出三个任务是并发执行的,并且线程有三个不同的标识码,说明为了保证并发执行,并行队列开启了三个不同的线程。

dispatch_group_t:在开发中我们经常有这样的需求,在多个任务异步处理之后,我们需要一个统一的回调通知,来告诉我们所有的任务都已结束,然后我们根据也无需求再去处理其他的业务。这个时候就要用到dispatch_group_t。

上代码:

    NSLog(@"我是主线程");
    dispatch_queue_t queue = dispatch_queue_create("com.GCD.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"start task 3");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 3");
    });
    //所有任务完成之后group的回调
    dispatch_group_notify(group, queue, ^{
        NSLog(@"All tasks over");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主线程刷新UI");
        });
    });

控制台打印结果:

2017-02-23 17:38:06.462 pThreadTest[2984:277621] 我是主线程
2017-02-23 17:38:06.462 pThreadTest[2984:278062] start task 1
2017-02-23 17:38:06.462 pThreadTest[2984:278066] start task 2
2017-02-23 17:38:06.462 pThreadTest[2984:278067] start task 3
2017-02-23 17:38:08.464 pThreadTest[2984:278062] end task 1
2017-02-23 17:38:08.464 pThreadTest[2984:278067] end task 3
2017-02-23 17:38:08.464 pThreadTest[2984:278066] end task 2
2017-02-23 17:38:08.465 pThreadTest[2984:278066] All tasks over
2017-02-23 17:38:08.465 pThreadTest[2984:277621] 回到主线程刷新UI
//由打印结果可以看出,dispatch_group_t在所有任务结束之后回调的通知的线程标识码(278066)和主线程的标识码(277621)不同,可以确定回调的通知是在子线程执行的,并且,系统并没有新开一条子线程,而是在原来执行任务的子线程选择了一条,因此,如果接收到回调通知后,需要刷新UI还需要回到主线程操作。

在实际的开发工程中我们可能有这样的需求,有几个异步的请求需要执行,执行完了统一告诉我们然后执行UI操作,于是写如下的代码:

- (void)clickGCD{
    NSLog(@"我是主线程");
    dispatch_queue_t queue = dispatch_queue_create("com.GCD.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        [self sendRequest1:^{
            NSLog(@"request1 done");
        }];
    });
    dispatch_group_async(group, queue, ^{
        [self sendRequest2:^{
            NSLog(@"request2 done");
        }];
    });

    //所有任务完成之后group的回调
    dispatch_group_notify(group, queue, ^{
        NSLog(@"All tasks over");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主线程刷新UI");
        });
    });
}
//网络请求1
- (void)sendRequest1:(void(^)())block{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 1");
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block();
            }
        });

    });
}
//网络请求2
- (void)sendRequest2:(void(^)())block{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 2");
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block();
            }
        });
    });
}

控制台打印结果:

2017-02-23 18:04:07.967 pThreadTest[3082:293445] 我是主线程
2017-02-23 18:04:07.967 pThreadTest[3082:293706] start task 1
2017-02-23 18:04:07.967 pThreadTest[3082:293704] start task 2
2017-02-23 18:04:07.967 pThreadTest[3082:293707] All tasks over
2017-02-23 18:04:07.968 pThreadTest[3082:293445] 回到主线程刷新UI
2017-02-23 18:04:09.972 pThreadTest[3082:293706] end task 1
2017-02-23 18:04:09.972 pThreadTest[3082:293704] end task 2
2017-02-23 18:04:09.972 pThreadTest[3082:293445] request1 done
2017-02-23 18:04:09.972 pThreadTest[3082:293445] request2 done
//很奇怪,网络请求还没有执行完,group完成的回调通知已经结束了,这是因为加入group的操作是异步请求,两个任务瞬间走过了,而异步的请求还在执行。

为了解决上面的问题dispatch_group_t为我们提供了下面enter和leave的方法:
代码:

- (void)clickGCD{

    NSLog(@"我是主线程");
    dispatch_queue_t queue = dispatch_queue_create("com.GCD.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    [self sendRequest1:^{
        NSLog(@"request1 done");
        dispatch_group_leave(group);
    }];

    dispatch_group_enter(group);
    [self sendRequest2:^{
        NSLog(@"request2 done");
        dispatch_group_leave(group);
    }];

    //所有任务完成之后group的回调
    dispatch_group_notify(group, queue, ^{
        NSLog(@"All tasks over");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主线程刷新UI");
        });
    });
}
//网络请求1
- (void)sendRequest1:(void(^)())block{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 1");
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block();
            }
        });

    });
}
//网络请求2
- (void)sendRequest2:(void(^)())block{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 2");
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block();
            }
        });
    });
}

控制台打印的结果:

2017-02-23 18:09:43.277 pThreadTest[3115:297830] 我是主线程
2017-02-23 18:09:43.277 pThreadTest[3115:298153] start task 1
2017-02-23 18:09:43.277 pThreadTest[3115:298155] start task 2
2017-02-23 18:09:45.283 pThreadTest[3115:298155] end task 2
2017-02-23 18:09:45.283 pThreadTest[3115:298153] end task 1
2017-02-23 18:09:45.283 pThreadTest[3115:297830] request2 done
2017-02-23 18:09:45.283 pThreadTest[3115:297830] request1 done
2017-02-23 18:09:45.284 pThreadTest[3115:298153] All tasks over
2017-02-23 18:09:45.284 pThreadTest[3115:297830] 回到主线程刷新UI

GCD模式单例:
上代码:

//
//  testSingle.h
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface testSingle : NSObject

+ (instancetype)shareInstance;

@end
//
//  testSingle.m
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import "testSingle.h"

static id _instance=nil;

@implementation testSingle

+ (instancetype)shareInstance{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[testSingle alloc] init];
        NSLog(@"init the testSingle");
    });
    return _instance;
}

@end
//控制器中的调用
- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 250, 100, 50)];
    [btn setTitle:@"single" forState:UIControlStateNormal];
    [btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(clickSingle) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
}
//点击按钮不停的调用
- (void)clickSingle{
    testSingle *single = [testSingle shareInstance];
    NSLog(@"%@",single);

}

控制台打印结果:

2017-02-23 18:38:05.637 pThreadTest[3368:314898] init the testSingle
2017-02-23 18:38:05.637 pThreadTest[3368:314898] <testSingle: 0x600000000d60>
2017-02-23 18:38:06.437 pThreadTest[3368:314898] <testSingle: 0x600000000d60>
2017-02-23 18:38:07.013 pThreadTest[3368:314898] <testSingle: 0x600000000d60>
2017-02-23 18:38:07.317 pThreadTest[3368:314898] <testSingle: 0x600000000d60>
2017-02-23 18:38:07.509 pThreadTest[3368:314898] <testSingle: 0x600000000d60>
//由打印结果看出testSingle只被初始化了一次,并且只存在一个实例。

如果我们想使某一段代码在应用程序的整个生命周期只执行一次的话,我们也可以用GCD来限制:
代码:

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"excute only once");
    });

GCD之延迟执行:
缺点:无法取消。

- (void)clickDelayExecute{

    NSLog(@"--begin--");

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"delay execute");
    });
}

控制台打印结果:

2017-02-23 18:55:03.120 pThreadTest[3436:326559] --begin--
2017-02-23 18:55:05.299 pThreadTest[3436:326559] delay execute
//开始时间:18:55:03.120   执行时间:18:55:05.299

四、多线程之NSOperation

NSOperation其实是对GCD的封装,它的实例封装了需要执行的操作,以及执行操作所需要的数据,并且可以以并发或者非并发的形式执行这个操作。
两种使用方式:
1、NSInvovationOperation&NSBlockOperation
2、自定义类继承NSOperation

相关概念:
1、NSOperationQueue 队列,线程池
a、addOperation 将线程加入线程池
b、setMaxConcurrentOperationCount 设置线程池同一时间最大的线程数
2、状态
ready、cancelled、executing、finished、asynchronous(并发还是非并发)
3、依赖-addDependency

废话少说,上代码:

//NSInvocationOperation
- (void)NSOperationTest{
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction) object:nil];
    [operation start];
}

- (void)invocationAction{
    NSLog(@"main thread%@",[NSThread currentThread]);
    for (int i = 0; i <=3; i ++) {
        [NSThread sleepForTimeInterval:1];
        NSLog(@"%d 在线程%@",i,[NSThread currentThread]);
    }
}

控制台打印:

2017-02-23 19:17:00.487 pThreadTest[3498:342096] main thread<NSThread: 0x60000007ee80>{number = 1, name = main}
2017-02-23 19:17:01.489 pThreadTest[3498:342096] 0 在线程<NSThread: 0x60000007ee80>{number = 1, name = main}
2017-02-23 19:17:02.490 pThreadTest[3498:342096] 1 在线程<NSThread: 0x60000007ee80>{number = 1, name = main}
2017-02-23 19:17:03.491 pThreadTest[3498:342096] 2 在线程<NSThread: 0x60000007ee80>{number = 1, name = main}
2017-02-23 19:17:04.493 pThreadTest[3498:342096] 3 在线程<NSThread: 0x60000007ee80>{number = 1, name = main}
//说明NSInvocationOperation操作是在主线程执行
- (void)NSOperationTest{
    // NSBlockOperation
    NSLog(@"main thread%@",[NSThread currentThread]);
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i <=3; i ++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"%d 在线程%@",i,[NSThread currentThread]);
        }
    }];
    [blockOperation start];
}

控制台打印:

2017-02-23 19:25:09.868 pThreadTest[3524:346135] main thread<NSThread: 0x608000071800>{number = 1, name = main}
2017-02-23 19:25:10.870 pThreadTest[3524:346135] 0 在线程<NSThread: 0x608000071800>{number = 1, name = main}
2017-02-23 19:25:11.871 pThreadTest[3524:346135] 1 在线程<NSThread: 0x608000071800>{number = 1, name = main}
2017-02-23 19:25:12.872 pThreadTest[3524:346135] 2 在线程<NSThread: 0x608000071800>{number = 1, name = main}
2017-02-23 19:25:13.874 pThreadTest[3524:346135] 3 在线程<NSThread: 0x608000071800>{number = 1, name = main}
//说明NSBlockOperation操作是在主线程执行

上面两种方式都是在主线程创建操作,那么NSOperation怎么用异步线程呢?这里就用到了NSOperationQueue:
上代码:

@property (nonatomic,strong) NSOperationQueue *queue;


- (void)NSOperationTest{

    NSLog(@"main thread%@",[NSThread currentThread]);
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i <=3; i ++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"%d 在线程%@",i,[NSThread currentThread]);
        }
    }];
    
    if (!self.queue) {
        self.queue = [[NSOperationQueue alloc] init];
    }
    
    [self.queue addOperation:blockOperation];
    NSLog(@"end");
}

控制台打印:

2017-02-23 19:30:25.969 pThreadTest[3543:350315] main thread<NSThread: 0x608000072600>{number = 1, name = main}
2017-02-23 19:30:25.970 pThreadTest[3543:350315] end
2017-02-23 19:30:27.042 pThreadTest[3543:350428] 0 在线程<NSThread: 0x60800007b140>{number = 3, name = (null)}
2017-02-23 19:30:28.114 pThreadTest[3543:350428] 1 在线程<NSThread: 0x60800007b140>{number = 3, name = (null)}
2017-02-23 19:30:29.183 pThreadTest[3543:350428] 2 在线程<NSThread: 0x60800007b140>{number = 3, name = (null)}
2017-02-23 19:30:30.228 pThreadTest[3543:350428] 3 在线程<NSThread: 0x60800007b140>{number = 3, name = (null)}
//end先执行,说明是异步执行,线程的编码不同,说明是在自线程中执行。

NSOperation自定义:
上代码:

//
//  customOperation.h
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface customOperation : NSOperation
- (instancetype)initWithName:(NSString *)name;
@end
//
//  customOperation.m
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import "customOperation.h"

@interface customOperation()
@property (nonatomic,copy)NSString *operName;

@end

@implementation customOperation

- (instancetype)initWithName:(NSString *)name{
    if (self = [super init]) {
        self.operName = name;
    }
    return self;
}

//重写main方法
- (void)main{
    for (int i = 0; i < 3; i ++) {
        NSLog(@"%@ %d",self.operName,i);
        [NSThread sleepForTimeInterval:1];
    }
}

@end

//
//  ViewController.m
- (void)NSOperationTest{

    NSLog(@"main thread%@",[NSThread currentThread]);
    if (!self.queue) {
        self.queue = [[NSOperationQueue alloc] init];
    }
    customOperation *customA = [[customOperation alloc] initWithName:@"customA"];
    [self.queue addOperation:customA];
    NSLog(@"end");
}

控制台打印结果:

2017-02-23 19:51:03.284 pThreadTest[3621:365709] main thread<NSThread: 0x600000263a80>{number = 1, name = main}
2017-02-23 19:51:03.285 pThreadTest[3621:365709] end
2017-02-23 19:51:03.285 pThreadTest[3621:365846] customA 0
2017-02-23 19:51:04.359 pThreadTest[3621:365846] customA 1
2017-02-23 19:51:05.433 pThreadTest[3621:365846] customA 2
//由打印结果可以看出操作,是异步子线程中执行

设置线程池同时最大线程数:
上代码:

//
//  customOperation.h
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface customOperation : NSOperation
- (instancetype)initWithName:(NSString *)name;
@end
//
//  customOperation.m
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import "customOperation.h"

@interface customOperation()
@property (nonatomic,copy)NSString *operName;

@end

@implementation customOperation

- (instancetype)initWithName:(NSString *)name{
    if (self = [super init]) {
        self.operName = name;
    }
    return self;
}

//重写main方法
- (void)main{
    for (int i = 0; i < 3; i ++) {
        NSLog(@"%@ %d",self.operName,i);
        [NSThread sleepForTimeInterval:1];
    }
}

@end

//
//  ViewController.m
- (void)NSOperationTest{

    NSLog(@"main thread%@",[NSThread currentThread]);

    
    if (!self.queue) {
        self.queue = [[NSOperationQueue alloc] init];
    }
    //设置线程池同时最大线程数
    [self.queue setMaxConcurrentOperationCount:1];
    
    customOperation *customA = [[customOperation alloc] initWithName:@"customA"];
    customOperation *customB = [[customOperation alloc] initWithName:@"customB"];
    customOperation *customC = [[customOperation alloc] initWithName:@"customC"];
    customOperation *customD = [[customOperation alloc] initWithName:@"customD"];
    [self.queue addOperation:customA];
    [self.queue addOperation:customB];
    [self.queue addOperation:customC];
    [self.queue addOperation:customD];
    NSLog(@"end");
}

控制台打印结果:
当[self.queue setMaxConcurrentOperationCount:1];时

2017-02-23 19:54:44.725 pThreadTest[3647:367833] main thread<NSThread: 0x600000076e80>{number = 1, name = main}
2017-02-23 19:54:44.726 pThreadTest[3647:367833] end
2017-02-23 19:54:44.726 pThreadTest[3647:368602] customA 0
2017-02-23 19:54:45.800 pThreadTest[3647:368602] customA 1
2017-02-23 19:54:46.809 pThreadTest[3647:368602] customA 2
2017-02-23 19:54:47.882 pThreadTest[3647:368610] customB 0
2017-02-23 19:54:48.957 pThreadTest[3647:368610] customB 1
2017-02-23 19:54:50.028 pThreadTest[3647:368610] customB 2
2017-02-23 19:54:51.098 pThreadTest[3647:368602] customC 0
2017-02-23 19:54:52.172 pThreadTest[3647:368602] customC 1
2017-02-23 19:54:53.241 pThreadTest[3647:368602] customC 2
2017-02-23 19:54:54.308 pThreadTest[3647:368610] customD 0
2017-02-23 19:54:55.379 pThreadTest[3647:368610] customD 1
2017-02-23 19:54:56.451 pThreadTest[3647:368610] customD 2
//当最大线程数1时依次执行

当[self.queue setMaxConcurrentOperationCount:3];时

2017-02-23 19:56:05.337 pThreadTest[3662:369743] main thread<NSThread: 0x600000070740>{number = 1, name = main}
2017-02-23 19:56:05.337 pThreadTest[3662:369743] end
2017-02-23 19:56:05.338 pThreadTest[3662:369819] customB 0
2017-02-23 19:56:05.338 pThreadTest[3662:369789] customA 0
2017-02-23 19:56:05.338 pThreadTest[3662:369792] customC 0
2017-02-23 19:56:06.413 pThreadTest[3662:369792] customC 1
2017-02-23 19:56:06.413 pThreadTest[3662:369819] customB 1
2017-02-23 19:56:06.413 pThreadTest[3662:369789] customA 1
2017-02-23 19:56:07.485 pThreadTest[3662:369792] customC 2
2017-02-23 19:56:07.485 pThreadTest[3662:369819] customB 2
2017-02-23 19:56:07.485 pThreadTest[3662:369789] customA 2
2017-02-23 19:56:08.561 pThreadTest[3662:369790] customD 0
2017-02-23 19:56:09.635 pThreadTest[3662:369790] customD 1
2017-02-23 19:56:10.709 pThreadTest[3662:369790] customD 2
//当最大线程数3的时候就并行执行

设置NSOperation之间的互相依赖
上代码:

//
//  ViewController.m
- (void)NSOperationTest{

    NSLog(@"main thread%@",[NSThread currentThread]);

    
    if (!self.queue) {
        self.queue = [[NSOperationQueue alloc] init];
    }
    //设置线程池同时最大线程数
    [self.queue setMaxConcurrentOperationCount:3];
    
    customOperation *customA = [[customOperation alloc] initWithName:@"customA"];
    customOperation *customB = [[customOperation alloc] initWithName:@"customB"];
    customOperation *customC = [[customOperation alloc] initWithName:@"customC"];
    customOperation *customD = [[customOperation alloc] initWithName:@"customD"];
    //给操作之间添加依赖
    [customD addDependency:customA];
    [customA addDependency:customC];
    [customC addDependency:customB];
    //下面这句不能加不然会死锁
//    [customB addDependency:customD];
    
    [self.queue addOperation:customA];
    [self.queue addOperation:customB];
    [self.queue addOperation:customC];
    [self.queue addOperation:customD];
    NSLog(@"end");
}

控制台打印结果:

2017-02-23 20:01:51.687 pThreadTest[3701:374279] main thread<NSThread: 0x600000066180>{number = 1, name = main}
2017-02-23 20:01:51.688 pThreadTest[3701:374279] end
2017-02-23 20:01:51.688 pThreadTest[3701:374318] customB 0
2017-02-23 20:01:52.740 pThreadTest[3701:374318] customB 1
2017-02-23 20:01:53.808 pThreadTest[3701:374318] customB 2
2017-02-23 20:01:54.884 pThreadTest[3701:374321] customC 0
2017-02-23 20:01:55.951 pThreadTest[3701:374321] customC 1
2017-02-23 20:01:57.025 pThreadTest[3701:374321] customC 2
2017-02-23 20:01:58.026 pThreadTest[3701:374321] customA 0
2017-02-23 20:01:59.100 pThreadTest[3701:374321] customA 1
2017-02-23 20:02:00.101 pThreadTest[3701:374321] customA 2
2017-02-23 20:02:01.172 pThreadTest[3701:374318] customD 0
2017-02-23 20:02:02.230 pThreadTest[3701:374318] customD 1
2017-02-23 20:02:03.231 pThreadTest[3701:374318] customD 2

当自定义的NSOperation添加依赖,并且operation中添加异步操作时:
上代码:

//
//  customOperation.h
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface customOperation : NSOperation
- (instancetype)initWithName:(NSString *)name;
@end
//
//  customOperation.m
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import "customOperation.h"

@interface customOperation()
@property (nonatomic,copy)NSString *operName;

@end

@implementation customOperation

- (instancetype)initWithName:(NSString *)name{
    if (self = [super init]) {
        self.operName = name;
    }
    return self;
}

//重写main方法
- (void)main{
//    for (int i = 0; i < 3; i ++) {
//        NSLog(@"%@ %d",self.operName,i);
//        [NSThread sleepForTimeInterval:1];
//    }
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:1];
        if (self.cancelled) {
            return ;
        }
        
        NSLog(@"%@",self.operName);
    });
}

//
//  ViewController.m
- (void)NSOperationTest{

    NSLog(@"main thread%@",[NSThread currentThread]);

    
    if (!self.queue) {
        self.queue = [[NSOperationQueue alloc] init];
    }
    //设置线程池同时最大线程数
    [self.queue setMaxConcurrentOperationCount:3];
    
    customOperation *customA = [[customOperation alloc] initWithName:@"customA"];
    customOperation *customB = [[customOperation alloc] initWithName:@"customB"];
    customOperation *customC = [[customOperation alloc] initWithName:@"customC"];
    customOperation *customD = [[customOperation alloc] initWithName:@"customD"];
    
    [customD addDependency:customA];
    [customA addDependency:customC];
    [customC addDependency:customB];
    //下面这句不能加不然会死锁
//    [customB addDependency:customD];
    
    [self.queue addOperation:customA];
    [self.queue addOperation:customB];
    [self.queue addOperation:customC];
    [self.queue addOperation:customD];
    NSLog(@"end");
}

控制台打印结果:

2017-02-23 20:08:05.766 pThreadTest[3722:378340] main thread<NSThread: 0x600000079180>{number = 1, name = main}
2017-02-23 20:08:05.767 pThreadTest[3722:378340] end
2017-02-23 20:08:06.841 pThreadTest[3722:378589] customB
2017-02-23 20:08:06.841 pThreadTest[3722:378624] customD
2017-02-23 20:08:06.841 pThreadTest[3722:378591] customA
2017-02-23 20:08:06.841 pThreadTest[3722:378588] customC
//执行操作并没有按照 B C A D的顺序执行,这是因为main函数中打印的操作是放在异步线程中执行的。

解决上面的问题:

//
//  customOperation.m
//  pThreadTest
//
//  Created by Dustin on 17/2/23.
//  Copyright © 2017年 Dustin. All rights reserved.
//

#import "customOperation.h"

@interface customOperation()
@property (nonatomic,copy)NSString *operName;
@property (nonatomic,assign)BOOL over;
@end

@implementation customOperation

- (instancetype)initWithName:(NSString *)name{
    if (self = [super init]) {
        self.operName = name;
    }
    return self;
}

//重写main方法
- (void)main{
//    for (int i = 0; i < 3; i ++) {
//        NSLog(@"%@ %d",self.operName,i);
//        [NSThread sleepForTimeInterval:1];
//    }
//异步进行耗时操作
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:1];
        if (self.cancelled) {
            return ;
        }
        
        NSLog(@"%@",self.operName);
        self.over = YES;
    });
    //利用runloop保持线程在没执行完时不会结束
    while (!self.over && !self.cancelled) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
}
@end

控制台打印结果:

2017-02-23 20:16:01.330 pThreadTest[3744:384234] main thread<NSThread: 0x60800007c140>{number = 1, name = main}
2017-02-23 20:16:01.330 pThreadTest[3744:384234] end
2017-02-23 20:16:02.394 pThreadTest[3744:384384] customB
2017-02-23 20:16:03.459 pThreadTest[3744:384383] customC
2017-02-23 20:16:04.535 pThreadTest[3744:384384] customA
2017-02-23 20:16:05.574 pThreadTest[3744:384386] customD
//这样依赖的顺序就正确了

关于Runloop这里就不做过多介绍,可以从网上看一些资料。

总结:关于多线程暂时就总结到这里,如果哪里我写的不够准确或者有遗漏,还请看过的朋友多多交流。

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

推荐阅读更多精彩内容

  • 前言 前言:了解多线程之前首先了解一些计算机的基本概念计算机操作系统的基本概念进程: 一个具有一定独立功能的程序关...
    东了个尼阅读 159评论 2 3
  • 首先明确线程和进程的关系和区别: 一个程序至少有一个进程,一个进程至少有一个线程. 线程的划分尺度小于进程,使得多...
    kkj1996阅读 199评论 0 0
  • iOS开发中常用的几种多线程方案,简单做个小结,方便日后查阅。 Pthreads NSThead GCD NSOp...
    acqiang阅读 402评论 0 4
  • 身为开发者的我们,总是会面临着各种各样的需求.必不可少的一个硬性规则:从用户体验的角度来开发项目!那么这就要求我...
    Small_Potato阅读 443评论 2 8
  • 1.标记清除算法 分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象...
    雨笋情缘阅读 280评论 0 0