多线程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机取钱; 卖票;

#import "ViewController.h"

 

@interface ViewController ()

 // 剩余票数

@property (nonatomic,assign) NSInteger tickets;

 

@end

 

 @implementation ViewController

 

- (void)viewDidLoad {

    [super viewDidLoad];

    // 票数

    self.tickets = 100;

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

    // 卖票窗口

    

    // 1号窗口

    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets:) object:@"1号窗口"];

    

    // 2号窗口

    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets:) object:@"2号窗口"];

    

    // 3号窗口

    NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets:) object:@"3号窗口"];

 

    // 卖票

    [thread1 start];

    [thread2 start];

    [thread3 start];

    

}

// 卖票程序

- (void)saleTickets:(NSString *)window

{

    while (1) {

        

        [NSThread sleepForTimeInterval:0.05];

        

        @synchronized(self.view){

 

            if (self.tickets > 0) {

                

                // 睡一会

                [NSThread sleepForTimeInterval:0.05];

                

                self.tickets --; // 卖了一张票

                NSLog(@"%@卖了一张票,还剩余:%lu张票",window,(unsigned long)self.tickets);

                

            }else

            {

                NSLog(@"票卖完了");

                return;

            }

        }

    }

}

@end

    

    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];

    

}

posted on 2015-09-05 00:10  li杨  阅读(130)  评论(0)    收藏  举报

导航