iOS 多线程 GCD NSThread NSOperation

本文是根据Sky109大神的导图的博客进行个人总结学习

总图

导图

1.什么是多线程?

多线程是在进程中为提高CPU、内存使用率最大化的一种方式,这种优化是通过在进程中消耗多余CPU和内存空间开辟多个线程,同时执行多个任务来实现(其实是CPU在多个线程中来回切换)。因为开辟线程是需要消耗CPU和内存的,所以线程数不宜太多,适当应用才能提高程序效率。

2.iOS中多线程有那几种

1.pthread(底层语言C,跨平台,使用不便,iOS很少使用这种)
2.NSThead (轻量级,使用简单,适用应用场景较少)
3.GCD (重量级,经常使用,适用大多应用场景,充分利用多核)
4.NSOperation (重量级,基于GCD,面向对象,使用简单方面)

3.多线程的使用

1.pthread

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //创建线程
    pthread_t thread;
    /*
     第一个参数pthread_t *restrict:线程对象
     第二个参数const pthread_attr_t *restrict:线程属性
     第三个参数void *(*)(void *) :指向函数的指针
     第四个参数void *restrict:函数的参数
     */
    pthread_create(&thread, NULL,run ,NULL);
}
//void *(*)(void *)
void *run(void *param)
{
    for (NSInteger i =0 ; i<10000; i++) {
        NSLog(@"%zd--%@-",i,[NSThread currentThread]);
    }
    return NULL;
}

2.NSThread
常用的方法

// 方法一:创建线程,需要自己开启线程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
// 开启线程
[thread start];

// 方法二:创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

// 方法三:隐式创建并启动线程
[self performSelectorInBackground:@selector(run) withObject:nil];

属性

// 获取当前线程
 + (NSThread *)currentThread;
 // 创建启动线程
 + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
 // 判断是否是多线程
 + (BOOL)isMultiThreaded;
 // 线程休眠 NSDate 休眠到什么时候
 + (void)sleepUntilDate:(NSDate *)date;
 // 线程休眠时间
 + (void)sleepForTimeInterval:(NSTimeInterval)ti;
 // 结束/退出当前线程
 + (void)exit;
 // 获取当前线程优先级
 + (double)threadPriority;
 // 设置线程优先级 默认为0.5 取值范围为0.0 - 1.0 
 // 1.0优先级最高
 // 设置优先级
 + (BOOL)setThreadPriority:(double)p;
 // 获取指定线程的优先级
 - (double)threadPriority NS_AVAILABLE(10_6, 4_0);
 - (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);
 // 设置线程的名字
 - (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0);
 - (NSString *)name NS_AVAILABLE(10_5, 2_0);
 // 判断指定的线程是否是 主线程
 - (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);
 - (id)init NS_AVAILABLE(10_5, 2_0);    // designated initializer
 // 创建线程
 - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0);
 // 指定线程是否在执行
 - (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);
 // 线程是否完成
 - (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);
 // 线程是否被取消 (是否给当前线程发过取消信号)
 - (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0);
 // 发送线程取消信号的 最终线程是否结束 由 线程本身决定
 - (void)cancel NS_AVAILABLE(10_5, 2_0);
 // 启动线程
 - (void)start NS_AVAILABLE(10_5, 2_0);
 // 线程主函数  在线程中执行的函数 都要在-main函数中调用,自定义线程中重写-main方法
 - (void)main NS_AVAILABLE(10_5, 2_0);    // thread body metho

NSThread线程图


线程图

NSThread线程状态

启动线程
- (void)start; 
// 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 进入阻塞状态
强制停止线程
+ (void)exit;
// 进入死亡状态

线程安全
多线程安全隐患原因:一块资源被多个线程访问,容易引起数据错乱,数据安全
大多是异步线程执行完成后,因为顺序没有控制好,造成访问先后时数据没有及时更新
解决办法:互斥锁(当多条线程抢夺同一块资源,一个地方只能存在一个互斥锁,会消耗大量CPU资源)
使用场景:售票系统

#import "ViewController.h"

@interface ViewController ()

