iOS 多线程

NSThread

1.NSThread共有3种创建方式

1.init
/// init 创建完成之后需要手动start
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(log) object:nil];
[thread start];
2.detachNewThreadSelector
/// 创建完成自动启动
[NSThread detachNewThreadSelector:@selector(log) toTarget:self withObject:nil];
3.performSelectorInBackground
/// 创建完成自动启动
[self performSelectorInBackground:@selector(log) withObject:nil];

2.NSThread类方法

    /// 获取当前线程 number = 1 为主线程 否则为子线程
    [NSThread currentThread];
    /// 线程休眠 单位秒
    [NSThread sleepForTimeInterval:2];
    /// 线程休眠到指定时间
    [NSThread sleepUntilDate: [NSDate date]];
    /// 获取主线程
    [NSThread mainThread];
    /// 退出线程
    [NSThread exit];
    /// 判断当前线程是否为主线程
    [NSThread isMainThread];
    /// 判断当前线程是否为多线程
    [NSThread isMultiThreaded];

3.NSThread的属性

/// 线程是否在执行
@property (readonly, getter=isExecuting) BOOL executing;
/// 线程是否被取消
@property (readonly, getter=isFinished) BOOL finished;
/// 线程是否完成
@property (readonly, getter=isCancelled) BOOL cancelled;
/// 是否是主线程
@property (readonly) BOOL isMainThread;

GCD

GCD会自动利用更多的CPU内核,自动管理线程的生命周期(创建、调度、销毁)

1.GCD的基本概念

  • 任务(block):任务就是需要执行的代码块。
  • 队列:装载线程任务的序列。
  • 并发队列:同一时间,队列中的线程可以一起执行,实质是CPU在多条线程之前快速的切换。
  • 串行队列:线程按照先进先出的顺序执行。
  • 同步:不开启新线程,一个接一个顺序执行。
  • 异步:开启多个线程,任务同一时间可以一起执行。

1.队列的创建

/// 传入两个参数,第一个参数为队列的标识符,可为空,第二个参数用来表示创建串行队列DISPATCH_QUEUE_SERIAL或者并发队列DISPATCH_QUEUE_CONCURRENT
/// 串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
/// 并发队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
 ///  主队列
dispatch_async(dispatch_get_global_queue(0, 0), ^{
   dispatch_async(dispatch_get_main_queue(), ^{
     });
});
/// 全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//全局并发队列的优先级
#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 // 后台优先级

2.同步异步的创建

同步:用dispatch_sync
异步:用dispatch_async

/// 同步执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
     /// 代码块
});
/// 异步执行
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
     /// 代码块
});

3.GCD的使用

由于CGD存在多种队列和不同的执行方式,所以它们的组合方式有许多种。
1.串行同步
2.串行异步
3.并发同步
4.并发异步
5.主队列同步
6.主队列异步

  • 串行同步
    /// 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    /// 创建同步线程
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"串行同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"串行同步2   %@",[NSThread currentThread]);
        }
    });

输出结果都在主线程,顺序执行

串行同步1   {number = 1, name = main}
串行同步1   {number = 1, name = main}
串行同步2   {number = 1, name = main}
串行同步2   {number = 1, name = main}

  • 串行异步
    /// 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    /// 创建异步线程
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"串行异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"串行异步2   %@",[NSThread currentThread]);
        }
    });

输出结果在子线程,顺序执行

串行异步1   {number = 5, name = (null)}
串行异步1   {number = 5, name = (null)}
串行异步2   {number = 5, name = (null)}
串行异步2   {number = 5, name = (null)}
  • 并发同步
    /// 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_CONCURRENT);
    /// 创建同步线程
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"并发同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"并发同步2   %@",[NSThread currentThread]);
        }
    });

输出结果都在主线程,顺序执行

并发同步1   {number = 1, name = main}
并发同步1   {number = 1, name = main}
并发同步2   {number = 1, name = main}
并发同步2   {number = 1, name = main}
  • 并发异步
    /// 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_CONCURRENT);
    /// 创建异步线程
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"并发异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"并发异步2   %@",[NSThread currentThread]);
        }
    });

输出结果在不同子线程,无序执行

并发异步1   {number = 5, name = (null)}
并发异步2   {number = 3, name = (null)}
并发异步2   {number = 3, name = (null)}
并发异步1   {number = 5, name = (null)}

  • 主队列同步
    /// 获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    /// 创建同步线程
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"主队列同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"主队列同步2   %@",[NSThread currentThread]);
        }
    });

