iOS多线程总结

时间:2023-01-20 21:09:18

总览

iOS多线程开发的技术,主要包括pthread、NSThread、GCD、NSOperation;其中pthread/NSThread用的情况不多,而GCD和NSOperation用的比较频繁,所以是重点。

pthread

pthread的全称叫POSIX Thread,是一个线程管理的通用规范,基本上所有的开发平台都有实现,所以用pthread实现的代码,其通用性比较强。然而pthread不是面向对象的,而且需要开发者自己管理线程,包括创建线程、结束线程、处理冲突、各种锁等,一般在底层开发的时候用的会比较多,比如C语言的开发等,在iOS中用的很少。我曾经在一个音频库中用过。这个东西了解一下就好。

NSThread

NSThread可以说是对pthread的封装,里面有几个比较好用的方法,下面一一讲一下。

初始化和开始线程

//初始化线程,SEL是线程的入口
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;
//开始线程
- (void)start;

暂停线程

//让线程休眠到某一时刻
+ (void)sleepUntilDate:(NSDate *)date;
//让线程休眠一段时间
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

几个变量+方法,获取线程的情况

NSThread有几个参数,可以很方便的得到线程的运行情况:

executing :线程是否在运行;

finished:线程是否结束;

cancelled:线程是否被取消了;

[NSThread isMainThread]:当前线程是不是主线程;

[NSThread currentThread]:获取当前线程;

可以看出来,NSThread的功能比较简单,实际开发中用的也不多,了解一下就好。

GCD(Grand Central Dispatch)

同学们注意,划重点了!(敲黑板)

简介

GCD是iOS和Mac系统中实现的多线程模块,在实际开发过程中用的也最多,要着重掌握。

几个概念:

queue:也就是dispatch_queue,就是处理多线程的queue,tasks都是放在queue中处理的。一个queue可以维护多个threads。

task:每一个任务,叫做task;task被enqueue进queue之后,由queue自主分配线程执行。

GCD的优点:

1、使用简单,不需要创建和维护线程;

2、灵活方便,大部分情况下可以避免维护线程锁;

下面逐个知识点介绍。

dispatch_queue的种类

共2类:

serial queue:也就是每次只能执行一个task,后面再enqueue进来的task,要等到前面的task执行完之后再依次执行,即FIFO规则。serial queue的这个特性,可以用来处理多个线程要写一个资源的情况。将所有tasks放到一个serial queue中,就可以直接实现这个需求,而不需要自己管理锁;

concurrent queue:可以同步执行多个tasks。每个task运行于独立的线程中。执行任务时,也是按照添加到queue的顺序执行。

一个特殊的queue:main queue,也就是刷新UI所用的queue。它是serial queue,所以当阻塞main queue的时候,UI的刷新就会被阻塞。main queue通过dispatch_get_main_queue()方法得到。

系统提供了4个可以直接使用的concurrent queues,按照queue的优先级,分为4种:

high/default/low/background;

通过dispatch_get_global_queue()方法来获取。

dispatch执行task的方式

queue执行task的方式有2种:async和sync方式。

async方法:用这种方法执行task时,当前queue(一般是main queue)不会被阻塞。async方法比较更常用一些。

sync方法:用这种方法执行task时,当前queue会等待task任务完成,再继续执行下面的任务。比如,如果在main queue时,使用sync方法,main queue就会被阻塞,此时UI无法操作。

注意:如果在一个serial queue中执行sync方法,并再次调用此queue,会导致死锁。下面举个栗子:

//在main queue中
dispatch_sync(dispatch_get_main_queue(),^{
NSLog(@"where is main queue?");
})
;

为什么会导致死锁?因为main queue是一个serial queue,此时执行sync方法,会导致main queue被锁住。而sync方法又要求在main queue中执行task(此时main queue已经被锁住了),导致此task一直在等待解锁,从而导致死锁。

dispatch_group

dispatch_group是用来管理一组queue的方法。比如,如果需要在一组queue都执行完任务再进行下一步操作,就可以用到dispatch_group。

具体做法:

//将queue加入到group
dispatch_group_async(group, queue, ^(block));
//多个dispatch_group_async
//...
//当所有block都执行完毕,此block才会被调用
dispatch_group_notify(group, queue, ^(block));

//或者用wait方法也可以实现,还可以加一个超时时间
dispatch_group_wait(group, 10);//阻塞当前线程
//继续操作
//...

其它要注意的地方

dispatch_apply使用技巧

根据苹果的官方文档,如果需要很多个任务,可以用dispatch_apply()来实现,这样做比直接用for循环效率高。

dispatch_apply是sync的调用方法,因此会阻塞当前线程。可以将dispatch_apply包装到dispatch_async中来实现。按照文档说法,调用dispatch_apply后,需要等待所有tasks都执行结束,才会执行后面的操作。所以它也应该可以实现dispatch_group可以实现的功能。

dispatch_get_current_queue被deprecated了

为什么?我的理解是,有2个原因:

  1. 如果在queue A中调用queue B,这个时候current queue是A还是B呢?这个就比较难以回答。一般来讲我们想得到的答案是A,但实际上结果是B。但我们没必要知道我们在B上,因为是我们自己调用的queue B。
  2. 如果当前queue是serial queue,容易导致死锁。

单例模式的几种实现方式

  1. @synchronized关键字:

    static id instance;
    - (instancetype)singleton{
    @synchronized(self){
    if (instance == nil){
    instance = ...;
    }
    }
    return instance;
    }

    @synchronized是Objective-C层面实现的互斥锁,以此保证同一时刻只会执行一个task,从而实现单例模式。

  2. dispatch_once

    static id instance;
    - (instancetype)singleton{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    instance = ...;
    });
    return instance;
    }

    dispatch_once可以保证代码只被执行一次,从而实现单例模式。

如何解决争夺资源的问题?

  1. 用@synchronized方法;
  2. 用serial queue;
  3. 用dispatch_barrier_async()方法。此方法会保证queue在当前时刻,只会执行这一个task;

NSOperaion/NSOperationQueue

NSOperation和NSOperationQueue是对GCD的封装,使之面向对象,更好用。

NSOperation -> GCD的task;

NSOperationQueue -> GCD的queue;

NSOperation有2个subClass,NSInvocationOperation(调用function)和NSBlockOperation(调用block)。

讲一下它们的优势。

NSOperation在执行的过程中,可以方便的取消操作:

[operation cancel];

还可以设置任务执行完成之后的操作:

[operation setCompletionBlock:^{
//任务完成后的操作
}]
;

NSOperation可以添加多个操作,这些操作是在多个线程中同时执行的:

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//操作1
}];
[operation addExecutionBlock:^{
//操作2
}];
[operation addExecutionBlock:^{
//操作3
}];
[operation start];

NSOperation可以同时管理多个operation,比如批量暂停、等待所有操作都完成之后再继续等。

NSOperationQueue可以设置队列的最大并发操作数目:

queue.maxConcurrentOperationCount = 1;

这样就实现了一个serial queue。实现concurrent queue也是一样,只是将数目改一下就好了。

NSOperation有一个比较强大的功能,可以设置各个operation之间的相互依赖:

//在operationA完成之后,operationB才开始执行
[operationB addDependency:operationA];

而且支持图状的依赖(当然是无环图,如果是有环图的话,就死锁了)。