多线程

多线程

###进程
每个程序运行, 都会在内存中开辟一个空间, 来执行程序的运行, (正在进行的程序), 内存中的每个进程都互不相关, 相对独立.

###线程
每个进程要执行什么任务, 都要开一个线程来执行, 每个进程只要要有一个线程

    * 串行: 当只有一个线程时, 需要执行几个任务的时候, 任务的执行是排队按顺序来的, 一个个执行下来, 称为串行(类似一个端口传送信息, 任务只能一个个来)
    * 并行: 开了n个线程之后, 可以在同一时间执行n 个任务, 每个线程执行一个任务. 其实 单个 cpu 在一个时间点只能处理一个线程, 因其处理速度快, 可以高频的在不同的线程间切换, 形成了同时执行多线程的假象.   快速在多线程之间调度
        多线程优点: 提高 CPU 利用率, 加快多任务的执行效率, 提高程序的执行效率
              缺点: 在线程过多的时候, 每个线程切换的频次降低, 会显得任务卡顿, CPU 负荷高
开启线程需要占用一定的内存空间, (默认情况下, 主线程是1M, 子线程默认是512k), 线程开的过多, 内存就占用大, 降低程序性能
线程越多, CPU 在调度线程方面的开销就越大
程序设计会更加复杂, 如: 线程键通信, 多线程数据共享

在 iOS 中, 程序运行会开辟一个线程–主线程, 也称为 UI线程,
程序对 UI 的各种操作, 拖动控件, 创建控件, 点击等等和 UI 有关的操作都是在主线程中执行.
一条线程只能处理一个任务, 任务多的话就要排队, 所以, 如果在主线程中进行一些耗时操作, 就会阻碍后面的操作, 在进行耗时操作时, 用户还进行了 UI 交互的话, 就会排队等待, 会造成操作卡顿的体验
如果能把耗时操作放到子线程, 那么在执行子线程耗时操作的时候, 主线程有 UI 事件要响应, 那么在同一时间会执行2个线程, UI事件会立即相应, 不会让用户等待.
————-如果以后有耗时操作的话, 放到子线程会是比较好的选择

开启线程:

实现多线程的方案:
* | pthread : POSIX thread | C 语言 | 几乎不用 | 跨平台, 比较接近底层, 但是不面向对象, 不好操作, 线程的各种操作需要程序员手动管理 |
* | NSThread : NS 框架 | OC | 少用 | OC 方法, 面向对象, 可以直接操作对象, 设置对象属性等, 如拿到当前线程: [NSThread curremtThread]; 也是需要手动管理线程 |
* | CGD |   C 语言    |   常用  | 可以调用多核来处理线程, 可以自动管理线程|
* | NSOperation |OC | 常用 | 基于 GCD, 面向对象, 自动管理线程的生命周期 |

####pthread:
        开启线程: 传入线程 id 地址, 好让函数可以将线程放到指定的空间,
                    传入调用的方法, 线程开启后执行的任务写在这个方法里,
        主线程开启子线程后继续自己的线程的任务, 然后子线程的事就不关主线程的事了

####NSThread:
        纯纯的 OC 面向对象, 像创建其他对象一样创建就好, 传入调用谁的什么方法, 还有一个传入方法的参数就好
        创建对象之后还要调用 NSThread 的开始方法才会开始这个线程,
        之后便是完整的开启了子线程

        对于主线程的一些操作

        对线程的属性的操作

        创建 NSThread 的三种方法和不同

        线程的创建都是在栈中完成的





    >_打印对象时, 会调用对象的 description 方法_
    >改项目名称的时候, 字啊项目名要改, 在上面的运行环境中的 scheme 也要改

线程状态

