美文网首页iOS
iOS 多线程知识相关

iOS 多线程知识相关

作者: 错肩期盼 | 来源:发表于2019-01-29 10:28 被阅读31次

    前言

    主要是整理下关于iOS中多线程的相关知识点,加强记忆

    • 目前iOS有四种多线程

    1.Pthreads
    2.NSThread
    3.GCD
    4.NSOperation

    Pthreads

    这个其实很少用,(我基本没用过),查资料后的解释是一个线程标准,提供了一整套API,在多平台可移植性强。

    • 具体使用
    //pthread创建线程
    - (void)createPthread{
        /**
         参数:
         1> 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *
         2> 用来设置线程属性
         3> 线程运行函数的起始地址
         4> 运行函数的参数
         
         返回值:
         - 若线程创建成功,则返回0
         - 若线程创建失败,则返回出错编号
         */
        pthread_t thread =NULL;
        NSString *str = @"pthread";
        int result = pthread_create(&thread, NULL, demo, (__bridge void *)(str));
        
        if (result == 0) {
            NSLog(@"创建线程 OK");
        } else {
            NSLog(@"创建线程失败 %d", result);
        }
        // pthread_detach:设置子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
        pthread_detach(thread);
    
    }
    
    // 后台线程调用函数
    void *demo(void *params) {
        NSString *str = (__bridge NSString *)(params);
        NSLog(@"%@ - %@", [NSThread currentThread], str);
        pthread_exit((void*)0);
    }
    

    能看到都是C语言,用起来很难受。。。我基本用不到,不过按常理来说越难用的越高深,我这里就不谈论这么高深的东西了

    NSThread

    这个方案是苹果封装后面向对象的,可以直接操作对象,但是还是需要我们手动管理线程生命周期,常用的方法有[NSThread currentThread],获取当前线程。

    • 具体使用
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(thread:) object:nil];
        //开始线程
        [thread start];
        
        //or创建并自动启动
        [NSThread detachNewThreadSelector:@selector(thread:) toTarget:self withObject:nil];
        //or
        [self  performSelectorInBackground:@selector(thread:) withObject:nil];
    

    GCD

    这个方案又叫Grand Central Dispatch,对,你想的没错,名字越长,就越强大,它是苹果为了多核并行运算提出的解决方案,会合理的利用更多的CPU,最重要的是它会自动管理线程的,完全不需要我们管理,它也是使用的C语言,但是运用了大量的block方式,使用起来很方便,灵活,所以iOS当中绝大部分都是使用的GCD。

    任务和队列

    说起GCD就不得不说两个概念,任务队列

    • 任务:就是blcok里面的操作,就是一段需要执行的代码,任务又分为两种执行方式,同步执行异步执行

    同步执行会阻塞当前线程直到block中的任务执行完毕。
    异步线程则不会。

    • 队列:用于 存放任务,一共有两种,串行队列并行队列

    串行队列 中的任务会根据队列的定义 FIFO 的执行,一个接一个的先进先出的进行执行。
    并行队列 中的任务也会根据 FIFO 的执行,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

    如表
    同步执行 异步执行
    串行队列 当前线程,一个一个执行 其他线程,一个一个执行
    并行队列 当前线程,一个一个执行 开很多线程,一起执行

    主队列

    • 主队列 Main Queue: 是一个特殊的串行队列。用于更新UI,所以一般耗时的功能都要放在子线程中,以免造成界面卡顿。
    dispatch_queue_t queue = dispatch_get_main_queue();
    

    注意

    在主队列下的任务不管是异步任务还是同步任务都不会开辟线程,任务只会在主线程顺序执行。

    验证
    //主线程中的异步任务
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i< 10; i++) {
                NSLog(@"%@", [NSThread currentThread]);
                NSLog(@"%d", i);
            }
        });
    

    打印

    2018-10-12 15:02:22.840104+0800 GCD[2305:313422] <NSThread: 0x600001596940>{number = 1, name = main}
    2018-10-12 15:02:22.840259+0800 GCD[2305:313422] 0
    2018-10-12 15:02:22.840389+0800 GCD[2305:313422] <NSThread: 0x600001596940>{number = 1, name = main}
    2018-10-12 15:02:22.840494+0800 GCD[2305:313422] 1
    2018-10-12 15:02:22.840605+0800 GCD[2305:313422] <NSThread: 0x600001596940>{number = 1, name = main}
    2018-10-12 15:02:22.840697+0800 GCD[2305:313422] 2
    2018-10-12 15:02:22.840804+0800 GCD[2305:313422] <NSThread: 0x600001596940>{number = 1, name = main}
    2018-10-12 15:02:22.840906+0800 GCD[2305:313422] 3
    2018-10-12 15:02:22.841031+0800 GCD[2305:313422] <NSThread: 0x600001596940>{number = 1, name = main}
    2018-10-12 15:02:22.841124+0800 GCD[2305:313422] 4
    2018-10-12 15:02:22.841229+0800 GCD[2305:313422] <NSThread: 0x600001596940>{number = 1, name = main}
    2018-10-12 15:02:22.841452+0800 GCD[2305:313422] 5
    2018-10-12 15:02:22.841751+0800 GCD[2305:313422] <NSThread: 0x600001596940>{number = 1, name = main}
    2018-10-12 15:02:22.842015+0800 GCD[2305:313422] 6
    2018-10-12 15:02:22.842294+0800 GCD[2305:313422] <NSThread: 0x600001596940>{number = 1, name = main}
    2018-10-12 15:02:23.166180+0800 GCD[2305:313422] 7
    2018-10-12 15:02:23.166520+0800 GCD[2305:313422] <NSThread: 0x600001596940>{number = 1, name = main}
    2018-10-12 15:02:23.166708+0800 GCD[2305:313422] 8
    2018-10-12 15:02:23.166973+0800 GCD[2305:313422] <NSThread: 0x600001596940>{number = 1, name = main}
    2018-10-12 15:02:23.167109+0800 GCD[2305:313422] 9
    

    可以看出异步任务在主线程并没有开辟新的线程!
    然后我们在创建一个自己的串行队列

        dispatch_queue_t queue = dispatch_queue_create("GCDQueue", DISPATCH_QUEUE_SERIAL);
    
        dispatch_async(queue, ^{
            for (int i = 0; i< 10; i++) {
                NSLog(@"%@", [NSThread currentThread]);
                NSLog(@"%d", i);
            }
        });
    

    打印

    2018-10-12 15:16:18.489051+0800 GCD[2472:346381] <NSThread: 0x60000113fdc0>{number = 3, name = (null)}
    2018-10-12 15:16:18.489204+0800 GCD[2472:346381] 0
    2018-10-12 15:16:18.489409+0800 GCD[2472:346381] <NSThread: 0x60000113fdc0>{number = 3, name = (null)}
    2018-10-12 15:16:18.489503+0800 GCD[2472:346381] 1
    2018-10-12 15:16:18.489611+0800 GCD[2472:346381] <NSThread: 0x60000113fdc0>{number = 3, name = (null)}
    2018-10-12 15:16:18.489701+0800 GCD[2472:346381] 2
    2018-10-12 15:16:18.489814+0800 GCD[2472:346381] <NSThread: 0x60000113fdc0>{number = 3, name = (null)}
    2018-10-12 15:16:18.489908+0800 GCD[2472:346381] 3
    

    开辟了新线程

    主队列异步任务:现将任务放在主队列中,但是不是马上执行,等到主队列中的其它所有除我们使用代码添加到主队列的任务的任务都执行完毕之后才会执行我们使用代码添加的任务。

    主队列同步任务:容易阻塞主线程,所以不要这样写。原因:我们自己代码任务需要马上执行,但是主线程正在执行代码任务的方法体,因此代码任务就必须等待,而主线程又是串行队列在等待代码任务的完成好去完成下面的任务,因此就形成了相互等待。整个主线程就被阻塞了。

    下面的代码就造成了死锁

        dispatch_sync(dispatch_get_main_queue(), ^{
            for (int i = 0; i< 10; i++) {
                NSLog(@"%@", [NSThread currentThread]);
                NSLog(@"%d", i);
            }
        });
    
    • 自己创建的队列 Queue
      //串行队列
      dispatch_queue_t queue = dispatch_queue_create("GCDQueue", NULL);
      dispatch_queue_t queue = dispatch_queue_create("GCDQueue", DISPATCH_QUEUE_SERIAL);
      //并行队列
      dispatch_queue_t queue = dispatch_queue_create("GCDQueue", DISPATCH_QUEUE_CONCURRENT);
    
    

    其中,第一个参数是标识符,唯一标识。第二个参数为创建的队列是串行还是并行队列
    传入 DISPATCH_QUEUE_SERIALNULL 表示创建串行队列
    传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。

    • 全局并行队列 Global Queue
      全局队列:本质是一个并发队列,由系统提供,方便编程,可以不用创建就直接使用。
      获取全局队列的方法:
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    全局队列和并发队列的区别:
    
    1,全局队列没有名字,但是并发队列有名字。有名字可以便于查看系统日志
    
    2,全局队列是所有应用程序共享的。
    
    3,在mrc的时候,全局队列不用手动释放,但是并发队列需要。
    

    同步任务 会堵塞当前线程

      dispatch_sync(<#queue#>, ^{
          NSLog(@"%@", [NSThread currentThread]);
      });
    

    异步任务 不会堵塞当前线程

      dispatch_async(<#queue#>, ^{
          NSLog(@"%@", [NSThread currentThread]);
      });
    

    那么线程锁死又是什么呢?

        dispatch_sync(dispatch_get_main_queue(), ^{
            for (int i = 0; i< 10; i++) {
                NSLog(@"mainQueue--%@", [NSThread currentThread]);
                NSLog(@"mainQueue--%d", i);
            }
        });
    

    这样的代码就会程序就会卡死崩溃

    解释:

    同步任务会阻塞当前线程,然后把 Block 中的任务放到指定的队列中执行,只有等到 Block 中的任务完成后才会让当前线程继续往下运行。dispatch_sync 立即阻塞当前的主线程,然后把 Block 中的任务放到 main_queue 中,可是 main_queue 中的任务会被取出来放到主线程中执行,但主线程这个时候已经被阻塞了,所以 Block 中的任务就不能完成,它不完成,dispatch_sync 就会一直阻塞主线程,这就是死锁现象。导致主线程一直卡死。

    下面这段代码会怎么样打印?

        dispatch_queue_t queue = dispatch_queue_create("GCDQueue", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            NSLog(@"之前%@", [NSThread currentThread]);
            dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"同步任务%@", [NSThread currentThread]);
            });
            NSLog(@"之后%@", [NSThread currentThread]);
        });
    

    2019-01-29 09:57:07.783071+0800 MVVM+RAC[35936:2890866] 之前-<NSThread: 0x6000020a0480>{number = 1, name = main}
    2019-01-29 09:57:10.601277+0800 MVVM+RAC[35936:2891028] 之前async-<NSThread: 0x6000020dc300>{number = 3, name = (null)}
    2019-01-29 09:57:10.601277+0800 MVVM+RAC[35936:2890866] 之后-<NSThread: 0x6000020a0480>{number = 1, name = main}

    结果发现只有“之前xxx”打印了

    分析:
    我们按执行顺序一步步来哦:

    1. 使用 DISPATCH_QUEUE_SERIAL 这个参数,创建了一个 串行队列
    2. 打印出 之前 - %@ 这句。
    3. dispatch_async 异步执行,所以当前线程不会被阻塞,于是有了两条线程,一条当前线程继续往下打印出 之后 - %@这句, 另一台执行 Block 中的内容打印 sync之前 - %@ 这句。因为这两条是并行的,所以打印的先后顺序无所谓。
    4. 注意,高潮来了。现在的情况和上一个例子一样了。dispatch_sync同步执行,于是它所在的线程会被阻塞,一直等到 sync 里的任务执行完才会继续往下。于是 sync 就高兴的把自己 Block 中的任务放到 queue 中,可谁想 queue 是一个串行队列,一次执行一个任务,所以 sync 的 Block 必须等到前一个任务执行完毕,可万万没想到的是 queue 正在执行的任务就是被 sync 阻塞了的那个。于是又发生了死锁。所以 sync 所在的线程被卡死了。剩下的两句代码自然不会打印。

    NSOperation和NSOperationQueue

    NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。操作步骤也很好理解:

    将要执行的任务封装到一个 NSOperation对象中。
    将此任务添加到一个 NSOperationQueue 对象中。

    然后系统就会自动在执行任务。至于同步还是异步、串行还是并行请继续往下看:

    添加任务

    值得说明的是,NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperationNSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel方法即可。

    • NSInvocationOperation
      //1.创建NSInvocationOperation对象
      NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    
      //2.开始执行
      [operation start];
    
    • NSBlockOperation
    //1.创建NSBlockOperation对象
      NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
          NSLog(@"%@", [NSThread currentThread]);
      }];
    
      //2.开始任务
      [operation start];
    

    之前说过这样的任务,默认会在当前线程执行。但是 NSBlockOperation 还有一个方法:addExecutionBlock: ,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务 会并发执行,它会 在主线程和其它的多个线程 执行这些任务,注意下面的打印结果:

        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@", [NSThread currentThread]);
        }];
        
        //添加多个Block
        for (NSInteger i = 0; i < 5; i++) {
            [operation addExecutionBlock:^{
                NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
            }];
        }
        
        //2.开始任务
        [operation start];
    

    2019-01-29 10:04:57.675390+0800 MVVM+RAC[36032:2923254] [framework] CUIThemeStore: No theme registered with id=0
    2019-01-29 10:04:57.753524+0800 MVVM+RAC[36032:2923465] <NSThread: 0x600000507000>{number = 4, name = (null)}
    2019-01-29 10:04:57.753525+0800 MVVM+RAC[36032:2923466] 第1次:<NSThread: 0x600000538840>{number = 5, name = (null)}
    2019-01-29 10:04:57.753525+0800 MVVM+RAC[36032:2923464] 第2次:<NSThread: 0x600000522fc0>{number = 3, name = (null)}
    2019-01-29 10:04:57.753529+0800 MVVM+RAC[36032:2923459] 第4次:<NSThread: 0x600000538680>{number = 7, name = (null)}
    2019-01-29 10:04:57.753529+0800 MVVM+RAC[36032:2923254] 第0次:<NSThread: 0x600000542940>{number = 1, name = main}
    2019-01-29 10:04:57.753545+0800 MVVM+RAC[36032:2923472] 第3次:<NSThread: 0x600000525480>{number = 6, name = (null)}

    注意⚠️addExecutionBlock 方法必须在 start() 方法之前执行,否则就会报错

    创建队列

    看过上面的内容就知道,我们可以调用一个 NSOperation 对象的start() 方法来启动这个任务,但是这样做他们默认是 同步执行 的。就算是 addExecutionBlock 方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。这是就要用到队列 NSOperationQueue 了。而且,按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法

    • 主队列
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    
    • 其他队列
    /1.创建一个其他队列    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //2.创建NSBlockOperation对象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
    
    //3.添加多个Block
    for (NSInteger i = 0; i < 5; i++) {
        [operation addExecutionBlock:^{
            NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
        }];
    }
    
    //4.队列添加任务
    [queue addOperation:operation];
    

    NSOperationQueue 没有串行和并行队列的方法 但是有一个参数 maxConcurrentOperationCount 最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,你就发现他变成串行队列

        //1.创建一个其他队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        //2.创建NSBlockOperation对象
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"1--%@", [NSThread currentThread]);
        }];
        
        //3.添加多个Block
        for (NSInteger i = 0; i < 5; i++) {
            [operation addExecutionBlock:^{
                NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
            }];
        }
        
        
        NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"2--%@", [NSThread currentThread]);
        }];
        
        NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"3--%@", [NSThread currentThread]);
        }];
        
        NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"4--%@", [NSThread currentThread]);
        }];
        
        queue.maxConcurrentOperationCount = 1;
        //4.队列添加任务
        [queue addOperations:@[operation,operation2,operation3,operation4] waitUntilFinished:NO];
    

    2019-01-29 10:21:04.874686+0800 MVVM+RAC[36311:2981964] [framework] CUIThemeStore: No theme registered with id=0
    2019-01-29 10:21:04.966160+0800 MVVM+RAC[36311:2982072] 第0次:<NSThread: 0x600002681a40>{number = 3, name = (null)}
    2019-01-29 10:21:04.966169+0800 MVVM+RAC[36311:2982141] 第4次:<NSThread: 0x600002684740>{number = 8, name = (null)}
    2019-01-29 10:21:04.966179+0800 MVVM+RAC[36311:2982142] 第3次:<NSThread: 0x600002681b00>{number = 6, name = (null)}
    2019-01-29 10:21:04.966182+0800 MVVM+RAC[36311:2982076] 第1次:<NSThread: 0x600002681a80>{number = 5, name = (null)}
    2019-01-29 10:21:04.966214+0800 MVVM+RAC[36311:2982078] 第2次:<NSThread: 0x600002698e40>{number = 7, name = (null)}
    2019-01-29 10:21:04.966214+0800 MVVM+RAC[36311:2982077] 1--<NSThread: 0x600002698c80>{number = 4, name = (null)}
    2019-01-29 10:21:04.966690+0800 MVVM+RAC[36311:2982077] 2--<NSThread: 0x600002698c80>{number = 4, name = (null)}
    2019-01-29 10:21:04.967049+0800 MVVM+RAC[36311:2982078] 3--<NSThread: 0x600002698e40>{number = 7, name = (null)}
    2019-01-29 10:21:04.967231+0800 MVVM+RAC[36311:2982078] 4--<NSThread: 0x600002698e40>2019-01-29 10:21:04.971641+0800 MVVM+

    • 依赖关系
    //1.任务一:下载图片
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载图片 - %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    
    //2.任务二:打水印
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"打水印   - %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    
    //3.任务三:上传图片
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"上传图片 - %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    
    //4.设置依赖
    [operation2 addDependency:operation1];      //任务二依赖任务一
    [operation3 addDependency:operation2];      //任务三依赖任务二
    
    //5.创建队列并加入任务
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
    

    注意

    • 不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。
    • 可以使用 removeDependency 来解除依赖关系。
    • 可以在不同的队列之间依赖,反正就是这个依赖是添加到任务身上的,和队列没关系。

    其他相关

    互斥锁

    @synchronized(self) {
      //需要执行的代码块
    }
    

    延迟

    // 3秒后自动调用self的run:方法,并且传递参数:@"run"
    [self performSelector:@selector(run:) withObject:@"run" afterDelay:3];
    

    GCD

    // 创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 设置延时,单位秒
    double delay = 3; 
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
      // 3秒后需要执行的任务
    });
    

    NSTimer

    [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];
    
    

    单例模式

    @interface ZBSharedTest : NSObject <NSCopying>
    
    + (instancetype)sharedTest;
    
    @end
    
    @implementation ZBSharedTest
    
    static id _instance;
    
    + (instancetype)sharedTest {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _instance = [[ZBSharedTest alloc] init];
        });
    
        return _instance;
    }
    
    @end
    

    相关文章

      网友评论

        本文标题:iOS 多线程知识相关

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