iOS底层原理 - 多线程原理

章前回顾

上章我们了解了锁的一些知识,线程安全需要锁的协助。这章我们探索一下多线程原理篇;

初识

周知,了解多线程首先需要捋一下线程、进程、同步、异步、串行、并行、死锁等概念与关系。

多线程原理

线程:1、进程的基本执行单元,一个进程的所有任务必须需要在线程中执行;2、进程中至少需要一条线程;3、程序启动默认创建一条线程,创建的线程为主线程(UI线程);

进程:1、每个进程是独立的,拥有独立的内存空间;2、进程是指在系统正在运行的一个程序;

线程 进程 对比
地址空间 一个进程内线程地址共享 每个进程都有一个独立内存地址 各有优势
创建、销毁、切换 切换简单,速度快 切换复杂,速度慢 线程占优
可靠性 一个线程挂掉会导致整个进程挂 每个进程都是独立的,互不影响 进程健壮
效率 占用内存小,切换简单,CPU利用率高 占用内存空间较大,切换复杂,CPU利用率低 线程占优
分布 适用于多核分布式 适用多核、多机分布 进程占优

进程与线程的关系

​ 可以把iOS系统看做一个商场,进程(APP)则是商场中的店铺,线程就是类似店铺雇佣的员工

进程之间相互独立(奶茶店、果汁店):

​ ·奶茶店看不了果汁店的账目(访问不了别的进程的内存)

​ ·果汁店用不了奶茶店的波霸 (进程间的资源是相互独立的)

进程至少要一条线程:

​ ·店铺里至少需要一名员工(进程至少有一个线程)

​ ·店铺早上开店的员工 (进程中的主线程)

进程/线程崩溃

​ ·奶茶店倒闭了,并不影响果汁店营业(进程崩溃不会对其他进程有影响)

​ ·奶茶店的收银员不干了导致店铺无法正常营业(线程崩溃进而影响进程瘫痪)

同步:在当前线程中执行任务,不具备创新新线程能力;

异步:在新线程中执行任务,能够开启新线程;

串行:一个任务执行完毕执行下个任务,按顺序执行;

并行:多个任务同时(并发)执行;

主队列:特殊的串行队列,在主线程执行;

同步函数:不会创建新线程,当前线程执行,执行完毕后才能继续往下执行任务,其中一条任务没执行完成,会卡住改函数,不会往下执行;

异步函数:创建新线程,不要求当前线程执行完成,会等上个任务执行完后再执行;

串行队列:任务按顺序执行,一个任务一个任务执行;

并行队列:多个任务同时执行,异步函数下有效,开启多个线程;

串行队列 并行队列 主队列
同步(sync) ·没有开启新线程 ;·串行执行任务(卡住当前队列) ·没有开启新线程 ;·串行执行任务(有序) ·没有开启新线程 ;·串行执行任务(死锁)
异步(async) ·开启新线程 ;·串行执行任务(无序) ·开启新线程 ;·并发执行任务(无序) ·开启新线程 ;·串行执行任务(无序)

死锁

具有队列的特点,FIFO;

产生死锁的情况以及解决方案:

  • 主队列同步函数,会产生死锁;解决方案:主队列(mainQueue)异步,实现串行执行
  • 串行队列使用同步函数,会产生死锁,即使用async函数往串行队列中添加任务;解决方案:需要按顺序执行:并行队列加入同步函数执行/串行队列异步执行;需要并发执行:并行队列中加入异步函数执行

栅栏函数

特点:控制任务的执行顺序。(栅栏函数之前的执行完毕之后,执行栅栏函数,然后在执行栅栏函数之后的)

​ 在并行队列实现异步函数里,顺序是不固定的,加入栅栏函数可以使其按顺序执行,或者在串行队列异步函数也可以实现控制执行顺序

dispatch_queue_t queue = dispatch_queue_create("com.xx.def", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    for (NSInteger i =0; i < 3; i++) {
        NSLog(@"%ld TestLine1 %@",i,[NSThread currentThread]);
    }
});

dispatch_async(queue, ^{
    for (NSInteger i =0; i < 3; i++) {
        NSLog(@"%ld TestLine2 %@",i,[NSThread currentThread]);
    }
});

dispatch_barrier_async(queue, ^{
    NSLog(@"这是个栅栏函数");
});

dispatch_async(queue, ^{
    for (NSInteger i =0; i < 3; i++) {
        NSLog(@"%ld TestLine3 %@",i,[NSThread currentThread]);
    }
});