线程的生命周期, pthread 和 NSThread 都需要手动写代码来管理线程的生命周期,
了解线程的状态
    线程有5种状态:
        当创建一个新线程时[还未执行]:  New (新建), 这时仅仅是开辟了个内存
        开始线程(start)               :  runnable(就绪), 这时线程进入可调度线程池, 随时待 CPU 调度
        Cpu 调度当前线程            :   RUNning(运行) , 执行这个线程中的任务
        CpU 调度其他线程            :   重新回到 runnable(就绪), 等待下一次调度
        运行中调用了 sleep/等待同步锁: Blocked(阻塞), 线程阻塞后不做任何任务了, 所以就移出了调度池, 不可调度了,
        sleep 到时/得到同步锁        : 回到调度池, 进到 Runnable 状态, 等待调度
        当线程任务执行完毕[正常退出] 或 异常[到某行代码时意外退出] 或 强制退出[调用方法强制退出线程] :  Dead(死亡),  这时线程移出调度池, 但是还在内存中, 且没有方法回到调度池, 等待手动代码释放 release, 当 release 后释放内存后线程才从内存中移除

控制线程状态

控制线程的:    新建,  >>>>   开始  >>>>    睡眠    >>>>    强制退出
    新建: alloc.init .etc
    开始: start
    睡眠: sleepFor...
    强制退出: exit
    当线程 Dead 之后便不能在重新开始, 会报错
创建线程来实验下这些状态, 特别是 sleep 和 exit

多线程引发的安全问题

当有多条线程同时操作一个数据的时候, 会引发数据安全的问题
    同一时间(CPU 上如 A 比较块一点), A读取了数据 data, 迟一点, B 也读取了, 然后 A 改动后写回去, 迟一点, B 也改动了写回去, 最终只有 B 操作的结果有效, A 就白操作了, 数据就不对了

方法: 给数据加锁, 当 A 读取时, 先加一把锁, 再读取, 加锁的数据同一时间只能有一个线程读取, 只有线程对数据解锁后, 其他的线程才能操作读取这个数据. 这就相当于是串行了, 但是并行会有问题, 没办法

买票的例子 :
    多条线程同时操作同一个方法, 操作同一个数据, 没加锁, 在 CPU 调度的时候修改同一个数据会以最后一个结果为准, 显示出来就是几个结果都是一样的

    在 OC 中锁是锁代码, 在同一时间只能有一个线程执行某段代码,  代码中有执行操作数据, 所以同一时间只有一个线程操作数据        给代码加锁, 只能是用同一把锁, 这样在其他线程访问的时候才能知道这把锁有没有上锁, 如果没词都新建锁, 没次都是不上锁.
        加锁:
            NSLock tryLock   |  Lock    -------   太繁琐, 不建议
            @synchronized(锁对象) {   需要上锁的代码   }    ------   任何一个对象都可以是锁对象, 而一般本控制器就只有一个, 所以一般用 self 就好
            设置优先级: 优先级高的会比较大概率被 CPU 调度

        加锁的优点: 能有效防止多线程抢占资源造成的数据安全问题
                缺点: 需要消耗大量的 CPU 资源
                            上锁需要记录状态, , 其他等待的线程也需要消耗资源
                    多条线程抢夺同一资源时才要加锁, 不要乱加

    线程同步: 多条线程按顺序的执行任务
        线程同步方案: 加锁

原子属性和非原子属性

原子属性: atomic        |   会为 setter 方法加锁, 在 OC 中所有的属性都是 atomic 的, 性能较差,
非院子属性: nonautomic |   不会为 Setter 方法加锁  |  苹果官方推荐 nonatomic 提高性能, 减少资源占用

iOS 开发建议
    * 所有属性都声明为 nonatomic
    * 尽量避免多线程抢夺同一块资源
    * 尽量将加锁, 资源抢夺的业务逻辑交给服务器端处理 , 减小移动客户端的压力

线程间通信

*在一个进程中, 多线程往往不是孤立的 , 多个线程之间需要经常通信

 *线程通信体现:
    * 1个线程传递数据给另一线程
    * 在一个线程执行完特定任务后, 转到另一个线程继续执行任务

    下载图片的例子
        下载图片依赖网络状况, 是比较耗时的操作, 应该放到子线程中,
            开辟一条子线程来下载图片, 不影响主线程的 UI 操作
        iOS 规定, 对 UI 的刷新, 修改等操作必须在主线程中完成, 不然是不安全的, 我们设置的属性是 nonatomic

    传递数据给主线程: performSelectorOnMainThread....  waitUntil: No, 表示不等待, YES 表示等待, 一般是 NO, 直接结束子线程, 主线程等待返回数据