@property(nonatomic,strong)NSThread *thread01;
@property(nonatomic,strong)NSThread *thread02;
@property(nonatomic,strong)NSThread *thread03;
@property(nonatomic,assign)NSInteger numTicket;

//@property(nonatomic,strong)NSObject *obj;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 总票数为30
    self.numTicket = 30;
    self.thread01 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread01.name = @"售票员01";
    self.thread02 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread02.name = @"售票员02";
    self.thread03 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread03.name = @"售票员03";
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.thread01 start];
    [self.thread02 start];
    [self.thread03 start];
}
// 售票
-(void)saleTicket
{
    while (1) {
        // 创建对象
        // self.obj = [[NSObject alloc]init];
        // 锁对象,本身就是一个对象,所以self就可以了
        // 锁定的时候,其他线程没有办法访问这段代码
        @synchronized (self) {
            // 模拟售票时间,我们让线程休息0.05s 
            [NSThread sleepForTimeInterval:0.05];
            if (self.numTicket > 0) {
                self.numTicket -= 1;
                NSLog(@"%@卖出了一张票,还剩下%zd张票",[NSThread currentThread].name,self.numTicket);
            }else{
                NSLog(@"票已经卖完了");
                break;
            }
        }
    }
}

3.GCD 的使用
核心概念:任务和队列
任务:执行操作,有同步和异步
队列:存放任务,有串行和并行

使用步骤:创建任务然后添加到队列中,GCD会根据队列中取出任务:FIFO。先进先出,后进后出
队列的创建+

// 第一个参数const char *label : C语言字符串,用来标识
// 第二个参数dispatch_queue_attr_t attr : 队列的类型
// 并发队列:DISPATCH_QUEUE_CONCURRENT
// 串行队列:DISPATCH_QUEUE_SERIAL 或者 NULL
dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

并发队列
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_CONCURRENT);

串行队列
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);

系统默认并发队列
 /** 
     第一个参数:优先级 也可直接填后面的数字
     #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 // 后台
     第二个参数: 预留参数  0
     */
    dispatch_queue_t quque1 =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

获得主队列
dispatch_queue_t  queue = dispatch_get_main_queue();

任务的执行:队列在queue中,任务在block中

开启同步函数 同步函数:要求立刻马上开始执行
 /*
     第一个参数:队列
     第二个参数:block,在里面封装任务
     */
    dispatch_sync(queue, ^{
        
    });

开启异步函数 异步函数 :等主线程执行完毕之后,回过头开线程执行任务
    dispatch_async(queue, ^{
        
    });

任务:同步函数 异步函数
队列:串行 并行
异步函数+并发队列:会开启新的线程,并发执行
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 dispatch_async(queue, ^{
       NSLog(@"---download1---%@",[NSThread currentThread]);
 });

异步函数+串行队列:会开启一条线程,任务串行执行
dispatch_queue_t queue =  dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
   dispatch_async(queue, ^{
       NSLog(@"---download1---%@",[NSThread currentThread]);
 });

同步函数+并发队列:不会开线程,任务串行执行
 dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
   dispatch_sync(queue, ^{
       NSLog(@"---download1---%@",[NSThread currentThread]);
   });

同步函数+串行队列:不会开线程,任务串行执行
 dispatch_queue_t queue =  dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);   
   dispatch_sync(queue, ^{
       NSLog(@"---download1---%@",[NSThread currentThread]);
});

异步函数+主队列:不会开线程,任务串行执行
使用主队列(跟主线程相关联的队列)
##主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
//1.获得主队列
dispatch_queue_t queue =  dispatch_get_main_queue();
//2.异步函数
dispatch_async(queue, ^{
        NSLog(@"---download1---%@",[NSThread currentThread]);
    });

有意思的函数

