多线程----> NSThread

多线程


n 什么是多线程
p 1 个进程中可以开启多条线程,每条线程可以 并行(同时) 执行不同的任务
p 进程 à 车间,线程 à 车间工人
p 多线程技术可以提高程序的执行效率
p
比如同时开启 3 条线程分别下载 3 个文件(分别是文件 A 、文件 B 、文件 C


多线程的原理
同一时间, CPU 只能处理 1 条线程,只有 1 条线程在工作 执行)
多线程并发(同时)执行,其实是 CPU 快速地在多条线程之间 调度 (切换)
如果 CPU 调度线程的时间足够快,就造成了多线程并发执行的假象
思考:如果线程非常非常多,会发生什么情况?
CPU 会在 N 多线程之间调度, CPU 会累死,消耗大量的 CPU 资源
每条线程被调度执行的频次会降低(线程的执行效率降低)

n 多线程的优点
p 能适当提高程序的执行效率
p 能适当提高资源利用率( CPU 、内存利用率)
n 多线程的缺点
p 开启线程需要占用一定的内存空间(默认情况下,主线程占用 1M ,子线程占用 512KB 如果开启大量的线程 会占用大量的内存空间,降低程序的性能
p 线程越多, CPU 在调度线程上的开销就越大
p 程序设计更加复杂:比如线程之间的通信、多线程的数据共享


n 什么是进程
p 进程是指在系统中 正在运行 的一个应用程序
p 每个进程之间是 独立 每个进程均运行在其专用且受保护的内存空间内
n 比如同时打开 QQ Xcode ,系统就会分别启动 2 个进程
n 通过“活动监视器”可以查看 Mac 系统中所开启的进程


n 什么是线程
p 1个进程要想执行任务, 必须 得有线程( 1 个进程至少要有 1 条线程
p 线程是进程的基本执行单元 ,一个进程(程序)的所有任务都在线程中执行
p
n 比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行

进程和线程的关系:进程室友线程组成的,每个进程中至少包含一个线程(主线程);

n 1 个线程中任务的执行是 串行
p 如果要在 1 个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务
p 也就是说,在同一时间内, 1 个线程只能执行 1 个任务


n 什么是主线程
p 一个 iOS 程序运行后,默认会开启 1 条线程 称为“ 主线程 ”或“ UI 线程
p
n 主线 程的主要作用
p 显示 \ 刷新 UI 界面
p 处理 UI 事件(比如点击事件、滚动事件、拖拽事件等)
p
n 主线程的使用注意
p 别将比较耗时的操作放到主线程中
p 耗时操作会卡住主线程,严重影响 UI 的流畅度,给用户一种“卡”的坏体验


问题演示: 在执行好事操作的时候,UI无法执行! (滚动/点击)----主线程!


进程和线程有什么区别?


进程:在系统中,"正在运行"的应用程序! -- "进程"为应用程序分配独立的"内存"空间!


1.app启动!


线程:线程"执行进程中的代码"!----线程是CPU调度的最小单元!


2.app中的代码被执行!


一个应用程序启动之后,会默认生成一条线程---->"主线程"


一个线程中有可能有很多代码(有很多代码块/方法)!------->线程中代码的执行顺序?


在同一条线程中,任务(代码)是按顺序执行的!线程只能执行完一块代码再执行另外一块代码! ------ "串行执行"


耗时操作---->统统放在"子线程"中执行!


子线程: "主线程"之外的线程都叫做子线程!自己创建的线程都是子线程!


线程:对象!


开启线程之后,为什么"不同线程"之间的任务就可以"同时执行"!


多线程的原理:线程是由CPU执行!CPU快速地在多条线程之间切换(调度/执行)!CPU执行线程的速度非常快!给我们造成一种多条线程"同时"执行的'假象';


多条线程之间任务'同时执行' -----> "并发执行"

任务是没有顺序的,同时执行!

多核CPU同时执行线程:----> "并行执行" ,真正的同时执行!


重点: "串行执行""并发执行" ----->定理/原理性东西!



iOS中开启线程的方式:


pthread : C 的框架!


NSThread : OC 的框架! NSThread内部封装的时 pthread!  --->线程的概念!


GCD : C 的框架,使用最多的!


NSOpretion : 内部封装了 GCD ,是一种高级的并发编程方式!



线程状态:


{

    // [alloc init] 线程对象!

    

    '新建'

    

    线程死亡或者销毁之后,就不能再次开启!强行开启已经死亡的线程,会造成程序奔溃!

    

    CPU 区分哪些线程需要执行,哪些不需要执行?

    

    '阻塞/挂起'就是将当前线程由可调度线程池"移出"!

    

    可调度线程池: CPU只执行可调度线程池中的线程!

    {

                 CPU 调度当前线程的时候              线程中任务执行完毕

        "就绪" ------------------------>"运行" ---------------------->'销毁/死亡'

        

        

                CPU 调度/执行其他线程              CPU再次执行/调度当前线程

        "运行" ------------------------>"就绪" -------------------------->"运行"

        

        

                 sleep 方法/互斥锁                      sleep时间到了/解锁            CPU调度当前线程

        "运行" ------------------------>'阻塞/挂起' ----------------------> "就绪" ----------------->"运行"

        

                    exit 方法

        "运行" ----------------------->'销毁/死亡'

        

        

    }

    

}



优先级反转:


在可调度线程池中有三条线程! 三条线程的优先级分别为高,,


高优先级线程 低优先级线程 之间存在"互斥锁"! 同一时间内只能有一条线程位于可调度线程池中!


可调度线程池:

{

    

    ''   ""   

    

}



             互斥锁

线程按顺序执行-------->"线程同步技术"!




互斥锁: set get 方法都加锁!一定能够保证安全!为了保证'内存中的有些区域'只允许一个对象访问!


私密区域: 只有一个对象能够访问

公共区域: 所有对象都能够访问

有限的公共区域: 只能够允许固定数量的对象访问 :信号量,决定了同时最多有多少个对象访问!互斥锁就是信号量为1的特殊情况!


原子锁: 只为 set方法加锁,不会为 get方法加锁! 原子锁不能解决卖票的问题!会然线程以死循环的方式来等待解锁!轻量级!




nonatomic:非原子属性,不是线程安全的!效率比较高!移动端选择非原子属性,有限满足性能需求!


atomic:原子属性,是线程安全的!耗费性能!


原子锁的添加:


开发中遵循的原则: 复杂的业务逻辑/需要加锁的代码,尽量交给服务器去完成!



UI操作为什么放在主线程执行'网络回调的代码要注意线程'!注意: reloadData 是一个 UI 操作,一定要放在主线程!

{

    1.主线程 --- UI线程,所有的 UI操作都放在主线程执行! 优先保证与用户交互!

    

    2. UIKit框架 都不是线程安全的!为了保证线程安全,所有的 UI操作都放在主线程!

}


客户端来说,开启线程有什么要求:

{

    1.所有的耗时操作(所有的网络请求必须放在子线程!/大数据的计算/复杂的计算/文件解压缩操作)必须放在子线程执行!

    2. 所有的 UI 操作都放在主线程!

    3.不同线程之间数据的交换(线程间通信!)需要保证安全!包保证顺序!

}


iOS 开发

{

    1. 线程一般最多开启 6 (3 ~5).

    2.如果是面向线程对象开打,需要手动管理线程的生命周期! (GCD不需要!)

}


GCD(大中央调度器/NB的中枢调度器/任务派发器) :为了取代 NSThread . 可以充分发挥设备多核的性能!


NSThread:  iOS 2.0 就推出了  (4S 之前都是单核 CPU)


GCD : C框架,但是不需要手动管理内存!效率非常高!


面向"任务"开发! --- 程序员只要将代码写好,并且将代码放在不同的"队列",选择不同的"执行方式",就可以决定是否开启线程!









NSThread:

/*--------------------------------------- 卡住主线程------------------------------------------*/

重点:1.线程进程区别(面试)!2.串行执行!

{

    1. 问题演示 :

    为什么在执行打印输出(执行耗时代码)的时候, UITextView不能滚动? 按钮不能点击?


    因为在同一条线程中,代码按顺序执行!所以在执行打印输出(执行耗时代码)的时候,卡住了主线程!


    如何解决这个问题? NSThread类开启新线程!


    // 开启一条新的线程: NSThread

    // 1.创建一条线程;

    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longTimeOperation) object:nil];

    // 2.启动线程;调用start方法,告诉CPU线程准备就绪;线程被 CPU 调度之后会自动执行@selector()中的方法;

    [thread start];


    2. 线程


    应用程序中的代码是由线程来执行的!

    一个进程至少包含一条线程!

    在一个应用程序启动之后,会默认开启一条线程 ---->主线程!

    主线程之外的线程---->子线程


    问题:线程是如何执行应用程序中的代码的?


    串行执行:

    在线程中的代码是按顺序执行的!同一时间内,只能有一个代码块执行!



    3. 进程


    就是在系统中'正在运行'的应用程序!


    进程为应用程序开辟独立的内存空间;


    // 这块内存空间是独立的,受保护的!进程和进程之间是互不干扰的!

}

