我的iOS开发小屋天生不是作曲家牛逼哄哄的iOS开发

iOS多线程-归纳与总结

2016-11-16  本文已影响5566人  抱紧我的小鲤鱼
原创内容,转载请注明出处:

http://www.jianshu.com/p/ac11fe7ef78c

前言

多线程的东西好久没弄过了,项目里面用不到,慢慢都忘记了,最近不太忙,把这些东西重新拿出来弄一下,做下复习.这次先把api的东西捋一遍,下次再详说比较深层点的东西.

NSThead

先说几个点

iOS中隐式中创建线程的方法(NSObject的方法):

//(NSObject)
//1.waitUntilDone:在主线程中运行方法,wait表示是否阻塞这个方法的调用,如果为yes则等待主线程中的运行方法结束。一般可用于在子线程中调用ui方法。此规则针对其他线程也适用。
//2. modes:关于这个参数,暂时不做讲解,下次再说,这里先说一下,如果modes参数为kCFRunLoopCommonModes的话可以结局滑动过程中图片赋值引起的页面卡顿问题,等.
//3.此方法不能够自动回收线程,如果并发数量多,会建立大量子线程。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait 
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array 
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg
//延迟执行这里,用到runloop的东西,不太懂runloop的同学,可以去学习一下,我下次也会说到
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSString *> *)modes;
//取消线程队列中还没有执行的方法
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;

NSThread各个属性的意义

@property double threadPriority //线程优先级(double)0.0~1.0,默认0.5,优先级和执行顺序成正比
@property (nullable, copy) NSString *name  //线程名字
@property NSUInteger stackSize  //线程栈大小,默认主线程1m ,子线程512k,次属性可读写,但是写入大小必须为4k的倍数,最小为16k
@property (readonly) BOOL isMainThread // 是否是主线程
@property (readonly, getter=isExecuting) BOOL executing  //是否正在执行
@property (readonly, getter=isFinished) BOOL finished  //是否已经完成
@property (readonly, getter=isCancelled) BOOL cancelled  //是否已经取消

NSThead类方法,作用域:当前线程

+ (NSThread *)currentThread; //返回当前线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument; //开辟一个新的线程
+ (void)sleepUntilDate:(NSDate *)date;//休眠到什么时候(具体日期)
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; //休眠一段时间单位秒
+ (void)exit; //结束当前线程
+ (double)threadPriority; //返回当前线程优先级
+ (BOOL)setThreadPriority:(double)p; //设置当前线程优先级 0.0~1.0
+ (NSArray<NSNumber *> *)callStackReturnAddresses //返回当前线程访问的堆栈信息
+ (NSArray<NSString *> *)callStackSymbols //返回一堆十六进制的地址
+ (BOOL)isMainThread //返回当前线程是否是主线程
+ (NSThread *)mainThread //返回主线程

NSThead实例方法

- (instancetype)init //总共五个实例方法中没有给NSThread 加 selector 的方法,那这个方法是干什么用的呢?目测是用来继承然后重写main方法来用的
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument //通过selector初始化
- (void)main //主方法,用于子类继承重写
- (void)start //开始线程
- (void)cancel //取消线程

一些注意点:

NSOperation

基本属性:

@property (nullable, copy) NSString *name  //该操作的名称
@property (readonly, getter=isCancelled) BOOL cancelled; // 该操作是否已经取消
@property (readonly, getter=isExecuting) BOOL executing;  //该操作是否正在执行
@property (readonly, getter=isFinished) BOOL finished;  //该操作是否已经完成
@property (readonly, getter=isConcurrent) BOOL concurrent;   //该操作是否是并行操作
@property (readonly, getter=isAsynchronous) BOOL asynchronous   //该操作是否是异步的
@property (readonly, copy) NSArray<NSOperation *> *dependencies;  //和该操作有关的依赖关系
@property NSOperationQueuePriority queuePriority;  //该操作的优先级
@property (nullable, copy) void (^completionBlock)(void)   //该操作的完成回调block
@property double threadPriority  // 该操作的优先级

实例方法:

- (void)start; //开始方法,被加入队列或手动开始的时候会被调用
- (void)main; // 队列的主方法,start 后执行,子类应重写此方法相比于start方法
- (void)cancel; // 取消操作
- (void)addDependency:(NSOperation *)op;  //添加依赖,依赖只是设置先后执行的顺序关系,可以跨队列依赖,不可以循环依赖。添加依赖以后会顺序执行任务,但是不一定开一个线程,可能会开多个线程,但是不会太多。
- (void)removeDependency:(NSOperation *)op;  //移除依赖关系
- (void)waitUntilFinished  //
子类NSInvocationOperation

属性:

@property (readonly, retain) NSInvocation *invocation; //此队列的NSInvocation参数,注意此参数只读
@property (nullable, readonly, retain) id result; //

实例方法:

- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;//通过@selector 初始化
- (instancetype)initWithInvocation:(NSInvocation *)inv //通过NSInvocation初始化

一些注意点:

 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
                                                                  selector:@selector(test1)
                                                                    object:@"Invocation"];

添加到队列方式启动

