美文网首页
ios 中的多线程

ios 中的多线程

作者: 文小猿666 | 来源:发表于2021-03-01 16:29 被阅读0次

本节主要理解:
1.GCD中任务和队列的理解
2.GCD中不同组合方式的注意点(同步串行/异步串行/同步并发/异步并发/同步 + 主队列(死锁)等)
3.线程间通讯
4.GCD的其他使用方法(栅栏函数/线程组/信号量/延时执行/只执行一次等)

多线程:在同一时刻,一个CPU只能处理1条线程,但CPU可以在多条线程之间快速的切换,只要切换的足够快,就造成了多线程一同执行的假象

本文主要用来总结多线程中一些常见的易错易忘点,主要侧重在GCD
为什么推荐使用GCD呢,具体如下:

  • GCD 可用于多核的并行运算;
  • GCD 会自动利用更多的 CPU 内核(比如双核、四核);
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
一.死锁

两个必要条件
1.sync (同步任务)
2.往当前的串行队列添加任务

一个典型的串行队列

dispatch_async(dispatch_get_main_queue(), ^{

});

例1

    // 问题:以下代码是在主线程执行的,会不会产生死锁?会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"执行任务2");
    });
    
    NSLog(@"执行任务3");

例2

    // 问题:以下代码是在主线程执行的,会不会产生死锁?会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"执行任务3");
        });
    
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");

例3

    // 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"执行任务3");
        });
        
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");

例4

    // 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
//    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue2, ^{ // 1
            NSLog(@"执行任务3");
        });
        
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");
二.子线程runloop的使用

子线程中runloop需要手动激活
NSTimer依附于runloop生效

一个典型的并发队列

全局并发队列
dispatch_get_global_queue(0,0)
调用test2方法如何打印(1-3-2)
- (void)test2
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
        // 这句代码的本质是往Runloop中添加定时器
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"3");
        //手动激活子线程的runloop,执行子线程runloop中的定时器,如果不手动激活,则不会执行test方法
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}
- (void)test
{
    NSLog(@"2");
}
三.线程安全

解决方案:线程同步(依赖关系)与线程安全(数据读写)
常见线程同步方法:加锁

图片.png

根据性能与使用方便性推荐使用 dispath_semaphore(信号量)与 pthread_mutex

//信号量的初始值
int value = 1;
//初始化信号量
dispatch_ semaphore_ .t semaphore = dispatch_ semaphore_ create(value) ;
//如果信号量的值<=0,当前线程就会进入休眠等待(直到信号量的值>0)
//如果信号量的值>0,就减1,然后往下执行后面的代码
dispatch_ semaphore_ wait ( semaphore, DISPATCH_ .TIME_ FOREVER);
//让信号量的值加1
dispatch_ semaphore_ _signal (semaphore);

/**
 * 线程安全:使用 semaphore 加锁
 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
 */
- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    semaphoreLock = dispatch_semaphore_create(1);
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

/**
 * 售卖火车票(线程安全)
 */