1.栅栏函数可以控制任务执行的顺序,栅栏函数之前的执行完毕之后,执行栅栏函数,然后在执行栅栏函数之后的
 dispatch_barrier_async(queue, ^{
        NSLog(@"--dispatch_barrier_async-");
    });

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
     [self barrier];
}
-(void)barrier
{
    //1.创建队列(并发队列)
    dispatch_queue_t queue = dispatch_queue_create("com.xxccqueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<3; i++) {
            NSLog(@"%zd-download1--%@",i,[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<3; i++) {
            NSLog(@"%zd-download2--%@",i,[NSThread currentThread]);
        }
    });
    //栅栏函数
    dispatch_barrier_async(queue, ^{
        NSLog(@"我是一个栅栏函数");
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<3; i++) {
            NSLog(@"%zd-download3--%@",i,[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<3; i++) {
            NSLog(@"%zd-download4--%@",i,[NSThread currentThread]);
        }
    });
}

延迟执行(延迟·控制在哪个线程执行)
/*
     第一个参数:延迟时间
     第二个参数:要执行的代码
     如果想让延迟的代码在子线程中执行,也可以更改在哪个队列中执行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0)
     */
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"---%@",[NSThread currentThread]);
    });

// 2s中之后调用run方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// repeats:YES 是否重复
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];


一次性代码 :单例常用
-(void)once
    {
        //整个程序运行过程中只会执行一次
        //onceToken用来记录该部分的代码是否被执行过
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"-----");
        });
    }

快速迭代(开多个线程并发完成迭代操作)
快速迭代:开启多条线程,并发执行,相比于for循环在耗时操作中极大的提高效率和速度
   /*
     第一个参数:迭代的次数
     第二个参数:在哪个队列中执行
     第三个参数:block要执行的任务
     */
dispatch_apply(10, queue, ^(size_t index) {
});


队列组(同栅栏函数)
// 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并行队列 
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 执行队列组任务
    dispatch_group_async(group, queue, ^{   
    });
    //队列组中的任务执行完毕之后,执行该函数
    dispatch_group_notify(group, queue, ^{
    });

场景应用:不确定下载图片数量,下载完成后刷新UI

- (void)downImage
{
      self.imageUrl = @[@"http://img1.3lian.com/img2011/w12/1202/19/d/88.jpg",@"http://img1.3lian.com/img2011/w12/1202/19/d/8.jpg",@"http://www.itunes123.com/uploadfile/2016/0421/20160421014340186.jpg"];
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i=0;i

4.NSOperation
NSOperation 是苹果公司对 GCD 的封装,完全面向对象,并比GCD多了一些更简单实用的功能,所以使用起来更加方便易于理解。NSOperation 和NSOperationQueue 分别对应 GCD 的 任务 和 队列。
NSOperation和NSOperationQueue实现多线程的具体步骤
1.将需要执行的操作封装到一个NSOperation对象中
2.将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperationQueue中的NSOperation取出来,并将取出的NSOperation封装的操作放到一条新线程中执行

4.1 NSOperation
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
使用NSOperation子类的方式有3种
1.NSInvocationOperation

   /*
     第一个参数:目标对象
     第二个参数:选择器,要调用的方法
     第三个参数:方法要传递的参数
     */
NSInvocationOperation *op  = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil];
//启动操作
[op start];

2.NSBlockOperation(最常用)

//1.封装操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
       //要执行的操作,在主线程中执行
       NSLog(@"1------%@",[NSThread currentThread]); 
}];
//2.追加操作,追加的操作在子线程中执行,可以追加多条操作
[op addExecutionBlock:^{
        NSLog(@"---download2--%@",[NSThread currentThread]);
    }];
[op start];

3.自定义子类继承NSOperation,实现内部相应的方法

-(void)main
{
    // 要执行的操作
}
// 实例化一个自定义对象,并执行操作
CLOperation *op = [[CLOperation alloc]init];
[op start];

NSOperation中的两种队列

主队列:通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行
非主队列:直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行

NSOperationQueue的作用

NSOperation可以调用start方法来执行任务,但默认是同步执行的
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

