美文网首页
iOS学习-多线程2

iOS学习-多线程2

作者: 快乐的tomato | 来源:发表于2021-09-13 19:02 被阅读0次

    一、NSThread

    NSThread是苹果官方提供面向对象操作线程的技术,简单方便,可以直接操作线程对象,不过需要自己控制线程的生命周期。在平时使用很少,最常用到的无非就是 [NSThread currentThread]获取当前线程。

    [NSThread currentThread]
    

    二、GCD

    1、是什么?

    全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”。
    纯c语言,提供了非常多、强大的函数

    2、为什么用

    • GCD是苹果公司为多核的并行运算提出的解决法案
    • GCD会自动利用更多的CPU内核,会自动管理线程的生命周期(创建线程、调度线程、销毁线程)
    • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

    3、任务和对列

    • 任务:执行什么操作
    • 队列:用来存放任务,决定了任务的执行方式
      串行对列:任务一个一个执行,单车道 DISPATCH_QUEUE_SERIAL
      并发对列:多个任务同时执行,多车道 DISPATCH_QUEUE_CONCURRENT
      主队列: 又叫全局串行对列 dispatch_get_main_queue(), 同步执行,系统为我们创建
      全局对列:全局对列就是并发对列 dispatch_get_global_queue(0, 0),系统为我们创建
    // 串行队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("取个名字,比如test", DISPATCH_QUEUE_SERIAL);
    // 并发队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("取个名字", DISPATCH_QUEUE_CONCURRENT);
    
    主队列的特点:
    先执行完主线程上的代码,才会执行主对列中的任务
    
    下面这段代码会崩掉,
    因为根据主队列的特点,先执行完主线程上的代码,才会执行主对列中的任务,
    当执行完i= 0的时候,加入了 dispatch_sync(dispatch_get_main_queue()),
    当i = 1的时候,会造成相互等待而不能动的情况。
    for (int i = 0; i<10; i++) {
           
           dispatch_sync(dispatch_get_main_queue(), ^{
               
               [self NSThreadTest];
           });
       }
    
    • 使用步骤:
      1、创建任务
      2、再将任务添加对列

    4、同步和异步

    同步:在当前线程,不能开启新线程
    异步:能开启新的线程

    • 决定了要不要开启新的线程

    5、各队列的执行效果

    e.png
    #import "ViewController.h"
    
    @interface HMViewController ()
    
    @end
    
    @implementation HMViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        [self performSelectorInBackground:@selector(test) withObject:nil];
        
    //    [self syncMainQueue];
    }
    
    - (void)test
    {
        NSLog(@"test --- %@", [NSThread currentThread]);
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"任务 --- %@", [NSThread currentThread]);
        });
    }
    
    /**
     * 使用dispatch_async异步函数, 在主线程中往主队列中添加任务
     */
    - (void)asyncMainQueue
    {
        // 1.获得主队列
        dispatch_queue_t queue = dispatch_get_main_queue();
        
        // 2.添加任务到队列中 执行
        dispatch_async(queue, ^{
            NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
        });
    }
    
    /**
     * 使用dispatch_sync同步函数, 在主线程中往主队列中添加任务 : 任务无法往下执行
     */
    - (void)syncMainQueue
    {
        // 1.获得主队列
        dispatch_queue_t queue = dispatch_get_main_queue();
        
        // 2.添加任务到队列中 执行
        dispatch_sync(queue, ^{
            NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
        });
    //    dispatch_sync(queue, ^{
    //        NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
    //    });
    //    dispatch_sync(queue, ^{
    //        NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
    //    });
        
        // 不会开启新的线程, 所有任务在主线程中执行
    }
    
    // 凡是函数名种带有create\copy\new\retain等字眼, 都需要在不需要使用这个数据的时候进行release
    // GCD的数据类型在ARC环境下不需要再做release
    // CF(Core Foundation)的数据类型在ARC环境下还是需要再做release
    
    /**
     * 用dispatch_sync同步函数往串行列中添加任务
     */
    - (void)syncSerialQueue
    {
        // 1.创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", NULL);
        
        // 2.添加任务到队列中 执行
        dispatch_sync(queue, ^{
            NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
        });
        
        // 3.释放资源
    //    dispatch_release(queue);   // MRC(非ARC)
        
        // 总结: 不会开启新的线程
    }
    
    /**
     * 用dispatch_sync同步函数往并发队列中添加任务
     */
    - (void)syncGlobalQueue
    {
        // 1.获得全局的并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        // 2.添加任务到队列中 执行
        dispatch_sync(queue, ^{
            NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
        });
        
        // 总结: 不会开启新的线程, 并发队列失去了并发的功能
    }
    
    /**
     * 用dispatch_async异步函数往串行队列中添加任务
     */
    - (void)asyncSerialQueue
    {
        // 1.创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", NULL);
        
        // 2.添加任务到队列中 执行
        dispatch_async(queue, ^{
            NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
        });
        
        // 总结: 只开1个线程执行任务
    }
    
    /**
     * 用dispatch_async异步函数往并发队列中添加任务
     */
    - (void)asyncGlobalQueue
    {
        // 1.获得全局的并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        // 2.添加任务到队列中 执行
        dispatch_async(queue, ^{
            NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
        });
        
        // 总结: 同时开启了3个线程
    }
    
    @end
    
    并发对列
    dispatch_queue_t queue = dispatch_queue_create("haha",DISPATCH_QUEUE_CONCURRENT);
    

    8、dispatch_barrier

    1、是什么?

    barrier: 障碍物, 屏障, 界线
    GCD中提供了Dispatch_barrier系统的API,俗称栅栏
    使用dispatch_barrier_sync()或者dispatch_barrier_async()入队的block,会等到所有的之前入队的block执行完成后才开始执行。
    除此之外,在barrier block后面入队的所有的block,会等到到barrier block本身已经执行完成之后才继续执行

    2、怎么用?

    主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新
    适合大规模的I/O操作
    当访问数据库或文件的时候,更新数据的时候补不能和其他更新或读取的操作在统一时间执行,可以用 dispatch_barrier_async

    首先说明:
    dispatch_barrier_async和dispatch_barrier_sync不能使用系统的全局并发队列 dispatch_get_global_queue(0, 0),否则不起作用,需要自定义的对列dispatch_queue_create("haha",DISPATCH_QUEUE_CONCURRENT);

    #pragma mark - dispatch_barrier_async + 自定义并发队列
    /*
     * 特点:
     * 1.barrier之前的任务并发执行,barrier之后的任务在barrier任务完成之后并发执行
     * 2.会开启新线程执行任务
     * 3.不会阻塞当前线程(主线程)
     */
    
    -(void)GCD_Barrier2{
        
        
        dispatch_queue_t queue = dispatch_queue_create("Barrier", DISPATCH_QUEUE_CONCURRENT);
    
        
        NSLog(@"******************开   始**  **************************");
        
        NSLog(@"追加任务1");
        dispatch_async(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"任务1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"追加任务2");
        dispatch_async(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"任务2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"追加barrier_async任务");
        dispatch_barrier_async(queue, ^{
            // 追加barrier任务
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"任务barrier---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"追加任务3");
        dispatch_async(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"任务3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"追加任务4");
        dispatch_async(queue, ^{
            // 追加任务4
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"任务4---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"******************结     束****************************");
    }
    
    

    由此我们可以看出:

    在barrier_async任务之前加入队列的任务,会在barrier任务之前并发执行,并且开辟了2条新线程去执行,barrier任务在任务1、任务2执行完成之后执行,执行完成之后,后续添加的任务才继续往下执行,并且dispatch_async并没有阻塞当前的主线程

    代码中的dispatch_barrier_async改为dispatch_barrier_sync方法去执行

    2021-09-13 15:37:05.585641+0800 OCStudy[38166:795918] ******************开   始**  **************************
    2021-09-13 15:37:05.586006+0800 OCStudy[38166:795918] 追加任务1
    2021-09-13 15:37:05.586381+0800 OCStudy[38166:795918] 追加任务2
    2021-09-13 15:37:05.586622+0800 OCStudy[38166:795918] 追加barrier_async任务
    2021-09-13 15:37:07.586992+0800 OCStudy[38166:796006] 任务2---<NSThread: 0x600001d71c40>{number = 6, name = (null)}
    2021-09-13 15:37:07.587019+0800 OCStudy[38166:796007] 任务1---<NSThread: 0x600001d63380>{number = 4, name = (null)}
    2021-09-13 15:37:09.592369+0800 OCStudy[38166:796006] 任务2---<NSThread: 0x600001d71c40>{number = 6, name = (null)}
    2021-09-13 15:37:09.592394+0800 OCStudy[38166:796007] 任务1---<NSThread: 0x600001d63380>{number = 4, name = (null)}
    2021-09-13 15:37:11.594419+0800 OCStudy[38166:795918] 任务barrier---<NSThread: 0x600001d28900>{number = 1, name = main}
    2021-09-13 15:37:13.595278+0800 OCStudy[38166:795918] 任务barrier---<NSThread: 0x600001d28900>{number = 1, name = main}
    2021-09-13 15:37:13.595760+0800 OCStudy[38166:795918] 追加任务3
    2021-09-13 15:37:13.596171+0800 OCStudy[38166:795918] 追加任务4
    2021-09-13 15:37:13.596515+0800 OCStudy[38166:795918] ******************结     束****************************
    2021-09-13 15:37:15.599730+0800 OCStudy[38166:796007] 任务3---<NSThread: 0x600001d63380>{number = 4, name = (null)}
    2021-09-13 15:37:15.599745+0800 OCStudy[38166:796006] 任务4---<NSThread: 0x600001d71c40>{number = 6, name = (null)}
    2021-09-13 15:37:17.603041+0800 OCStudy[38166:796007] 任务3---<NSThread: 0x600001d63380>{number = 4, name = (null)}
    2021-09-13 15:37:17.603041+0800 OCStudy[38166:796006] 任务4---<NSThread: 0x600001d71c40>{number = 6, name = (null)}
    
    

    由此我们可以看出:
    barrier任务会阻塞线程,只有barrier之前的任务执行完了,才会执行后面的任务。

    总结:

    1、dispatch_barrier的作用?

    主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新
    适合大规模的I/O操作
    当访问数据库或文件的时候,更新数据的时候补不能和其他更新或读取的操作在统一时间执行,可以用 dispatch_barrier_async

    2、dispatch_barrier_async和dispatch_barrier_sync的区别?

    barrier_async与barrier_sync的区别仅仅在于,barrier_sync在主线程执行,会阻塞它之后的任务的入队,必须等到barrier_sync任务执行完毕,才会把后面的异步任务添加到并发队列中,而barrier_async不需要等自身的block执行完成,就可以把后面的任务添加到队列中。

    3、dispatch_barrier_sync与死锁

    由于只有使用自定义并发队列时,dispatch_barrier方式添加的任务,才能起到栅栏的作用,添加到其它队列的情况下,dispatch_barrier_async/dispatch_barrier_sync与dispatch_async/dispatch_sync的作用是一样的,所以,当在串行队列中使用dispatch_barrier_sync时,同样的也有可能死锁,所以,我们在平常开发中要谨慎使用dispatch_barrier_sync

    4、dispatch_barrier_async实现多读单写

    场景:
    1、微信阅读里有10本书,10个人可以随意看这10本书
    2、现在要往网书库里增加3本,要一本一本的按顺序来加,加的时候,不能有人阅读这3本书,加完之后这10个人才能随意阅读这13本书
    代码如下:

    @property(nonatomic , strong) NSMutableArray *dataArr;属性
     self.dataArr = [NSMutableArray array];初始化10本书
        //书库原本有10本小说
        for (int i = 0; i < 10; ++i) {
           
            [self.dataArr addObject:[NSString stringWithFormat:@"小说%d",i]];
        }
    
    //dispatch_barrier_async实现多读单写
    -(void)GCD_Barrier3{
        
        
        //自定义全局对列
        dispatch_queue_t queue = dispatch_queue_create("Barrier", DISPATCH_QUEUE_CONCURRENT);
        
        //前10个人读10本小说
        dispatch_async(queue, ^{
        
            for (int i = 0; i < 10; ++i) {
                
                int suijishu1 = [self GettRandomNumber:0 to:9];
    //            NSLog(@"随机数%d,",suijishu1);
                NSLog(@"读者%d读%@,",i,self.dataArr[suijishu1]);
              
            }
        });
        
        //新增3本小说
        dispatch_barrier_sync(queue, ^{
           
            for (int i = 0; i < 3; ++i) {
                [self.dataArr addObject:[NSString stringWithFormat:@"小说新增==%d",i]];
                NSLog(@"新增后的小说=%@",self.dataArr);
            }
            
        });
        
        //后10个人读数据
        dispatch_async(queue, ^{
        
            for (int i = 0; i < 10; ++i) {
                
                int suijishu2 = [self GettRandomNumber:0 to:12];
    //            NSLog(@"随机数%d,",suijishu2);
                NSLog(@"读者%d读%@,",i,self.dataArr[suijishu2]);
            }
        });
        
    }
    

    打印结果

    2021-09-13 16:27:43.956943+0800 OCStudy[39358:828129] 读者0读小说9,
    2021-09-13 16:27:43.957223+0800 OCStudy[39358:828129] 读者1读小说6,
    2021-09-13 16:27:43.957436+0800 OCStudy[39358:828129] 读者2读小说1,
    2021-09-13 16:27:43.957626+0800 OCStudy[39358:828129] 读者3读小说8,
    2021-09-13 16:27:43.957867+0800 OCStudy[39358:828129] 读者4读小说3,
    2021-09-13 16:27:43.958067+0800 OCStudy[39358:828129] 读者5读小说8,
    2021-09-13 16:27:43.958286+0800 OCStudy[39358:828129] 读者6读小说8,
    2021-09-13 16:27:43.958601+0800 OCStudy[39358:828129] 读者7读小说6,
    2021-09-13 16:27:43.958814+0800 OCStudy[39358:828129] 读者8读小说7,
    2021-09-13 16:27:43.959118+0800 OCStudy[39358:828129] 读者9读小说2,
    2021-09-13 16:27:43.959784+0800 OCStudy[39358:828014] 新增后的小说=(
        "\U5c0f\U8bf40",
        "\U5c0f\U8bf41",
        "\U5c0f\U8bf42",
        "\U5c0f\U8bf43",
        "\U5c0f\U8bf44",
        "\U5c0f\U8bf45",
        "\U5c0f\U8bf46",
        "\U5c0f\U8bf47",
        "\U5c0f\U8bf48",
        "\U5c0f\U8bf49",
        "\U5c0f\U8bf4\U65b0\U589e==0"
    )
    2021-09-13 16:27:43.960261+0800 OCStudy[39358:828014] 新增后的小说=(
        "\U5c0f\U8bf40",
        "\U5c0f\U8bf41",
        "\U5c0f\U8bf42",
        "\U5c0f\U8bf43",
        "\U5c0f\U8bf44",
        "\U5c0f\U8bf45",
        "\U5c0f\U8bf46",
        "\U5c0f\U8bf47",
        "\U5c0f\U8bf48",
        "\U5c0f\U8bf49",
        "\U5c0f\U8bf4\U65b0\U589e==0",
        "\U5c0f\U8bf4\U65b0\U589e==1"
    )
    2021-09-13 16:27:43.966920+0800 OCStudy[39358:828014] 新增后的小说=(
        "\U5c0f\U8bf40",
        "\U5c0f\U8bf41",
        "\U5c0f\U8bf42",
        "\U5c0f\U8bf43",
        "\U5c0f\U8bf44",
        "\U5c0f\U8bf45",
        "\U5c0f\U8bf46",
        "\U5c0f\U8bf47",
        "\U5c0f\U8bf48",
        "\U5c0f\U8bf49",
        "\U5c0f\U8bf4\U65b0\U589e==0",
        "\U5c0f\U8bf4\U65b0\U589e==1",
        "\U5c0f\U8bf4\U65b0\U589e==2"
    )
    2021-09-13 16:27:43.967910+0800 OCStudy[39358:828129] 读者0读小说8,
    2021-09-13 16:27:43.968226+0800 OCStudy[39358:828129] 读者1读小说新增==1,
    2021-09-13 16:27:43.968474+0800 OCStudy[39358:828129] 读者2读小说1,
    2021-09-13 16:27:43.969052+0800 OCStudy[39358:828129] 读者3读小说6,
    2021-09-13 16:27:43.969494+0800 OCStudy[39358:828129] 读者4读小说新增==2,
    2021-09-13 16:27:43.970253+0800 OCStudy[39358:828129] 读者5读小说2,
    2021-09-13 16:27:43.971260+0800 OCStudy[39358:828129] 读者6读小说新增==0,
    2021-09-13 16:27:43.972066+0800 OCStudy[39358:828129] 读者7读小说5,
    2021-09-13 16:27:43.972871+0800 OCStudy[39358:828129] 读者8读小说7,
    2021-09-13 16:27:43.973618+0800 OCStudy[39358:828129] 读者9读小说4,
    
    

    相关文章

      网友评论

          本文标题:iOS学习-多线程2

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