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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,928评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,748评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,282评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,065评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,101评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,855评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,521评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,414评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,931评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,053评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,191评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,873评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,529评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,074评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,188评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,491评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,173评论 2 357

推荐阅读更多精彩内容