程序崩溃,原因是把任务放到主线程队列中,它就会立即执行,而主线程正在处理syncMain方法,任务需要等待主队列执行完才能执行,syncMain执行到第一个任务时,又要等待第一个任务完成才能继续执行,这样就形成了死锁。

  • 主队列异步
    /// 获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    /// 创建异步线程
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"主队列异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"主队列异步2   %@",[NSThread currentThread]);
        }
    });

输出结果在主线程,顺序执行。

主队列异步1   {number = 1, name = main}
主队列异步1   {number = 1, name = main}
主队列异步2   {number = 1, name = main}
主队列异步2   {number = 1, name = main}
  • GCD线程之间通讯
    开发中会经常需要做耗时操作的任务,比如网络请求,图片下载等,当完成耗时操作后需要回到主线程更新UI,就需要用到线程之间的通讯。
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        // 模拟图片下载
        NSLog(@"开始下载图片");
        [NSThread sleepForTimeInterval:3];
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"更新UI");
        });
    });

会新开辟子线程下载,下载完成回到主线程刷新UI。

  • CGD栅栏
    当我们需要异步执行任务,而任务又分为两组执行,A执行完成之后才能执行B,这时候就可以用到GCD的栅栏dispatch_barrier_async
    /// 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"栅栏异步1-%d   %@",i,[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"栅栏异步2-%d   %@",i,[NSThread currentThread]);
        }
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"并发异步执行");
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"栅栏异步3-%d   %@",i,[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"栅栏异步4-%d   %@",i,[NSThread currentThread]);
        }
    });

运行结果:开启多条线程,所有任务并发执行,但3,4在1,2之后才会执行

栅栏异步1-0   {number = 7, name = (null)}
栅栏异步2-0   {number = 6, name = (null)}
栅栏异步1-1   {number = 7, name = (null)}
栅栏异步2-1   {number = 6, name = (null)}
并发异步执行
栅栏异步4-0   {number = 7, name = (null)}
栅栏异步3-0   {number = 6, name = (null)}
栅栏异步3-1   {number = 6, name = (null)}
栅栏异步4-1   {number = 7, name = (null)}
  • GCD延时执行
    当我们需要等待一段时间后才执行某个任务时就会用到延时执行dispatch_after
    NSLog(@"开始");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 4), dispatch_get_main_queue(), ^{
        NSLog(@"4秒后执行");
    });
  • GCD队列组
    当我们需要异步执行多个任务,且当所有任务完成后需要做某些操作,就需要用到队列组。
    /// 创建一个队列组
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"耗时操作1 %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"耗时操作2 %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"耗时操作3 %@",[NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"主线程刷新 %@",[NSThread currentThread]);
    });

运行结果为

耗时操作3 {number = 7, name = (null)}
耗时操作2 {number = 5, name = (null)}
耗时操作1 {number = 4, name = (null)}
主线程刷新 {number = 1, name = main}
  • GCD对循环任务的处理
    /// GCD循环任务处理
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t i) {
        NSLog(@"GCD循环任务处理   %@:%zu",[NSThread currentThread],i);
    });

输出结果是由不同线程完成的,有效的提升了循环的效率

GCD循环任务处理   {number = 3, name = (null)}:2
GCD循环任务处理   {number = 6, name = (null)}:1
GCD循环任务处理   {number = 1, name = main}:0
GCD循环任务处理   {number = 5, name = (null)}:3
GCD循环任务处理   {number = 3, name = (null)}:4
GCD循环任务处理   {number = 1, name = main}:5
GCD循环任务处理   {number = 6, name = (null)}:6
GCD循环任务处理   {number = 5, name = (null)}:7
GCD循环任务处理   {number = 3, name = (null)}:8
GCD循环任务处理   {number = 1, name = main}:9
  • GCD中的消息与信号
    GCD框架中提供了dispatch_source_t的对象,用来接收和传递某个消息,之后执行对应的代码块
    /// 创建数据对象
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    /// 接收数据变化
    dispatch_source_set_event_handler(source, ^{
        NSLog(@"收到数据 %lu",dispatch_source_get_data(source));
    });
    /// 启动
    dispatch_resume(source);
    /// 设置数据
    dispatch_source_merge_data(source, 1);

输出:

收到数据 1

GCD还有一个概念是信号量,它的用法与消息类似

    /// 创建一个信号 初始值为0
    dispatch_semaphore_t singer = dispatch_semaphore_create(0);
    /// 发送信号
    dispatch_semaphore_signal(singer);
    /// 等待信号,当信号大于0执行后面的代码,否则等待(会阻塞当前线程),第二个参数为等待时间,DISPATCH_TIME_FOREVER为一直等待
    dispatch_semaphore_wait(singer, DISPATCH_TIME_FOREVER);
    NSLog(@"执行");