/*-------------------------------------  多线程实现原理 ---------------------------------------*/

重点:并发执行!

{

    1.问题又来了! 为什么开启一条新线程之后就能解决卡住主线程这个问题了呢?


    :因为线程和线程之间是并发执行(同时执行)!


    2.多线程


    进程是由许多条线程组成的!

    一个进程可以包含很多条线程,每条线程都可以执行不同的代码!


    并发执行(同时执行):

    线程和线程之间是同时执行的!  ---->提高程序的运行效率!



    为什么多条线程之间可以并发(同时)执行呢?


    线程是由 CPU来执行的,同一时间只能有一条线程被执行!CPU在多条线程之间快速的切换!

    由于 CPU的执行速度非常快!就给我们造成了多条线程并发执行的'假象'  ------- 多线程实现原理!



    既然多线程这么爽,线程是不是越多越好呢?


    <1>开启线程需要消耗一定的内存(默认情况下,线程占用512KB的栈区空间);

    <2>会使应用程序增加很多代码!代码变多之后,程序复杂性就会提高!

    <3> CPU 在多条线程之间来回切换!线程越多, CPU就越累!


    建议:在移动应用的开发中; 一般只开3~5条线程!

}

/*---------------------------------------  UI 线程-------------------------------------------*/

重点:用户体验(面试)!

