iOS-多线程-总结

容易混淆的概念(队列、线程、串行并行、同步异步、任务)

任务:代码块(一行或多行),也叫block
队列:管理任务的一个线性数据结构,有两种:串行和并行。区别是是否有开启新线程的能力
同步异步:说的是任务的执行方式。同步指的是阻塞当前线程等待该任务执行完才继续下面的任务。

//是否开辟新线程不是队列决定的,是由系统线程池决定的。经验:
1.串行+同步不创建,串行+异步会创建(主队列除外)
2.并行+同步不创建,并行+异步可能会创建

// dispatch_sync官方原文
This function submits a block to the specified dispatch queue for synchronous execution. Unlike dispatch_async, this function does not return until the block has finished. Calling this function and targeting the current queue results in deadlock.
Unlike with dispatch_async, no retain is performed on the target queue. Because calls to this function are synchronous, it "borrows" the reference of the caller. Moreover, no Block_copy is performed on the block.
As a performance optimization, this function executes blocks on the current thread whenever possible, with one exception: Blocks submitted to the main dispatch queue always run on the main thread.
//大意:
1、是在当前线程上指定队列中同步地执行任务。会阻塞当前线程等待该任务执行完才继续下面的任务,主队列除外。
2、是立即返回无需等待的,所以不需要block copy操作

iOS多线程方案有四种:pthread(C),NSThread(OC), GCD(C,自动管理),NSOperation(GCD的封装)。常用的是自动管理线程生命周期的GCD和NSOperation,其中GCD有以灵活性居先。

一、GCD

1、四种基本组合

串行+同步

    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
// 打印1,崩溃在__DISPATCH_WAIT_FOR_QUEUE__,死锁

串行+异步

    dispatch_queue_t queue = dispatch_get_main_queue();
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2-1-%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2-2-%@", [NSThread currentThread]);
    });
    NSLog(@"3");
// 打印1,3,2-1-main,2-2-main

并行+同步

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    NSLog(@"1");
    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"2-%@", [NSThread currentThread]);
    });
    NSLog(@"3");
// 打印1,2-main,3

并行+异步

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2-1-%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2-2-%@", [NSThread currentThread]);
    });
    NSLog(@"3");
// 打印1,3,2-1-num7,2-2-num4,开启了新线程

总结:串行+同步会死锁。并行+异步可开启新线程。

2、复杂情况

不同队列嵌套

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1-%@", [NSThread currentThread]);
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"2-%@", [NSThread currentThread]);
        });
        NSLog(@"3-%@", [NSThread currentThread]);
    });
    NSLog(@"4-%@", [NSThread currentThread]);
// 打印4-main,1-num5,2-mian,3-num5。1/4顺序不定

相同队列嵌套

    dispatch_queue_t queue = dispatch_queue_create("com.xx", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"#--0");
        dispatch_sync(queue, ^{
            NSLog(@"#--1");
        });
        NSLog(@"#--2");
    });
// 打印:0,然后死锁
    dispatch_queue_t queue = dispatch_queue_create("com.xx", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"#--0,%@",[NSThread currentThread]);
        dispatch_async(queue, ^{
            NSLog(@"#--1,%@",[NSThread currentThread]);
        });
        NSLog(@"#--2,%@",[NSThread currentThread]);
    });
//打印:0-main 2-main 1-null,主队列特殊,全是main

Exam 1

    NSLog(@"#--test");
    __block int i = 0;
    dispatch_queue_t queue0 = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL);
    while (i < 1000) {
        //dispatch_queue_t queue = dispatch_queue_create(0, 0);
        dispatch_async(queue0, ^{
            //NSLog(@"#--thread=%@",[NSThread currentThread]);
            i++;
        });
    }
    NSLog(@"#--i=%d",i);
// >=1000
3、api使用

1.dispatch_after

// 适用于不太精准的延迟执行
    NSLog(@"start");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });

dispatch_after不像NSTimer和performSelector:afterDelay一样需要runloop支持,但dispatch_after 并不是在指定时间后执行任务,而是在指定时间之后才将任务提交到队列中,这个延迟的时间是不精确的。

  1. dispatch_once
static TheClass *instance = nil;
+ (instance)sharedInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[TheClass alloc] init];
    });
    return instance;
}
  1. dispatch_apply
    同步执行按照指定的次数提交到指定的队列中的任务,可以将迭代任务转换为并发任务,迭代任务繁重时用它效率成倍提升。
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t iteration) {
        NSLog(@"%zu-%@", iteration, [NSThread currentThread]);
    });