dispatch_async(queue, ^{
    for (NSInteger i =0; i < 3; i++) {
        NSLog(@"%ld TestLine4 %@",i,[NSThread currentThread]);
    }
});
结果:
2020-04-05 00:20:27.690434+0800 Test-OC[31185:1550291] 0 TestLine2 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.690482+0800 Test-OC[31185:1550290] 0 TestLine1 <NSThread: 0x6000005f2340>{number = 3, name = (null)}
2020-04-05 00:20:27.690693+0800 Test-OC[31185:1550290] 1 TestLine1 <NSThread: 0x6000005f2340>{number = 3, name = (null)}
2020-04-05 00:20:27.690696+0800 Test-OC[31185:1550291] 1 TestLine2 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.690827+0800 Test-OC[31185:1550290] 2 TestLine1 <NSThread: 0x6000005f2340>{number = 3, name = (null)}
2020-04-05 00:20:27.690936+0800 Test-OC[31185:1550291] 2 TestLine2 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.691053+0800 Test-OC[31185:1550291] 这是个栅栏函数
2020-04-05 00:20:27.691229+0800 Test-OC[31185:1550291] 0 TestLine3 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.691950+0800 Test-OC[31185:1550290] 0 TestLine4 <NSThread: 0x6000005f2340>{number = 3, name = (null)}
2020-04-05 00:20:27.692283+0800 Test-OC[31185:1550291] 1 TestLine3 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.692670+0800 Test-OC[31185:1550290] 1 TestLine4 <NSThread: 0x6000005f2340>{number = 3, name = (null)}
2020-04-05 00:20:27.692802+0800 Test-OC[31185:1550291] 2 TestLine3 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.692989+0800 Test-OC[31185:1550290] 2 TestLine4 <NSThread: 0x6000005f2340>{number = 3, name = (null)}

dispatch_group_notify

​ 应用类似场景:用户下载一个图片,图片很大,需要分成很多份进行下载,使用GCD应该如何实现?使用什么队列?下载完后统一刷新UI

使用Dispatch Group追加Block到Gobal Group Queue,最后使用group_notify监听执行完所有group block后执行notify 放入主线程的block;

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 合并图片 });

多线程的实现(技术方案)

更多详细使用可以参考:多线程详解

  1. pthread

    优点

    · C语言,适用Unix/Linux/Windows等系统

    ·可跨平台,可移植性,轻量级

    缺点

    ·需要手动管理生命周期

    ·api较为复杂,适用难度较大

  2. NSThread

    优点

    · 使用面向对象化

    ·易用,可直接操作线程对象

    缺点

    ·需要手动管理生命周期

  3. GCD

    优点

    · 基于C语言,自动管理线程生命周期

    ·多核并行运算的解决方案

    ·使用简易,性能优异,经常使用

    缺点

    ·在并发队列中,顺序控制相对复杂。

  4. NSOperation

    优点

    · 基于GCD面向对象的OC封装,更加面向对象化,自动管理线程生命周期

    ·可以添加线程依赖,控制线程总数,最大并发数

    ·监听线程完成情况

    ·NSOPeration是一个抽象的基类,表示一个独立的运算单元。可以为子类提供更有效且线程安全的建立状态,优先级,线程总数,依赖关系和取消等操作

    缺点

    ·使用面向对象,创建使用起来相对GCD更加复杂些,在不考虑线程控制,依赖关系下优先选择GCD;

多线程的生命周期

线程的生命周期分为5部分:新建,准备就绪,运行(执行),阻塞,销毁

​ 生命周期流程图:

[图片上传中...(线程生命周期联想图.png-fa417b-1600399385620-0)]
  • 新建:实例化新线程
  • 准备就绪:向线程对象发送start消息,将线程对象加入线程池可调度状态,等待CUP调度
  • 运行(执行):CUP在可调度线程池中执行线程任务;当前任务执行完毕,CUP会进入一个就绪状态,即进入短暂的等待期,在此期间如果有新任务需要执行,则执行新任务,否则该线程会销毁。等有下个任务执行,会开启新的线程执行。
  • 阻塞:当满足某个预定条件时,可以使用锁或者休眠,来阻塞线程执行;sleepForTimeInterval(休眠指定时长)sleepUntilDate(休眠到指定日期),@synchronized(object)(互斥锁)。
  • 销毁:正常销毁:线程执行完毕。非正常销毁:满足某个条件,线程内部终止执行或者主线程终止线程对象;

线程生命周期联系思维导图:

线程生命周期联想图.png

标注:图片等资源参考自多线程原理

线程池实现的原理

线程池工作原理流程图:

线程池工作原理流程图.png

线程与RunLoop关系

  • runLoop和线程是一一对应的关系,一个runLoop对应一个核心线程。核心:runLoop可以嵌套多个多个线程,但是核心只有一个。关系保存在全局字典中。
  • 在主线程中,在程序启动时,runLoop就默认创建了。
  • 在子线程中,runLoop是懒加载状态,只有使用时才会创建;子线程中使用定时器需注意:确保子线程runLoop的创建,不然定时器不会开启;
  • runLoop是用来管理线程的,当线程的runLoop被开启,线程在执行完毕后会进入休眠状态,等待新任务执行才会被唤醒
  • runLoop在主线程被开启时创建,线程结束时被销毁。

线程安全介绍看官们可以移位iOS多线程安全-锁

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