iOS 多线程

2019-01-10  本文已影响0人  yyggzc521
进程与线程区别

A8处理器是一款双核处理器,A12处理器是6核CPU
进程是cpu资源分配的最小单位,线程是cpu调度的最小单位
一个程序至少有一个进程,一个进程至少有一个线程。线程依赖于进程才能运行
多线程实现主要是靠硬件CPU(中央处理器)件来实现的,CPU有一个很重要的特性时间片

更多的线程意味着更多的内存开销。创建线程也是需要CPU开销的
如果线程比核的数量多,则同一时间只能执行与核数量相等的线程数,线程过多会导致频繁的切换,消耗过多的CPU时间,降低了程序性能
多线程就可能出现线程安全问题,为了解决线程安全需要使用锁,进而可能会出现死锁问题。过多的线程会增加程序设计的复杂性,浪费更多精力去处理多线程通信和数据共享

多线程方案.png 队列和同步、异步的组合结果.png

总结:只要是同步或者是主队列都不会创建子线程!!!

NSThread

使用NSThread需要自己管理线程生命周期

类方法:自动开启

[NSThread detachNewThreadSelector:@selector(timerThread) toTarget:self withObject:nil];

对象方法:需要手动开启

_eocThread = [[NSThread alloc] initWithTarget:self selector:@selector(timerThreadTwo) object:nil];
    [_eocThread start];
self.block = ^{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        #//线程内部调用这个代码 会使线程睡眠 暂停1秒
        [NSThread sleepForTimeInterval:1];
        NSLog(@"%@",weakSelf);
    });
};

GCD

优点

  1. 多核并行运算
  2. 自动管理生命周期
  3. 简单高效

创建队列

    dispatch_queue_create(@"标识", DISPATCH_QUEUE_SERIAL)//串行
    dispatch_queue_create(@"标识", DISPATCH_QUEUE_CONCURRENT)//并行
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0)//获取全局并发队列
    dispatch_queue_t queue = dispatch_get_main_queue()//主队列
注意:queue4和queue5并不是同一个对象,最好不要这么写

NSOperation

基于GCD,更加面向对象,但是处理简单任务会比GCD代码更多 ;
需要配合 NSOperationQueue 来实现多线程。因为默认情况下,NSOperation 单独使用时系统同步执行操作
NSOperation中所有的任务执行完finished == YES,并且任务的执行不是按照先进先出执行的,而是并发无序的执行!!!

优点

  1. 添加操作之间的依赖关系,方便的控制执行顺序
  2. 设定操作执行的优先级
  3. 可以很方便的取消一个操作的执行
  4. 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled

NSOperationQueue

1. NSBlockOperation
operationQueue = [NSOperationQueue new];
    blockOperation = [NSBlockOperation new];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"one finish");
    }];
    
    [blockOperation addExecutionBlock:^{
       NSLog(@"%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"two finish");
    }];
    operationQueue.maxConcurrentOperationCount = 4;//设置线程最大并发数
    [operationQueue addOperation:blockOperation];

任务执行完毕后再次调用start方法,operation并不会再次执行,因为内部进行判断,当finish == YES 时不再执行

[blockOperation start];

自定义封装NSOperation

NSOperation只需要重写其中的main或start方法,在多线程执行任务的过程中需要注意线程安全问题,
我们还可以通过KVO监听isCancelled、isExecuting、isFinished等属性,确切的回调当前任务的状态

  1. NSOperationQueue的maxConcurrentOperationCount一般设置在5个以内,数量过多可能会有性能问题。maxConcurrentOperationCount为1时,队列中的任务串行执行,maxConcurrentOperationCount大于1时,队列中的任务并发执行;
  1. 不同的NSOperation实例之间可以设置依赖关系,不同queue的NSOperation之间也可以创建依赖关系 ,但是要注意不要“循环依赖”;
  2. NSOperation实例之间设置依赖关系应该在加入队列之前;
  3. 在没有使用 NSOperationQueue时,在主线程中单独使用 NSBlockOperation 执行(start)一个操作的情况下,操作是在当前线程执行的,并没有开启新线程,在其他线程中也一样;
  4. NSOperationQueue可以直接获取mainQueue,更新界面UI应该在mainQueue中进行;
    区别自定义封装NSOperation时,重写main或start方法的不同;
    自定义封装NSOperation时需要我们完全重载start,在start方法里面,我们还要查看isCanceled属性,确保start一个operation前,task是没有被取消的。如果我们自定义了dependency,我们还需要发送isReady的KVO通知。
死锁产生的条件
  1. sync
  2. 往当前串行队列添加任务


    死锁.png
    子线程保活.png
这种队列组只能在同步任务的情况下使用.png

队列组使用注意事项

线程同步

锁的对象必须是唯一的同一个,原理:会判断这个操作有没有被别的线程加锁,如果有就一直等待,否则就加锁

优先级反转就是低优先级的线程加锁之后,高优先级的线程一直处于忙等状态,系统把所有的时间都分给了高优先级的线程,所以没有时间处理低优先级的任务,造成低优先级的一直无法完成,高优先级的线程一直忙等
自旋锁的效率还是很高的,不过iOS10之后Apple不推荐使用,而是推荐os_unfair_lock

@interface SemaphoreDemo()

@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@end

@implementation SemaphoreDemo
//初始化
self.moneySemaphore = dispatch_semaphore_create(1);
//forever一直等待 信号量--1
dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
//信号量++1
dispatch_semaphore_signal(self.moneySemaphore);

@end

dispatch_semaphore_wait的声明为:

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

这个函数会使传入的信号量dsema的值减1;
这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,
不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。

//配置域名节点,因为项目的域名地址都要依赖这个,所以加一个信号量确保配置完成再往下走  
    dispatch_semaphore_t semap = dispatch_semaphore_create(0);
    [[SFIMLocationDomainNodeManager sharedInstance] locationDomainNodeFinish:^(BOOL locationSuccess) {
        if (!locationSuccess) {
            DDLogInfo(@"连接所有域名节点都失败了,默认选择内地节点");
        }
        dispatch_semaphore_signal(semap);
    }];
    dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
    dispatch_semaphore_wait(semap, t);

NSLock、NSRecursiveLock、NSCondition、NSConditionLock、串行队列

参考
多线程之NSOperation
https://juejin.im/post/5c14bf1af265da613f2f5e90
https://juejin.im/post/5bf21d935188251d9e0c2937(多线程锁)
https://juejin.im/post/5a9e57af6fb9a028df222555 这个详细
https://www.cnblogs.com/yajunLi/p/6274282.html 信号量的使用
dispatch_group

上一篇下一篇

猜你喜欢

热点阅读