// 可创建多个线程
  1. dispatch_barrier_async
    提供一个 “栅栏” 将两组异步执行的任务分隔开,保证先于栅栏方法提交到队列的任务全部执行完成之后,然后开始执行将栅栏任务,等到栅栏任务执行完成后,该队列便恢复原本执行状态。只适用于自定义并发队列,全局并发队列和串行队列无意义。
    dispatch_queue_t queue = dispatch_queue_create("thread", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"test1");
    });
    dispatch_async(queue, ^{
        NSLog(@"test2");
    });
    dispatch_async(queue, ^{
        NSLog(@"test3");
    });
    
    dispatch_barrier_sync(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"barrier");
    });
    NSLog(@"aaa");
    dispatch_async(queue, ^{
        NSLog(@"test4");
    });
    NSLog(@"bbb");
    dispatch_async(queue, ^{
        NSLog(@"test5");
    });
    dispatch_async(queue, ^{
        NSLog(@"test6");
    });
//打印 test1,test3,test2,barrier,aaa,bbb,test4,test5,test6
//异步 aaa,test1,bbb,test2,test3,barrier,test4,test5,test6
// 执行的顺序,同一线程内同步有序,异步无序和执行任务效率有关。不同线程只和任务效率有关。

5.dispatch_group

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"1");
//    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        dispatch_async(queue, ^{
            sleep(1); //这里线程睡眠1秒钟,模拟异步请求
            NSLog(@"2");
//            dispatch_group_leave(group);
        });
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"3");
    });
//    dispatch_group_wait(group, DISPATCH_TIME_NOW);
    NSLog(@"4");
    dispatch_async(queue, ^{
        NSLog(@"5");
    });
}
// 打印1,4,3,5,2(3,5顺序不定)。
//dispatch_group_enter/leave: notify只负责group里面的任务执行完,如果group里面嵌套有异步任务,是不会管他就直接返回的。这时就需要enter/leave了,成对出现的,enter多了会阻塞,leave多了会崩溃。
// dispatch_group_wait:阻塞group到某个时刻,然后释放继续执行下面的任务,参数DISPATCH_TIME_NOW表示不阻塞,相当于没有,参数DISPATCH_TIME_FOREVER表示永远阻塞,还可以自定义为3秒后的时刻,表示阻塞3秒,之后放开。打开wait并设置time为FOREVER后,打印为1,4,3,5,2,3在5前面

6、dispatch_semaphore
GCD的锁机制,iOS线程同步除了os_unfair_lock 和
OSSpinLock之外,dispatch_semaphore的性能是很好的极力推荐使用。
dispatch_semaphore_create:根据传入的初始值创建一个信号量。
不可传入负值。运行过程中,若内部值为负数,则这个值的绝对值便是正在等待资源的线程数。
dispatch_semaphore_wait:信号量 -1。 -1 之后的结果值小于 0 时,线程阻塞,并以 FIFO 的方式等待资源。
dispatch_semaphore_signal:信号量 +1。 +1 之后的结果值大于 0 时,以 FIFO 的方式唤醒等待的线程。
一般用来:控制最大并发数、数组多线程安全、阻塞异步任务

// 控制最大并发数
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 10; i++) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(queue, ^{
            NSLog(@"%d-%@", i, [NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
    }
// YYKit 中 YYThreadSafeArray 的实现(无法多读)
// 通过宏定义对代码块进行加锁操作
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);
// id obj = array[idx];
- (id)objectAtIndexedSubscript:(NSUInteger)idx {
    LOCK(id o = [_arr objectAtIndexedSubscript:idx]); return o;
}
// array[idx] = obj;
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx {
    LOCK([_arr setObject:obj atIndexedSubscript:idx]);
}

7、dispatch_source
用GCD的函数指定一个希望监听的系统事件类型,再指定一个捕获到事件后进行逻辑处理的闭包或者函数作为回调函数,然后再指定一个该回调函数执行的Dispatch Queue即可,当监听到指定的系统事件发生时会调用回调函数,将该回调函数作为一个任务放入指定的队列中执行。
https://blog.csdn.net/hailideboke/article/details/78711514

4、灵活运用

1.阻塞异步任务的几种方式
group、barrier、semaphore

二、NSOperation

三、其他相关问题

1、线程与runloop
延时执行任务:
dispatch_after 比 NSTimer 优秀,因为他不需要指定 Runloop 的运行模式。 dispatch_after 比 performSelector:afterDelay: 优秀,因为它不需要 Runloop 支持。

iOS定时器:
NSTimer:
dispatch_after:

2、线程与数据安全
多读单写-barrier、单读单写-semaphore

3、如何取消GCD任务
dispatch_block_cancel、外部变量控制

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

推荐阅读更多精彩内容