iOS多线程以及在项目中的使用

  • pThread几乎不用,不用管
  • NSThread

NSThread是对pThread的封装
优点:
1.实时性更高
2.与RunLoop结合,提供更为灵活高效的线程管理方式
缺点:
1.创建线程代时,需要同时占用应用和内核的内2.存空间(GCD只占用内核的内存空间)
编写线程相关代码相对繁杂

线程的创建方式

    [NSThread detachNewThreadWithBlock:^{
        NSLog(@"NSThread");
    }];
    // 2.
    [NSThread detachNewThreadSelector:@selector(dosomething:) toTarget:self withObject:data];
    // 3.
    NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(dosomething:) object:data];
    thread.name = @"thread1";
    [thread start];
  • GCD

项目中使用dispatch_async处理证件信息修改对OCR识别到大图片压缩耗时操作,压缩完成后去请求接口,JJ的做任务下载用了dispatch_group_t,还有JJ的检测最优线路也用了dispatch_group_t等

dispatch_sync同步
dispatch_async异步任务派发
dispatch_queue_t串行队列与并发队列
dispatch_once_t只执行一次
dispatch_after延后执行
dispatch_group_t组调度
dispatch_barrier_(a)sync栅栏
dispatch_semaphore信号量

dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex(自旋锁) 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。对磁盘缓存来说,它比较合适。自旋锁更适合内存缓存,虽然占用一些CPU,但是快

//可设置最大并发数,目前最大并发数为5,设置最大并发数还是建议用NSOperation
dispatch_semaphore_t sem = dispatch_semaphore_create(5);
//最大并发为1时也可以当锁使用
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
执行耗时操作};
dispatch_async(dispatch_get_main_queue(), ^{
回到主线程进行UI刷新操作
};

GCD中dispatch_queue大致可以分为三类
全局的并行的queue

主线程的串行的queue
自定义的queue
全局的queue和主线程的queue结合使用(上边提到的)就是我们平常最常用的一种用法,在异步线程中执行耗时操作,然后在UI线程执行刷新操作。
全局的queue我们可以通过dispatch_get_global_queue(0, 0)直接获取,这里有两个参数,第一个表示线程执行的优先级(第二个参数是预留参数暂时没用)
第一个参数有以下几个状态
DISPATCH_QUEUE_PRIORITY_HIGH 2
-》DISPATCH_QUEUE_PRIORITY_DEFAULT 0
-》DISPATCH_QUEUE_PRIORITY_LOW (-2)
-》DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN,具体打印结果看下面内容
下面我们设置第一个参数默认值为0

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 1");
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 2");
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 3");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 3");
    });

输出结果
2021-03-16 15:49:52.344146+0800 TestDemo[54759:1269344] start task 2
2021-03-16 15:49:52.344151+0800 TestDemo[54759:1269347] start task 3
2021-03-16 15:49:52.344146+0800 TestDemo[54759:1269346] start task 1

2021-03-16 15:49:55.348151+0800 TestDemo[54759:1269346] end task 1
2021-03-16 15:49:55.348151+0800 TestDemo[54759:1269347] end task 3
2021-03-16 15:49:55.348218+0800 TestDemo[54759:1269344] end task 2

下面设置优先级

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSLog(@"start task 0");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 0");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 1");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 2");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"start task 3");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 3");
    });
021-03-16 16:04:20.881211+0800 TestDemo[54815:1279607] start task 2
2021-03-16 16:04:20.881211+0800 TestDemo[54815:1279609] start task 3
2021-03-16 16:04:20.922642+0800 TestDemo[54815:1279608] start task 1
2021-03-16 16:04:21.037538+0800 TestDemo[54815:1279606] start task 0
2021-03-16 16:04:23.882068+0800 TestDemo[54815:1279609] end task 3
2021-03-16 16:04:23.882066+0800 TestDemo[54815:1279607] end task 2
2021-03-16 16:04:24.057318+0800 TestDemo[54815:1279608] end task 1
2021-03-16 16:04:24.133597+0800 TestDemo[54815:1279606] end task 0

串行队列和并发队列
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr),这个方法同样也有两个参数,第一个参数是确定唯一queue的一个标识,第二个参数创建queue的类型
DISPATCH_QUEUE_SERIAL(串行)
DISPATCH_QUEUE_CONCURRENT(并发)

dispatch_queue_t myQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL(串行)/DISPATCH_QUEUE_CONCURRENT(并发));
    dispatch_async(myQueue, ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 1");
    });
    dispatch_async(myQueue, ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 2");
    });
    dispatch_async(myQueue, ^{
        NSLog(@"start task 3");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 3");
    });