NSOperationQueue *queue = nil;
[queue addOperation:op];

调用start方式启动

[op start];
子类NSInvocationOperation

属性:

@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks; //此操作的所有blcok

类方法:

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;// 通过block初始化操作,该block没有参数没有返回值,

实例方法:

- (void)addExecutionBlock:(void (^)(void))block;//添加block,该block没有参数没有返回值

一些注意点:


NSOperationQueue

属性:

@property (readonly, copy) NSArray<__kindof NSOperation *> *operations; //当前队列的所有操作
@property (readonly) NSUInteger operationCount;// 当前队列的所有操作的数量
@property NSInteger maxConcurrentOperationCount; //设置最大并发量
@property (getter=isSuspended) BOOL suspended; //挂起任务和开始任务,挂起的队列不会影响已执行中和执行完的任务,队列暂停再添加任务也不会自动执行任务了
@property (nullable, copy) NSString *name ;//队列的名字(不只是操作,队列也可以有名字)
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue //

实例方法:

- (void)addOperation:(NSOperation *)op; //添加操作
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;//批量添加操作
- (void)addOperationWithBlock:(void (^)(void))block;//添加操作块
- (void)cancelAllOperations; //取消所有操作,已经执行的不会被取消,这时候会设置所有子操作对象的isCanceled 为yes,自己可以在子类操作里代码设置操作的取消,不会影响挂起的状态
- (void)waitUntilAllOperationsAreFinished; //暂时不知道这个方法的作用

类方法

+ (nullable NSOperationQueue *)currentQueue;//返回当前线程所在的队列
+ (NSOperationQueue *)mainQueue;//返回主队列

一些注意点:

关于NSOperation的子类在NSOperationQueue中执行完无法释放的问题

感谢简友_JD提出的问题,原文为: 你的NSOperation dealloc了么?并发数真的生效了么?

问题是这样的: NSOperation的子类创建出来并且执行完任务以后没有被释放.然后问题的具体描述和解决办法上文已经写的很清楚啦,我就不重复一遍了.
然后说一下我对这个问题的产生的理解:

GCD

先列一下GCD中:队列,同步任务,异步任务,线程之间的关系,验证代码就不贴这里了,有兴趣的同学可以挨个验证一下:

总结:
全局队列,主队列,并行队列,串行队列区别和联系:
dispatch_barrier_async栅栏函数
      NSLog(@">>>>>111:%@",[NSThread mainThread]);
    dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-1:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-2:%@",[NSThread currentThread]);
    });
    dispatch_barrier_async(concurrentQueue, ^(){
        NSLog(@"dispatch-barrier:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-3:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-4:%@",[NSThread currentThread]);
    });

打印结果

 >>>>>111:<NSThread: 0x7fc023c08bd0>{number = 1, name = main}
 >>>>>111:<NSThread: 0x7fc023c08bd0>{number = 1, name = main}
 dispatch-1:<NSThread: 0x7fc023f23780>{number = 2, name = (null)}
 dispatch-2:<NSThread: 0x7fc025b00000>{number = 3, name = (null)}
 dispatch-barrier:<NSThread: 0x7fc025b00000>{number = 3, name = (null)}
 dispatch-4:<NSThread: 0x7fc023f23780>{number = 2, name = (null)}
 dispatch-3:<NSThread: 0x7fc025b00000>{number = 3, name = (null)}

dispatch_group_t队列组

队列组可以手动管理也可以交给系统管理.

系统管理队列组:

 dispatch_group_async(group, queue, ^{
        
 });

手动管理队列组:dispatch_group_enter和dispatch_group_leave(这两个必须成对使用)

dispatch_group_enter(group);
    dispatch_async(queue, ^{
        
        dispatch_group_leave(group);
    });

以上的两段代码是相同的.

dispatch_group_notify队列组监听

在并行队列里面可以同时并发执行多个任务,但是有时我们可能需要等到全部任务都执行完成以后再去执行另外一个任务,这时候就需要用到GCD的这一个特性.
先看代码:

    dispatch_queue_t queue = dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@">>>>>>111:%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1];
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@">>>>>>222:%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2];
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@">>>>>>333:%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:5];
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@">>>>>>444:%@",[NSThread currentThread]);
    });

打印日志:

>>>>>>111:<NSThread: 0x7fd830414c80>{number = 2, name = (null)}
>>>>>>222:<NSThread: 0x7fd830405780>{number = 4, name = (null)}
>>>>>>333:<NSThread: 0x7fd830586c40>{number = 3, name = (null)}
>>>>>>444:<NSThread: 0x7fd830586c40>{number = 3, name = (null)}

dispatch_group_notify总结:此方法用于监听多并发队列,非并发无法监听,也没什么意义.

dispatch_semaphore_t信号量 和 dispatch_group_wait队列组等待

信号量是用于控制并发数量的,所以只用在全局队列和并行队列中.

创建信号量



信号量等待:dispatch_semaphore_wait(dispatch_semaphore_t, dispatch_time_t)