{

    iOS开发中,多线程开发的知识点://只有知道了这些知识点,大家才能进行多线程的开发!


    1.主线程又称为 UI 线程!主线程的作用: //有关UI操作,建议都放在主线程中执行!


    <1> 更新UI/ 刷新UI界面;

    <2> 处理 UI 事件(点击/拖拽/滚动等)


    2.耗时操作会卡住主线程!,影响 UI操作的流畅度!给用户一种'卡顿'的坏体验!


    注意点:别将耗时操作放在主线程中执行!

}

/*----------------------------iOS中多线程实现方案 1.pthread------------------------------------*/

重要知识点:

C 语言中的 void * 就等同于 OC 中的id;

{

    添加 pthread.h

    

    #import <pthread.h>


    看一遍代码!有时间看,没时间就别看!

}

/*-------------------------------------- 桥接 (__bridge) ------------------------------------*/

重点:为什么要使用桥接?你是怎么进行混合开发的?

{

    桥接 (__bridge) :C OC 之间传递数据的时候需要使用桥接! why?为什么呢?

    

    1.内存管理:

        OC ,如果是在 ARC环境下开发,编译器在编译的时候会根据代码结构,自动为 OC 代码添加 retain/release/autorelease.   ----->自动内存管理(ARC)的原理!

    

        但是, ARC只负责 OC部分的内存管理!不会负责 C语言部分代码的内存管理!

        也就是说!即使是在 ARC的开发环境中!如果使用的 C语言代码出现了 retain/copy/new/create等字样呢!我们都需要手动为其添加 release操作!否则会出现内存泄露!

    

        在混合开发时(C OC 代码混合),C OC 之间传递数据需要使用__bridge桥接,目的就是为了告诉编译器如何管理内存


        MRC中不需要使用桥接!因为都需要手动进行内存管理!

    

    2.数据类型转换:

    

        Foundation Core Foundation框架的数据类型可以互相转换的

        Foundation :  OC

        Core Foundation : C语言

    

        NSString *str = @"123"; // Foundation

        CFStringRef str2 = (__bridge CFStringRef)str;// Core Foundation

        NSString *str3 = (__bridge NSString *)str2;

            CFArrayRef ---- NSArray

            CFDictionaryRef ---- NSDictionary

            CFNumberRef ---- NSNumber


        Core Foundation中手动创建的数据类型,都需要手动释放


        CGPathRef path = CGPathCreateMutable();

        CGPathRetain(path);


        CGPathRelease(path);

        CGPathRelease(path);


    3.桥接的添加:

        利用 Xcode提示自动添加! --简单/方便/快速

/**

 凡是函数名中带有create\copy\new\retain等字眼,都应该在不需要使用这个数据的时候进行release

 GCD的数据类型在ARC环境下不需要再做release

 CF(Core Foundation)的数据类型在ARC\MRC环境下都需要再做release

 */

}

/*------------------------- iOS中多线程实现方案2.NSThread - 1基本使用 ---------------------------*/

重点:1.三种创建线程!2.常用方法!

{

    1.NSThread: 一个 NSThread 就代表一个线程对象!

    // OC语言 /使用面向对象 / 需要手动管理线程生命周期(创建/销毁等)

    

    2.三种多线程实现方案:

    

    1> 先创建,后启动

    // 创建

    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:nil];

    // 启动

    [thread start];

    

    2> 创建完自动启动

    [NSThread detachNewThreadSelector:@selector(download:) toTarget:self withObject:nil];

    

    3>隐式创建(自动启动)

    [self performSelectorInBackground:@selector(download:) withObject:nil];

    

    3.常用方法:

     名字/获得主线程/获得当前线程/阻塞线程/退出线程

    // 不常用: 栈区大小/优先级

    1> 获得当前线程

    + (NSThread *)currentThread;

    

    2> 获得主线程

    + (NSThread *)mainThread;

    

    3>睡眠(暂停)线程

    + (void)sleepUntilDate:(NSDate *)date;

    + (void)sleepForTimeInterval:(NSTimeInterval)ti;

    

    4> 设置线程的名字

    - (void)setName:(NSString *)n;

    - (NSString *)name;

}