串行队列输出结果
2021-03-16 16:28:15.643198+0800 TestDemo[55024:1292810] start task 1
2021-03-16 16:28:18.643548+0800 TestDemo[55024:1292810] end task 1
2021-03-16 16:28:18.643854+0800 TestDemo[55024:1292810] start task 2
2021-03-16 16:28:21.648152+0800 TestDemo[55024:1292810] end task 2
2021-03-16 16:28:21.648631+0800 TestDemo[55024:1292810] start task 3
2021-03-16 16:28:24.653826+0800 TestDemo[55024:1292810] end task 3

并发队列输出结果
2021-03-16 16:33:54.921235+0800 TestDemo[55071:1297272] start task 2
2021-03-16 16:33:54.921235+0800 TestDemo[55071:1297270] start task 1
2021-03-16 16:33:54.921243+0800 TestDemo[55071:1297271] start task 3
2021-03-16 16:33:57.923428+0800 TestDemo[55071:1297272] end task 2
2021-03-16 16:33:57.923442+0800 TestDemo[55071:1297271] end task 3
2021-03-16 16:33:57.923442+0800 TestDemo[55071:1297270] end task 1

dipatch_group(调度组)的使用,项目使用JJ的多章节任务下载,等所有任务下载成功后弹窗提示已完成,还有就是检测最优线路后刷新UI
重点:
dispatch_group_enter()(入组)
dispatch_group_leave()(出组)

- (void)test{
  dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    [self sendRequest1:^{
        dispatch_group_leave(group);
    }];

    dispatch_group_enter(group);
    [self sendRequest2:^{
        dispatch_group_leave(group);
    }];

    dispatch_group_notify(group, dispatch_queue_create(0, 0), ^{
        NSLog(@"task over");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"refresh ui");
        });
    });
}
- (void)sendRequest1:(void(^)())block {
//异步请求,请求结果后block回调
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"start task 1");
    [NSThread sleepForTimeInterval:3];
    NSLog(@"end task 1");
    dispatch_async(dispatch_get_main_queue(), ^{
        if (block) {
            block();
        }
    });
});
}
- (void)sendRequest2:(void(^)())block {
//异步请求,请求结果后block回调
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"start task 2");
    [NSThread sleepForTimeInterval:3];
    NSLog(@"end task 2");
    dispatch_async(dispatch_get_main_queue(), ^{
        if (block) {
            block();
        }
    });
});
}
输出结果
2021-03-16 16:53:52.788430+0800 TestDemo[55332:1314700] start task 2
2021-03-16 16:53:52.788430+0800 TestDemo[55332:1314695] start task 1
2021-03-16 16:53:55.789766+0800 TestDemo[55332:1314700] end task 2
2021-03-16 16:53:55.789766+0800 TestDemo[55332:1314695] end task 1
2021-03-16 16:53:55.790373+0800 TestDemo[55332:1314695] task over
2021-03-16 16:53:55.790798+0800 TestDemo[55332:1314538] refresh ui

dispatch_once_t一次执行,常用来实现单例模式,这里以单例模式实现的模板代码为例展示dispatch_once_t的用法,其中的实例化语句只会被执行一次:

+ (instancetype *)sharedInstance {
    static dispatch_once_t once = 0;
    static id sharedInstance = nil;
    dispatch_once(&once, ^{
        // 只实例化一次
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

dispatch_barrier_(a)sync 栅栏函数
通过Dispatch_barrier_async添加的操作会暂时阻塞当前队列,即等待前面的并发操作都完成后执行该阻塞操作,待其完成后后面的并发操作才可继续。可以将其比喻为一座霸道的独木桥,是并发队列中的一个并发障碍点,临时阻塞并独占。
可见使用Dispatch_barrier_async可以实现类似dispatch_group_t组调度的效果,同时主要的作用是避免数据竞争,高效访问数据。

⚠️官方说明大意:在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用

☆⚠️关于dispatch_barrier_(a)sync区别
个人理解:dispatch_barrier_sync 需要等待栅栏执行完才会执行栅栏后面的任务,而dispatch_barrier_async 无需等待栅栏执行完,会继续往下走(保留在队列里)

/* 创建并发队列 */
    dispatch_queue_t concurrentQueue = dispatch_queue_create("test.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    /* 添加两个并发操作A和B,即A和B会并发执行 */
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"OperationA");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"OperationB");
    });
    /* 添加barrier障碍操作,会等待前面的并发操作结束,并暂时阻塞后面的并发操作直到其完成 */
    dispatch_barrier_async(concurrentQueue, ^(){
        NSLog(@"OperationBarrier!");
    });
    NSLog(@"验证async和sync的区别--现在是async");
    /* 继续添加并发操作C和D,要等待barrier障碍操作结束才能开始 */
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"OperationC");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"OperationD");
    });