注意:将操作添加到NSOperationQueue中,就会自动启动,不需要再自己启动了,addOperation 内部调用 start方法, start方法 内部调用 main方法
注:这里使用NSBlockOperation示例,其他两种方法一样
    // 1. 创建非主队列 同时具备并发和串行的功能,默认是并发队列
    NSOperationQueue *queue =[[NSOperationQueue alloc]init];
    //NSBlockOperation 不论封装操作还是追加操作都是异步并发执行
    // 2. 封装操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download1 -- %@",[NSThread currentThread]);
    }];
    // 3. 将封装操作加入主队列
    // 也可以不获取封装操作对象 直接添加操作到队列中
    //[queue addOperationWithBlock:^{
    // 操作
    //}];
    [queue addOperation:op1];

常用方法

NSOperation的依赖 - (void)addDependency:(NSOperation *)op;
// 操作op1依赖op5,即op1必须等op5执行完毕之后才会执行
// 添加操作依赖,注意不能循环依赖,如果循环依赖会造成两个任务都不会执行
// 也可以夸队列依赖,依赖别的队列的操作
    [op1 addDependency:op5];

NSOperation操作监听void (^completionBlock)(void);
// 监听操作的完成
// 当op1线程完成之后,立刻就会执行block块中的代码
// block中的代码与op1不一定在一个线程中执行,但是一定在子线程中执行
op1.completionBlock = ^{
        NSLog(@"op1已经完成了---%@",[NSThread currentThread]);
    };

maxConcurrentOperationCount
 //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    /*
     默认是并发队列,如果最大并发数>1,并发
     如果最大并发数==1,串行队列
     系统的默认是最大并发数-1 ,表示不限制
     设置成0则不会执行任何操作
     */
     queue.maxConcurrentOperationCount = 1;

suspended
//当值为YES的时候暂停,为NO的时候是恢复
queue.suspended = YES;

-(void)cancelAllOperations
//取消所有的任务,不再执行,不可逆
[queue cancelAllOperations];
###注意:暂停和取消只能暂停或取消处于等待状态的任务,
###不能暂停或取消正在执行中的任务,必须等正在执行的任务执行完毕之后才会暂停,
###如果想要暂停或者取消正在执行的任务,
###可以在每个任务之间即每当执行完一段耗时操作之后,判断是否任务是否被取消或者暂停。
####如果想要精确的控制,则需要将判断代码放在任务之中,但是不建议这么做,频繁的判断会消耗太多时间

// 开启线程
- (void)start;
- (void)main;
// 判断线程是否被取消
@property (readonly, getter=isCancelled) BOOL cancelled;
// 取消当前线程
- (void)cancel;
//NSOperation任务是否在运行
@property (readonly, getter=isExecuting) BOOL executing;
//NSOperation任务是否已结束
@property (readonly, getter=isFinished) BOOL finished;
// 添加依赖
- (void)addDependency:(NSOperation *)op;
// 移除依赖
- (void)removeDependency:(NSOperation *)op;
// 优先级
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
// 操作监听
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
// 阻塞当前线程,直到该NSOperation结束。可用于线程执行顺序的同步
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
// 获取线程的优先级
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
// 线程名称
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);

NSOperationQueue

@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
// 队列中的操作数
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
// 最大并发数,同一时间最多只能执行三个操作
@property NSInteger maxConcurrentOperationCount;
// 暂停 YES:暂停 NO:继续
@property (getter=isSuspended) BOOL suspended;
// 取消所有操作
- (void)cancelAllOperations;
// 阻塞当前线程直到此队列中的所有任务执行完毕
- (void)waitUntilAllOperationsAreFinished;

应用场景:不确定数据顺序下载完成后再刷新UI

self.imageUrl = @[@"http://img1.3lian.com/img2011/w12/1202/19/d/88.jpg",@"http://img1.3lian.com/img2011/w12/1202/19/d/8.jpg",@"http://www.itunes123.com/uploadfile/2016/0421/20160421014340186.jpg"];
    self.dataArray = [NSMutableArray array];
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"start3");
        // 开启图形上下文
        UIGraphicsBeginImageContext(CGSizeMake(375, 999));
        // 绘制图片1
        CGFloat y = 0.0;
        for (int i=0; i

你可能感兴趣的:(iOS 多线程 GCD NSThread NSOperation)