/*------------------------- iOS中多线程实现方案2.NSThread - 2线程状态 ---------------------------*/

重点:1."Crash, P0级别 Bug(面试)!"2.理解线程状态!

{

    // 创建线程

    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];

    // 启动线程

    [thread start];


    线程池:存放线程的池子!分为:


    可调度线程池: CPU只会调度可调度线程池中的线程! 下面蓝色状态都位于可调度线程池中! '就绪' ,'运行'!


    不可调度线程池:下面红色状态都位于不可调度线程池中! "新建" ,"阻塞" ,"死亡"!


    线程状态:


                 start            CPU调度当前线程          运行结束/强制退出(exit)

        "新建" ---------->'就绪' -----------------> '运行' ----------------------->"死亡";


                CPU 调度其他线程           CPU调度当前线程

        '运行' ------------------>'就绪'----------------->'运行'


                调用 sleep/等待互斥锁            sleep时间到/得到互斥锁

        '运行' ----------------------->"阻塞"----------------------->'就绪';


    线程运行结束或者强制退出(exit)就进入"死亡"状态;

    

    "注意:一旦线程停止(死亡),就不可以再次开启任务!程序会挂掉: Crash!

    

    面试语句: 平时开发中,要特别关注 Crash! :"PO"级别的"Bug";


}

/*------------------------- iOS中多线程实现方案2.NSThread - 3资源共享 ---------------------------*/

重点:1.线程同步技术!2.理解资源共享

{

    当多条线程访问同一块资源的时候,就会出现数据错乱和数据安全的问题!

    

    1.ATM机取钱;卖票;

    

    2.解决方案:互斥锁@synchronized(锁对象self){ /*需要锁住的代码,越少越好!*/ }   -------加锁!

    

    注意:锁定一份代码只用一把锁,用多把锁是无效的!

    

    优点:能有效防止因多线程抢夺资源而引起的数据安全问题!

    缺点:需要消耗大量的CPU资源!

    

    结论:尽量少加锁!互斥锁的使用前提是多条线程抢夺同一块资源!

    

    3.添加互斥锁技巧: [[NSUserDefaults standardUserDefaults] synchronize];

    

    4.线程同步技术:  -----互斥锁使用了线程同步技术!

    

     线程同步技术:多条线程在同一条线上按顺序执行任务!

    

    5.线程安全:保证多条线程进行读写操作,都能够得到正确的结果!

    

    ''来实现线程安全!

}

/*--------------------------------- 原子属性和非原子属性 ---------------------------------------*/

重点:1.面试问题:为什么要在主线程更新UI? 2.原子和非原子属性选择!

{

    1.原子属性和非原子属性:

    

    OC在定义属性时有 atomic nonatomic 两种选择!

    

    atomic(默认属性):原子属性,自动为setter方法加锁!线程安全的,需要消耗大量的 CPU资源!

    

    nonatomic: 非原子属性,不会为 setter方法加锁!非线程安全的,适合内存小的移动设备!

    

    我们在声明属性的时候该如何选择?

    

    面试问题:为什么要在主线程更新UI?

    

    因为UIKit框架都不是线程安全的!为了得到更好的用户体验,UIKit框架牺牲了线程安全;

    

    所以我们要在主线程更新UI;

    

    2.iOS 开发建议:

    <1> 所有属性都声明为 nonatomic!

    <2>尽量避免多线程抢夺同一块资源!

    <3>尽量将加锁,资源抢夺等业务逻辑交给服务器端处理,减小移动客户端的压力!

}

/*------------------------- iOS中多线程实现方案2.NSThread - 4线程间通信 -------------------------*/

1.下载图片?更新 UI?

{

    1.后台线程(子线程)下载图片;

    

    [self performSelectorInBackground:@selector(downloadImage) withObject:nil];

    

    2.主线程更新 UI.

    

    线程间通信常用方法:

    

    // 最后一个参数:是否等待调用方法执行结束!

    <1>[self performSelectorOnMainThread:@selector(setImageWithImage:) withObject:nil waitUntilDone:YES];

    

    <2>[self performSelector:@selector(setImageWithImage:) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES];

    

}

/*------------------------------------------- 知识点补充 --------------------------------------*/

1.项目支持ARC

{

    -fobjc-arc :就可以让旧项目支持arc

    -fno-objc-arc :让原来支持arc的不使用arc

}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值