注:每次执行过等待信号后,信号量会减1

NSOperation

NSOperation可以理解为任务操作,是基于Objective-C封装的一套管理与执行线程操作的类,这个类是一个抽象类,通常我们会使用NSInvocationOperation和NSBlockOperation这两个子类进行开发,或者也可以继承于NSOperation类,封装我们自己的类。

  • NSInvocationOperation
    /// 创建NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    /// 启动NSInvocationOperation
    [invocationOperation start];

输出结果:并未开启新的线程

当前线程 {number = 1, name = main}
  • NSBlockOperation
    /// NSBlockOperation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程 %@",[NSThread currentThread]);
    }];
    /// 启动NSBlockOperation
    [operation start];

输出结果:并未开启新的线程

当前线程 {number = 1, name = main}

直接使用NSInvocationOperation和NSBlockOperation不会开启新的线程,但是可以使用NSBlockOperation的addExecutionBlock方法开启新的线程

    /// NSBlockOperation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程 %@",[NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@"方法一当前线程 %@",[NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@"方法二当前线程 %@",[NSThread currentThread]);
    }];
    /// 启动NSBlockOperation
    [operation start];

输出结果:通过addExecutionBlock的方法开启了新的线程

当前线程 {number = 1, name = main}
方法一当前线程 {number = 8, name = (null)}
方法二当前线程 {number = 7, name = (null)}
  • 继承NSOperation的子类
    首先我们继承NSOperation,然后重新它的main方法
#import "MyOperation.h"

@implementation MyOperation

- (void)main{
    NSLog(@"当前线程%@",[NSThread currentThread]);
}

@end

使用:

    /// 创建自定义的Operation
    MyOperation *operation = [[MyOperation alloc] init];
    [operation start];

结果:并未开启新的线程

当前线程{number = 1, name = main}

总结:NSOperation是需要配合使用NSOperationQueue来实现多线程的

  • NSOperationQueue
    NSOperationQueue有两种队列(主队列、其他队列)
    /// 主队列
    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    
    /// 其他队列,默认并发,开启多线程
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSOperationQueue有一个参数maxConcurrentOperationCount最大并发数量,默认为-1,并发执行,当maxConcurrentOperationCount为1时,则表示串行执行,当maxConcurrentOperationCount大于1时,则表示并发执行。

  • NSOperation+NSOperationQueue
    创建NSInvocationOperation和NSBlockOperation,并把它们加入队列。
    /// 创建队列,默认并发执行
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    /// 创建NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    
    /// NSBlockOperation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程 %@",[NSThread currentThread]);
    }];
    [queue addOperation:invocationOperation];
    [queue addOperation:operation];

结果:都是在子线程执行的,开启了新线程。

当前线程 {number = 5, name = (null)}
当前线程 {number = 6, name = (null)}

直接加入队列

    /// 创建队列,默认并发执行
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperationWithBlock:^{
        NSLog(@"当前线程 %@",[NSThread currentThread]);
    }];

结果:子线程执行,开启了新线程。

当前线程 {number = 7, name = (null)}

NSOperationQueue的串行与并行

    /// 创建队列,默认并发执行
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    /// 设置最大并发数
    queue.maxConcurrentOperationCount = 1;
    
    [queue addOperationWithBlock:^{
        NSLog(@"当前线程1 %@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"当前线程2 %@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"当前线程3 %@",[NSThread currentThread]);
    }];

当maxConcurrentOperationCount设置为1时,开启了新的线程且串行执行。

当前线程1 {number = 6, name = (null)}
当前线程2 {number = 6, name = (null)}
当前线程3 {number = 6, name = (null)}

当maxConcurrentOperationCount大于1时,开启了新的线程且并发执行。

当前线程2 {number = 6, name = (null)}
当前线程1 {number = 4, name = (null)}
当前线程3 {number = 5, name = (null)}
  • NSOperation操作依赖
    当我们在执行线程任务时,B需要等待A执行完成之后才能执行,这时候就可以使用NSOperation的操作依赖。
    /// 创建队列,默认并发执行
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    /// 操作1
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    
    /// 操作2
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 操作2当前线程 %@",[NSThread currentThread]);
    }];
    /// 操作1需要等待操作2执行完成
    [invocationOperation addDependency:operation];
    [queue addOperation:invocationOperation];
    [queue addOperation:operation];

结果:操作2完成之后才会执行操作1

操作2当前线程 {number = 7, name = (null)}
操作1当前线程 {number = 7, name = (null)}

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