对于 NSTread, 只要了解基本用法, 熟练掌握 GCD 和 NSOperation

GCD

* 苹果官方做的一个针对多任务处理利用多核的技术
* 纯 C, 不用管线程的生命周期, 会自动管理,
* 只有两个概念, 对这两个操作就会自动进行线程的操作
    * 任务: 首先要定制一个任务
    * 队列: 将任务添加到队列中排队, 添加到不同的队列会有不同的处理

GCD 在一个libdispatch,
GCD 的函数基本都以 dispatch 开头

GCD 执行任务:
    开启任务函数类型:
        * 同步: 在当前线程中排队按顺序执行任务, 不能开新线程
        * 异步: 另外开线程来执行任务, 可以开新线程
    队列类型
        * 并行: 同时将队列中的任务拿出来并发执行
        * 串行: 将队列中的任务拿出来按顺序执行

    GCD 在开始就准备好了全局并发队列待调用, 传入队列优先级(一般是默认), 还有个0
            串行队列需要create 一个  |   使用主队列(跟主线程相关联的队列)

同步:
    * 并行: 不会开新线程
    * 串行: 一样更不会开新线程
异步: 具备开线程能力
    * 并行: 根据任务开 N 个线程
    * 串行: 开一条线程, 按顺序执行任务
一般我们用 GCD 是用异步, 因为用 GCD 就是要开新线程才用的

一般在 iOS 中, C语言的框架里, 看到 create/copy/new/retain 的, 都要 release
    在 arc 环境中就不需要手动管理了
        例外: CF 框架的还需要手动管理
反正看到那些关键字的都写一下, 报错就不写了呗

主队列是 GCD 自带的一种特殊串行队列, 对应全局并行队列
    主队列不管是加到那种函数(异步/同步)都不会开启新的线程, 全部任务都是扔到主线程中执行
        主队列的意义: 当子线程执行完后, 需要与主线程通信的, 可以在主队列中添加任务, 就会把任务加到主线程

注意!!!
    在同步函数中在主线程往主队列添加任务: 会死锁, 任务排队在最后, 但是前面调用任务的方法又在等待任务执行完, 最后谁都通不过
    在异步函数中的情况不同: 任务依然排在最后, 但是非同步, 前面调用的函数可以先执行完, 不用等待调用任务执行完毕

常用的情况:
    1. 在一个方法中, 需要耗时操作的时候, 开个异步全局并行(可根据需要开串行还是并行)执行耗时操作,
    2. 然后在 dispatch 异步(相对于子线程, 主线程也是异步)主线程队列执行 UI 刷新的操作, 这时, 代码都是卸载一起的, 不同线程的操作写在了 dispatch 的 block 封装的任务里
        同时, 在子线程的 dispatch 中的变量都可以直接在回到主线程的 dispatch 中的 block 中 使用

GCD 其他一些好用的函数
    * 延迟操作, 填入延迟的时间, 参数中有函数会根据这个时间算出需要延迟的具体时间, 然后就是操作的代码
    * 组队列,: 当需要在一些线程都执行完后, 所有队列都执行完后才去到其他线程做一些事情的话, 用组队列,
        可以将异步并行开启的线程 队列加到组队列中, 这个组就会在线程中分一个组 , 调用组队列的函数, notify 当组队列执行完后会通知, 然后执行某队列的任务
        dispatch_group_async_...(group , 队列, block) ,    组: 要先创建一个组

NSThread 和 GCD 的总结

这里总结些需要掌握的, 重点掌握, 其他的先了解即可