设置等待需要两个参数:
(a)需要等待的信号量(dispatch_semaphore_t);
(b)等待时间(dispatch_time_t)这个时间表示你可以为执行任务所等待的时间,一般都设置为永远等待(DISPATCH_TIME_FOREVER)这样的话如果前面的任务没有执行完就一直等待到有任务执行完然后腾出来线程以后去执行任务.另外还可以设置一个时间(dispatch_time_t)比如说是1秒,这就意味这一秒以后如果前面的任务执行完了那么就按正常走,否则的话就新建一个另外的线程去执行等的着急的任务.
返回值(long)
如果是0就是等待成功(也就是没有超时)否则为失败(也就是超时了)

关于dispatch_semaphore_t:

在设置timeout时,比较有用的两个宏:DISPATCH_TIME_NOWDISPATCH_TIME_FOREVER.DISPATCH_TIME_NOW表示当前.DISPATCH_TIME_FOREVER表示遥远的未来.一般可以直接设置timeout为这两个宏其中的一个,或者自己创建一个dispatch_time_t类型的变量.
创建dispatch_time_t类型的变量有两种方法,dispatch_time和dispatch_walltime.利用创建dispatch_time创建dispatch_time_t类型变量的时候一般也会用到这两个变量.
dispatch_time的声明如下:

dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);其参数when需传入一个dispatch_time_t类型的变量,和一个delta值.表示when加delta时间就是timeout的时间.例如:dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW,110001000*1000);表示当前时间向后延时一秒为timeout的时间.

代码示例:

    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < 10; i++){
        
        //dispatch_time_t wait = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
        dispatch_time_t wait = DISPATCH_TIME_FOREVER;
        dispatch_semaphore_wait(semaphore, wait);
        dispatch_group_async(group, queue, ^{
            NSLog(@"----------------:%i :%@",i,[NSThread currentThread]);
            [NSThread sleepForTimeInterval:1];
            dispatch_semaphore_signal(semaphore);
        });
        
    }
    dispatch_group_notify(group, queue, ^{
        NSLog(@"xxxxxxxxxxxxxx");
    });

打印日志:

2016-11-15 23:08:25.894 多线程总结[86647:6419764] ----------------:0 :<NSThread: 0x7fc011d1ac90>{number = 2, name = (null)}
2016-11-15 23:08:25.894 多线程总结[86647:6419757] ----------------:1 :<NSThread: 0x7fc011f0ac80>{number = 3, name = (null)}
2016-11-15 23:08:25.894 多线程总结[86647:6419768] ----------------:2 :<NSThread: 0x7fc011c0f880>{number = 4, name = (null)}
2016-11-15 23:08:26.898 多线程总结[86647:6419764] ----------------:5 :<NSThread: 0x7fc011d1ac90>{number = 2, name = (null)}
2016-11-15 23:08:26.898 多线程总结[86647:6419768] ----------------:3 :<NSThread: 0x7fc011c0f880>{number = 4, name = (null)}
2016-11-15 23:08:26.898 多线程总结[86647:6419757] ----------------:4 :<NSThread: 0x7fc011f0ac80>{number = 3, name = (null)}
2016-11-15 23:08:27.904 多线程总结[86647:6419757] ----------------:7 :<NSThread: 0x7fc011f0ac80>{number = 3, name = (null)}
2016-11-15 23:08:27.904 多线程总结[86647:6419764] ----------------:6 :<NSThread: 0x7fc011d1ac90>{number = 2, name = (null)}
2016-11-15 23:08:27.904 多线程总结[86647:6419768] ----------------:8 :<NSThread: 0x7fc011c0f880>{number = 4, name = (null)}
2016-11-15 23:08:28.908 多线程总结[86647:6419768] ----------------:9 :<NSThread: 0x7fc011c0f880>{number = 4, name = (null)}
2016-11-15 23:08:29.913 多线程总结[86647:6419768] xxxxxxxxxxxxxx

代码分析:

首先这里面创建了一个组,这个组放在这里只是告诉你可以这么用,它不会影响信号量的功能.
这里创建了一个并发为3的信号量然而for循环是10个任务,那么理论上10个任务会创建十个线程去执行,但是你信号量为3所以只能创建3个线程去执行前面10个任务,然后等待前3个任务执行完成了腾出来新的线程再去执行等待执行的.所以下面的线程 number 最大是4,当然这前提是你设置的超时时间大于任务执行完的时间.如果设置超时时间是0.1秒的话,任务等超时了还是会开启另外的线程去执行任务,这样就达不到控制并发量的要求了.

关于信号量举个小栗子帮助大家理解下:

一般可以用停车来比喻:
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。

NSOperation与GCD的对比


iOS的常用多线程使用方式基本都在这里了.但是这里没有说线程安全,原理,线程锁,runloop等和线程相关的东西的.先这样,下次再详细说说更深点的东西.
上面的内容是很久以前笔记记下的东西,现在没重新做验证,如有错误,望请指正,多谢多谢.

在篇尾

程序员不需要打赏,只希望自己的项目能帮助更多人,请支持我的git开源框架:TFEasyCoder

原创内容,转载请注明出处:

http://www.jianshu.com/p/ac11fe7ef78c

如果我的文章对您有帮助请不吝喜欢和关注~~

上一篇下一篇

猜你喜欢

热点阅读