⚠️ dispatch_barrier_async输出结果
2021-03-16 17:33:09.429076+0800 TestDemo[55672:1343472] OperationB
2021-03-16 17:33:09.429084+0800 TestDemo[55672:1343332] 验证async和sync的区别--现在是async
2021-03-16 17:33:09.429090+0800 TestDemo[55672:1343476] OperationA
2021-03-16 17:33:09.429235+0800 TestDemo[55672:1343476] OperationBarrier!
2021-03-16 17:33:09.429327+0800 TestDemo[55672:1343476] OperationC
2021-03-16 17:33:09.429329+0800 TestDemo[55672:1343472] OperationD

⚠️dispatch_barrier_sync输出结果
2021-03-16 17:34:36.483220+0800 TestDemo[55690:1345240] OperationB
2021-03-16 17:34:36.483245+0800 TestDemo[55690:1345246] OperationA
2021-03-16 17:34:36.483487+0800 TestDemo[55690:1345114] OperationBarrier!
2021-03-16 17:34:36.483578+0800 TestDemo[55690:1345114] 验证async和sync的区别--现在是sync
2021-03-16 17:34:36.483675+0800 TestDemo[55690:1345242] OperationD
2021-03-16 17:34:36.483681+0800 TestDemo[55690:1345246] OperationC

dispatch_after
通过该函数可以让要提交的任务在从提交开始后的指定时间后执行,也就是定时延迟执行提交的任务,使用方法很简单:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //延迟执行
        });
  • NSOperation 和 NSOperationQueue

项目中的使用:例如微信分享图片时,等图片下载完后再分享,用的NSOperationQueue
当 NSOperation 支持了 cancel 操作时,NSOperationQueue 可以使用 cancelAllOperatoins 来对所有的 operation 执行 cancel 操作。
不过 cancel 的效果还是取决于 NSOperation 中代码是怎么写的。(比如 对于数据库的某些操作线程来说,cancel 可能会意味着 你需要把数据恢复到最原始的状态。)

maxConcurrentOperationCount设置最大并发数
默认的最大并发 operation 数量是由系统当前的运行情况决定的(来源),我们也可以强制指定一个固定的并发数量。

GCD 与 NSOperation 的对比,面试中会经常问,因为这两个都很强大,我们也都经常在用

  • NSOperationQueue 是基于 GCD 的更高层的封装(从 OS X 10.10 开始可以通过设置 underlyingQueue 来把 operation 放到已有的 dispatch queue 中。)
  • 从易用性角度,GCD 由于采用 C 风格的 API,在调用上比使用面向对象风格的 NSOperation 要简单一些。
  • 从对任务的控制性来说,NSOperation 显著得好于 GCD,和 GCD 相比支持了 Cancel 操作(注:在 iOS8 中 GCD 引入了 dispatch_block_cancel 和 dispatch_block_testcancel,也可以支持 Cancel 操作了),支持任务之间的依赖关系,支持同一个队列中任务的优先级设置,同时还可以通过 KVO 来监控任务的执行情况。(这些通过 GCD 也可以实现,不过需要很多代码,使用 NSOperation 显得方便了很多。)
  • 从第三方库的角度,知名的第三方库如 AFNetworking 和 SDWebImage 背后都是使用 NSOperation,也从另一方面说明对于需要复杂并发控制的需求,NSOperation 可能是更好的选择,但也并不绝对,各有利弊,这只是个人理解。

NSOperation的使用
maxConcurrentOperationCount设置对大并发数
⚠️切勿添加循环依赖

- (void)test
{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"11111111%@", [NSThread currentThread]);
    }];
      
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"22222%@", [NSThread currentThread]);
    }];
      
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(3);
        NSLog(@"333333 %@", [NSThread currentThread]);
    }];
      
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"44444444%@", [NSThread currentThread]);
    }];
      
    // 指定操作之间的”依赖“关系,某一个操作的执行,必须等待另一个操作完成才会开始
    // 依赖关系是可以跨队列指定的
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    [op4 addDependency:op3];
    // *** 添加依赖的时候,注意不要出现循环依赖
//    [op3 addDependency:op4];
      
    [self.queue addOperation:op1];
    [self.queue addOperation:op2];
    [self.queue addOperation:op3];
    // 主队列更新UI
    [[NSOperationQueue mainQueue] addOperation:op4];
}
- (NSOperationQueue *)queue
{
    if (!_queue) _queue = [[NSOperationQueue alloc] init];
    return _queue;
}

你可能感兴趣的:(iOS多线程以及在项目中的使用)