* NSThread
    * 线程开启的几种方法
        1. 先创建后启动, alloc] initWith...] -> start
        2. 直接启动: perform... / detach...

    * 其他用法
        1. [NSThread currentThread];
        2. + mainThread

    * 线程间通信:
        1. perform...InMain...

* GCD(重点)
    * 队列类型
        1. 并发: 获得全局并发队列: dispatch_get_globle...
        2. 串行: 自己创建: dispatch_queue_create  /  dispatch_get_main_queue
    * 任务类型(函数类型)
        1. 同步: dispatch_sync...
        2. 异步: dispatch_async
        3. 以上两个配合使用
        4. 线程间通信: dispatch_async....(globleQueue, ){  dispatch_asycn(主队列)}
        5. 其他用法: ..._once / after / group_async
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

单例模式

有些情况对于某个类自始自终只需要创建一个对象就好, 如资源工具, 想加载某个mp3, Application, .etc, 这时候就需要单例模式

设计模式: 长期以来某些方法/工具/经验的总结

不管别人怎么 alloc, init, retain, copy, 都只返回一个对象
    要做到这个, 需要控制几点
        1. 只创建一个存储空间  :: 用 dispatch_once,  将allocWithZone包起来, 为了试别人 alloc 时候返回同一个存储空间, 定义 static id _instance变量来保存 alloc 一次的对象
        2. 只初始化一次资源     :: dispatch_once将初始化资源的包起来// 或者将super init]也做 once 操作, 虽然苹果官方中没有这些, 建议还是包的好,
        3. 自己开的类方法访问获取单例时也做好控制
        4. copy 方法, 建议重写的是 copyWithZone, 也是什么都不做
        5. 对于非 ARC, 还需要自己做些内存管理
            5.1 retain 方法不加1, 返回自己就好
            5.2 release 方法. 什么也不做, 不释放对象
            5.3 retainCount 方法, 返回1
            5.4 autorelease 方法, 返回自己

    这些方法的重写几乎哪里都一样, 可以做个参数宏, 以后需要单例的时候直接调用就好, 注意: init 方法中有加载资源, 不一样, 就不纳入宏了

###适配 ARC 和非 ARC 的单例模式,
    在ARC 和 MRC 中内存管理不同, 除了 ARC 中要重写的方法之外, 还有其他的,
        适配同一个宏, 可以用条件编译, 看编译环境是不是 ARC, 是的话是什么宏, 不是的话是什么宏, 判断: __has_fearues(arc_)

NSOperation

墙裂推荐的多线程技术, 面向对象, 基于 GCD, 并封装了更多方法,
了解:
    OPeration: 操作, 将要执行的任务封装在里面, 相当 GCD 的任务
        是个抽象类, 需要子类才能封装任务: * InvocationOperation: 回调, 将任务封装在另一个方法里, 调用时会调用 target 的那个方法
                                         * BlockOperation: 块操作, 将任务封装在 block 里, 还可以用 add 在同一个 blockOperation 对象中封装多个
                                         * Operation 有对象方法取消本 Operation
                                         * 设置操作的队列优先级, 线程优先级
        单独执行需要 start, 单独 start 的话不会开线程, 只在主线程中同步串行
    Queue: 队列 ---
         NSOperationQueue:
                        * 可以直接通过 block 给对象添加 operation 任务
                        * queue addOperation] 添加 Operation 对象
                        * 根据任务的数量开线程, 不是 Operation 的数量, 有些 Operation 封装了几个任务, 还根据执行效率, 执行快的可能会线程复用
                        * 设置最大并发数, 不让线程过多而损耗 cpu 资源, 影响 UI 线程
                        * 操作监听: 可以给操作封装完成后的任务, 在 Operation完成后(里面封装的任务都完成)再执行某个任务, operation.completionBlock
                        * 设置依赖: 设置某个 Operation 在哪个 Operation 之后才执行, 叫依赖, A 依赖 B, A就在 B 之后执行, 注意: 不能相互依赖!!!
                        * 挂起/继续: 暂停整个队列的线程, 在某些动作时, 不想有线程耗 CPU 资源影响体验, 可以在动作时暂停队列中的所以线程(肯定都是子线程),
                        * 取消掉所有线程的 Operation [queue cancelAllOperation]