- (void)saleTicketSafe {
    while (1) {
        // 相当于加锁
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}
输出结果为:
2019-08-08 15:23:58.819891+0800 YSC-GCD-demo[18116:4348091] currentThread---<NSThread: 0x600000681380>{number = 1, name = main}
2019-08-08 15:23:58.820041+0800 YSC-GCD-demo[18116:4348091] semaphore---begin
2019-08-08 15:23:58.820305+0800 YSC-GCD-demo[18116:4348159] 剩余票数:49 窗口:<NSThread: 0x6000006ede80>{number = 3, name = (null)}
2019-08-08 15:23:59.022165+0800 YSC-GCD-demo[18116:4348157] 剩余票数:48 窗口:<NSThread: 0x6000006e4b40>{number = 4, name = (null)}
2019-08-08 15:23:59.225299+0800 YSC-GCD-demo[18116:4348159] 剩余票数:47 窗口:<NSThread: 0x6000006ede80>{number = 3, name = (null)}
...
2019-08-08 15:24:08.355977+0800 YSC-GCD-demo[18116:4348157] 剩余票数:2 窗口:<NSThread: 0x6000006e4b40>{number = 4, name = (null)}
2019-08-08 15:24:08.559201+0800 YSC-GCD-demo[18116:4348159] 剩余票数:1 窗口:<NSThread: 0x6000006ede80>{number = 3, name = (null)}
2019-08-08 15:24:08.759630+0800 YSC-GCD-demo[18116:4348157] 剩余票数:0 窗口:<NSThread: 0x6000006e4b40>{number = 4, name = (null)}
2019-08-08 15:24:08.965100+0800 YSC-GCD-demo[18116:4348159] 所有火车票均已售完
2019-08-08 15:24:08.965440+0800 YSC-GCD-demo[18116:4348157] 所有火车票均已售完
四.线程间通讯

以下三种方法通过线程的依赖关系实现线程同步:

1.组队列(dispatch_group_t)

2.阻塞任务(dispatch_barrier_(a)sync)

3.信号量机制(dispatch_semaphore)

4.子线程执行耗时操作,主队线程刷新UI ,使用dispatch_get_main_queue回到主线程

五.NSOperation与GCD的区别

1.GCD的核心是C语言写的系统服务,执行和操作简单高效,因此NSOperation底层也通过GCD实现,换个说法就是NSOperation是对GCD更高层次的抽象,这是他们之间最本质的区别.因此如果希望自定义任务,建议使用NSOperation;

2.依赖关系,NSOperation可以设置两个NSOperation之间的依赖,第二个任务依赖于第一个任务完成执行,GCD无法设置依赖关系,不过可以通过dispatch_barrier_async来实现这种效果;

3.KVO(键值对观察),NSOperation和容易判断Operation当前的状态(是否执行,是否取消),对此GCD无法通过KVO进行判断;

4.优先级,NSOperation可以设置自身的优先级,但是优先级高的不一定先执行,GCD只能设置队列的优先级,无法在执行的block设置优先级;

5.继承,NSOperation是一个抽象类实际开发中常用的两个类是NSInvocationOperation和NSBlockOperation,同样我们可以自定义NSOperation,GCD执行任务可以自由组装,没有继承那么高的代码复用度;

6.效率,直接使用GCD效率确实会更高效,NSOperation会多一点开销,但是通过NSOperation可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己;

六.多线程常见面试题

1.你理解的多线程 ?

2.iOS的多线程方案有哪几种 ?你更倾向于哪一种?

3.你在项目中用过 GCD吗?

4.GCD 的队列类型

5.说一下 OperationQueue和GCD的区别,以及各自的优势

6.线程安全的处理手段有哪些?

7.OC你了解的锁有 哪些?在你回答基础上进行二次提问;
-追问一:自旋和互斥对比?
-追问二:使用以上锁需要注意哪些?
-追问三:用C/OC/C++ ,任选其一,实现自旋或互斥?口述即可!

参考:
iOS 多线程:『GCD』详尽总结
# iOS开发-NSOperation与GCD区别
关于iOS多线程,你看我就够了
多线程之GCD 线程间的通信

相关文章

  • iOS中的多线程

    iOS中的多线程 现存的iOS多线程解决方案 现在在iOS中要实现多线程有如下四种方法。 PthreadsNSTh...

  • 多线程系列

    --------------------多线程-------------------- 你理解的多线程? iOS中...

  • iOS 多线程

    参考资料:iOS多线程iOS GCD 多线程问题在iOS中目前有4套多线程方案,他们分别是: PthreadsNS...

  • iOS开发进阶-多线程技术

    iOS中多线程 首先看一道面试题 iOS中多线程有哪些实现方案? iOS中,多线程一般有三种方案GCD、NSOpe...

  • iOS-多线程知识点整理

    iOS中多线程 首先看一道面试题 iOS中多线程有哪些实现方案? iOS中,多线程一般有三种方案GCD、NSOpe...

  • iOS多线程--并行开发一

    iOS多线程--并行开发二 重点分析iOS多线程开发:iOS多线程:在iOS中每个进程启动后都会建立一个主线程(U...

  • IOS多线程二 NSThread简约而不简单

    IOS多线程二NSThread简约而不简单 今天就来着手教大家在IOS中简单的实现多线程。IOS实现多线程的方式有...

  • OC--各种线程锁

    参考:正确使用多线程同步锁@synchronized()iOS中的锁iOS多线程安全详解iOS 常见知识点(三):...

  • iOS开发知识点总结(一)

    1.iOS中的多线程 iOS中的多线程,是Cocoa框架下的多线程,通过Cocoa的封装,可以让我们更方便的使用线...

  • iOS开发多线程篇-NSThread

    上篇我们学习了iOS多线程解决方式中的NSOperation,这篇我主要概况总结iOS多线程中NSThread的解...

网友评论

      本文标题:ios 中的多线程

      本文链接:https://www.haomeiwen.com/subject/sbyvfltx.html