iOS 多线程编程

NSThread

平时常用来获取当前线程和主线程

// 获得主线程
+ (NSThread *)mainThread;   
+ (NSThread *)currentThread;  

启动线程方法

- (void)start;
// 线程进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态

阻塞(暂停)线程方法

+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 线程进入阻塞状态

强制停止线程

+ (void)exit;
// 线程进入死亡状态

如何创建线程

+ (void)detachNewThreadWithBlock:(void (^)(void))block; // iOS 10 新增
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;//会自启动线程
- (instancetype)initWithBlock:(void (^)(void))block; // iOS 10 新增
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;//需要调用start方法开启线程
- (instancetype)init;

然后可以设置线程优先级,采用qualityOfService 属性

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    // 用户交互权限,属于最高等级,常被用于处理交互事件或者刷新UI,因为这些需要即时的
    NSQualityOfServiceUserInteractive = 0x21,
    
    // 为了可以进一步的后续操作,当用户发起请求结果需要被立即展示,比如当点了列表页某条信息后需要立即加载详情信息
    NSQualityOfServiceUserInitiated = 0x19,
    
    // 不需要马上就能得到结果,比如下载任务。当资源被限制后,此权限的任务将运行在节能模式下以提供更多资源给更高的优先级任务
    NSQualityOfServiceUtility = 0x11,
    
    // 后台权限,通常用户都不能意识到有任务正在进行,比如数据备份等。大多数处于节能模式下,需要把资源让出来给更高的优先级任务
    NSQualityOfServiceBackground = 0x09,

    // 默认权限,具体权限由系统根据实际情况来决定使用哪个等级权限,如果实际情况不太利于决定使用何种权限,则从UserInitiated和Utility之间选一个权限并使用。
    NSQualityOfServiceDefault = -1
}

GCD

在这里插入图片描述
GCD 公开有 5 个不同的队列:运行在主线程中的 main queue,3 个不同优先级的后台队列,以及一个优先级更低的后台队列(用于 I/O)。 另外,开发者可以创建自定义队列:串行或者并行队列。自定义队列非常强大,在自定义队列中被调度的所有 block 最终都将被放入到系统的全局队列中和线程池中。
但还是在绝大多数情况下使用默认的优先级队列就可以了。

苹果官方对GCD的说明。
开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
那么来看一下GCD的API
在这里插入图片描述
详解可参考浅谈iOS多线程_使用篇

NSOperation

顾名思义,操作,可以定义一系列操作,然后添加在队列NSOperationQueue中,有点像GCD block里面那部分。
NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们有三种方式来封装操作。

  • 使用子类 NSInvocationOperation
  • 使用子类 NSBlockOperation
  • 自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作。

通过一个例子来学习一下

- (void)viewDidLoad {
    [super viewDidLoad];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"com.qst.test";
    queue.maxConcurrentOperationCount = 2;
    
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test1) object:nil];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"第二头");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"第二头大象");
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"第四头");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"第四头大象");
    }];
    //添加到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op4];
		//或者直接执行
		//[op1 start];
		//[op2 start];
 }
- (void)test1 {
    NSLog(@"第一头。。。");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"第一头大象");
    
}

在这里插入图片描述
可见其创建操作的方式,而且并发执行
NSBlockOperation还可以添加任务,但是其任务之间是并行执行的。

    [op2 addExecutionBlock:^{
        NSLog(@" 第三头..");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"第三头大象");
    }];

那怎么样三个操作顺序执行呢
设置依赖,使当前操作依赖于操作 op 的完成。

    [op2 addDependency:op1];
    [op4 addDependency:op2];

在这里插入图片描述

//取消某个NSOperation
operation1.cancel()
 
//取消某个NSOperationQueue剩余的NSOperation
queue.cencelAllOperations()

然后还有NSOperation的优先级等等详解,可参考
iOS 多线程:『NSOperation、NSOperationQueue』详尽总结

对比