自定义 Operation

觉得系统自带的2个 Operation 不够用, 想自己写一个满足自己需求的
    e.g.tableViewZ 展示应用列表,
        一般 exception 中有 nib问题的在 storyBoard 里出问题或其他 xib
        不让任务栏和 tableView 重叠, 可以设置
        可以将 tableVie 的数据源方法和代理方法拖到代码库中,,,以免重复敲...

            在主线程(UI 线程)中下载图片显示的话,,,会先下载完显示 cell 的所以图片才能显示出 tableView 来....卡死...
            自定义 Operation 继承自 Operation, 设置好需要的属性, 然后重写- (void)main 方法, 当队列中的 Operation执行, 就会调用这个方法
            方案1:
                 cell 中下载图片肯定是要放到子线程的,
                 自定义的 Operation 只管下载图片, 传入 url, 返回给代理一个图片, 代理将图片设置给 cell, 属性 tableView
                 问题:
                    如果是相同的图片, 只用一个 Operation 下载就行, 不需要浪费资源, e.g.微博主页, 头像都是一样的, 不用每个 cell 都开线程下载
                        所以需要根据图片 url 确定一个下载 Operation, 这个 url 图片已经有 Operation 下载, 就不用重复下载
                            设定一个 Operations 字典记录正在执行下载操作的 Operation, key:URL, value: Operation
                    图片下一次就好, 下在的图片保存到一个字典中, 取这个 url 的图时先看看有没有缓存下来, 有就直接用, 没有在判断有没有 Operation去下载
                        key: URL, Value: img
                    在OPeration 下载完通知的代理方法中, 移除 字典中对应Operation[不然检测总有正在下载这个 URL 的],  将下载的图片保存到图片字典中缓存好, 刷新表格行,
                 这样就保证了对应行的 cell图片只下载了一次, 刷新时会判断查找对应行在图片字典中的图片, 只进行一次下载图片的操作
                 一定要下载完成后移除 Operation, 不然, 如果下载失败, Operation 还存在, 就不会去下载图片, 将永不可能下载了, 在移除下载操作缓存后判断 Image 是否有值
                    因下载可能会失败, 返回的 image = nil, 再赋值给 image 字典时, 对应 key: nil, 字典中不能存 nil, 就会报错
                    还要做性能优化, 拖拽开始挂起队列, 拖拽结束恢复队列
                    这个没次还要从新下载图片, 还需要将图片保存到本地
                    缺少本地离线缓存
                    在自定义 Operation 时在- (void)main方法中要自己创建自动释放池, autorelease, 因为子线程是不能访问主线程中创建的自动释放池的
                    而且每每耗时操作后要检测下是否被取消, 取消就直接 return,


        技巧:
        ####全局断点: 在导航区ccommand+6, 下面选择Add Exception BreakPoint: 异常断点, 会在报错的那行代码停下

        方案二:
            第三方框架: *SDWebImage, 一行代码, 就可以将方案一中的多线程, 加载一次, 下载一次, 离线缓存, 保存本地都做好...
        ####建议下载 github desktop 客户端管理下载的项目, 一般我们是需要文件夹里面的源代码, 它帮我们异步下载好图片展示, 同时做好了内存缓存和硬盘缓存
                        这个里面还有很多强大的方法, e.g.设置图片的时候可以看进度, 还能拿到图片, 同时设置当前下载的缓存策略
                注意: ! 这个框架因为可以把图片缓存下来, 所以内存要把关, 在设置图片的时候还能使用缓存策略, 如果一个项目都是用这个框架缓存图片, 要做好策略
                            设置 ImageCach 的策略 : 大小/ 时间/ ...
                        还必须做一件事! 在内存警告的方法中, 如果调用, 就是说发生了内存警告, 要立即清空所以的内存缓存!! , 同时停止所有正在下载的操作