GCD以 block 为单位,代码简洁。同时 GCD 中的队列、组、信号量、source、barriers 都是组成并行编程的基本原语。对于一次性的计算,或是仅仅为了加快现有方法的运行速度,选择轻量化的 GCD 就更加方便。

而 NSOperation 可以用来规划一组任务之间的依赖关系,设置它们的优先级,任务能被取消。队列可以暂停、恢复。NSOperation 还可以被子类化。这些都是 GCD 所不具备的。
所以在开发中,结合使用效率更高

死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁问题发生在许多领域,往往跟数据资源息息相关。
那么GCD中的死锁是什么呢

dispatch_sync(dispatch_get_main_queue(), ^(void){
            NSLog(@"这里死锁了");
        });

这种在某一个串行队列同步向这个队列派发一个任务,就会导致死锁。如果是同步的向其他队列添加任务,并不会造成死锁
在主线程中执行dispatch_sync代码,但是又需要在主队列中添加block,两者互相等待执行完成,造成死锁
为什么向并发队列添加的任务,没有开启新线程,而是在主线程执行的?

使用dispatch_sync 添加同步任务,必须等添加的block执行完成之后才返回。
既然要执行block,肯定需要线程,要么新开线程执行,要么再已存在的线程(包括当前线程)执行。  
dispatch_sync的官方注释里面有这么一句话:
As an optimization, dispatch_sync() invokes the block on the current thread when possible.
作为优化,如果可能,直接在当前线程调用这个block。
  
所以,一般,在大多数情况下,通过dispatch_sync添加的任务,在哪个线程添加就会在哪个线程执行。

上面我们添加的任务的代码是在主线程,所以就直接在主线程执行了。

使用dispatch_sync函数添加到serial dispatch queue中的任务,其运行的task往往与所在的上下文是同一个thread;使用dispatch_async函数添加到serial dispatch queue中的任务,一般会(不一定)新开一个线程,但是不同的异步任务用的是同一个线程

还有一种死锁情况
嵌套使用dispatch_apply,因为dispatch_apply会等待全部处理执行结束

dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
    
    dispatch_apply(3, queue, ^(size_t i) {
        NSLog(@"apply loop: %zu", i);
        
        //再来一个dispatch_apply!死锁!
        dispatch_apply(3, queue, ^(size_t j) {
            NSLog(@"apply loop inside %zu", j);
        });
    });

说到死锁这个问题,博大精深啊

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"1");
        dispatch_sync(queue, ^{
            NSLog(@"2");
        });
        NSLog(@"3");
        
    });

像下面这个,肯定会造成死锁派发第一个任务在串行队列里,然后执行block,到派发第二个任务,因为不会开启新线程,在主线程中,又是同步派发,所以相当于在主线程中同步派发一个任务,相当于上面的那种情况
主队列中的任务不一定在主线程中执行

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"1");
        dispatch_sync(queue, ^{
            NSLog(@"2");
        });
        NSLog(@"3");
        
        NSLog(@"main thread: %d", [NSThread isMainThread]);
        // 判断是否是主队列
        void *value2 = dispatch_get_specific("key");//返回与当前分派队列关联的键的值。
        NSLog(@"main queue: %d", value2 != NULL);
       
    });
    void *value3 = dispatch_get_specific("key");//返回与当前分派队列关联的键的值。
    NSLog(@"main queue: %d", value3 != NULL);

对同一个并发队列中进行同步嵌套。这里不会构成死锁,因为同步虽然不会开启新线程,但是并发队列,它并不会等待一个任务执行完才执行另一个

不同队列中嵌套同步操作dispatch_sync的结果:

// 全局队列,都在主线程上执行,不会死锁
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 并行队列,都在主线程上执行,不会死锁
dispatch_queue_t q = dispatch_queue_create("m.baidu.com", DISPATCH_QUEUE_CONCURRENT);
// 串行队列,会死锁,但是会执行嵌套同步操作之前的代码
dispatch_queue_t q = dispatch_queue_create("m.baidu.com", DISPATCH_QUEUE_SERIAL);
// 直接死锁
dispatch_queue_t q = dispatch_get_main_queue();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值