多线程

---恢复内容开始---

1.基本概念

   1.1 进程

              进程是指在系统中正在运行的一个应用程序.每个进程之间是独立的,每个进程之间均运行在其专受保护的内存空间内.

   1.2 线程

              基本概念:一个线程要想执行任务,必须得有线程(每个进程中至少有一个线程). 线程是进程基本执行单元,一个进程的所有任务都在线程中执行. 

              线程的串行:一个线程中任务的执行是串行的,要想在一个线程中执行多个任务,那么只能一个一个的按顺序执行这些任务.也就是说,在同一时间里,一个线程只能执行一个任务.

   1.3 多线程   

                 基本概念:即一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务.

                 线程的并行:并行也就是同时执行.比如同时开启3条线程分别下载3个文件(分别是文件A, 文件B, 文件C).     进程 ==> 车间         线程 ==> 车间工人

                 多线程并发执行的原理:在同一时间里,CPU只能处理一条线程,只有一条线程在执行. 多线程并发(同时)执行,其实是CPU快速的在多条线程之间调度(切换),如果CPU调度的时间足够快,就造成了线程并发执行的假象.

                 多线程的优缺点:

                                     a优点 1>能适当提高程序的执行效率

                                              2>能适当提高资源的利用率(CPU, 内存利用率)

                                     b缺点 1>开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB,每创建线程需耗时90毫秒)如果开启大量的线程,会占用大量的内存空间,降低程序的性能.

                                              2>线程越多,CPU在调度线程上的开销就越大

                                              3>程序设计更加复杂:比如线程之间的通信, 多线程的数据共享

   1.4 主线程

                 基本概念:一个iOS程序运行后,默认会开启一条线程,称之为"主线程" 或 "UI线程"

                 主要作用: 1>显示\刷新UI界面

                               2>处理UI事件 (比如点击事件, 滚动事件, 拖拽事件等)

                 使用注意: 1>别将比较耗时的操作放到主线程中

                               2>耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种"卡"的不好体验.

                               3>和UI相关的刷新操作必须放到主线程中进行处理.

2.实现方案

   2.1 NSThread的基本使用

                      创建方式 a> alloc + init

                                       注意:需要手动开启线程.

                                       特点:如果正在执行系统分离出来的线程(子线程)时, 系统内部会retaion当前线程.

                                       只有线程中的方法执行完毕,系统才会将其释放 release.

第一个参数: 目标对象

第二个参数: 选择器,线程启动要调用哪个方法

第三个参数: 前面方法要接收的参数(最多只能接收一个参数,没有则传nil)

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

//启动线程

[thread start];

                                  b> 分离出一条子线程

                                       不用手动调动start方法,系统会自动启动.

                                       没有返回值,不能对线程进行更多的设置.

                                       优点:简单快捷            缺点:无法对多线程进行更详细的设置

                                       应用场景:需要快速简便的执行线程

第一个参数: 线程启动调用的方法

第二个参数: 目标对象

第三个参数: 传递给调用方法的参数

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

                                             c> 后台线程 

//隐式创建并启动线程  

//系统会自动创建一个子线程,并且在子线程中自动执行self的@selector 

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

   2.2 设置线程的属性

 //设置线程的名称

thread.name = @"线程A";

//设置线程的优先级,注意线程优先级的取值范围为0.0~1.0之间, 1.0表示线程的优先级最高, 如果不设置,那么默认为0.5

thread.threadProiority = 1.0;

   2.3 线程的状态

                      创建出来 --> 新建状态

                      调用start --> 准备就绪

                      被CPU调用 --> 运行

                      sleep --> 阻塞

                      执行完毕,或者被强制关闭 --> 死亡

               注意:如果强制关闭线程,关闭之后的其他操作都无法执行

//阻塞线程

[NSThread sleepForTimeInterval:2.0];

[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];

//退出当前线程

[NSThread exit];

   2.4 线程安全

                    应用场景: 多个线程访问同一块资源会发生数据安全问题

                    注意点: 1>只要加锁就会消耗性能

                               2>加锁必须传递一个对象.作为锁

                               3>如果想要正真的锁住代码,那么多个线程必须使用同一把锁才行

                               4>加锁的时候尽量缩小范围,因为范围越大性能就越低

                           加锁: @synchronized

线程同步的意思是:多条线程在同一条线上执行(按顺序执行)

互斥锁,就是利用了线程同步技术

          安全隐患实例: a>存钱取钱

                              b>卖票(代码如下)

#import "ViewController.h"

 

@interface ViewController ()

@property (nonatomic, strong) NSThread *thread1; /** 售票员1*/

@property (nonatomic, strong) NSThread *thread2; /** 售票员2*/

@property (nonatomic, assign) NSInteger totalTicket; /** 剩余票数*/

@property (nonatomic, strong) NSObject *obj; /** 锁*/

@end

 

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

//初始化剩余票数

self.totalTicket = 100;

//创建2个售票员

self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];

self.thread1.name = @"售票员1"; 

self.thread2 = [[NSThread alloc]  initWithTarget:self selector:@selector(saleTicket) object:nil];

self.thread2.name = @"售票员2";

 

}

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

//2个售票员同时开始售票

[self.thread1 start];

[self.thread2 start];

}

- (void)saleTicket{

while (1)

//在开发中,如果要加锁,一般情况使用self

@synchronized(self)

{//取出剩余的票数

NSInteger count = self.totalTicket;

//判断还有没有余票

if (count > 0){

[NSThread sleepForTimeInterval:0.01];

//卖票

self.totalTicket = count - 1;

NSLog(@"%@, %ld", [NSThread currentThread].name, self.totalTicket);

}else

{//提示用户卖完啦

NSLog(@"对不起, 卖完啦");

[NSThread exit];

  }

 }

}

@end

   2.2原子和非原子属性

                               atomic: 原子属性为setter方法加锁(默认就是atomic)

                               nonatomic: 非原子属性,不会为setter方法加锁

                       注意点: atomic系统给我们自动添加的锁不是互斥锁,是自旋锁

          自旋锁和互斥锁的对比:

                                      共同点:1>都能够保证多线程在同一时候,只能有一个线程操作锁定的代码

                                      不同点:1>如果是互斥锁,假如现在被锁住了,那么后面来的线程就会进入"休眠"状态直到解锁之后,又会唤醒线程继续执行

                                                2>如果是自旋锁,假如现在被锁住了,那么后面来的线程不会进入休眠状态,会一直等待,直到解锁之后立即执行

                                                3>自旋锁更适合做一些较短的操作

   2.3 NSThread线程通信

                                  基本概念:在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信

                                  体现: 1> 一个线程传递数据给另一个线程

                                          2> 在一个线程中执行完特定任务后,转到另一个线程继续执行任务

                                  线程间通信常用方法:1> - (void)performSelectorOnMainThread:(SEL) aSelector WithObject:(id)arg waitUntilDone:(BOOL)wait;

                                                             2> - (void)performSelector(SEL)aSelector onThread:(NSThread*)thr withObject:(id)arg waitUntilDone:(BOOL)wait;    

         waitUnitlDone的含义:1>如果传入的是YES: 那么会等到主线程中i方法执行完毕,才会继续执行下面其他行的代码

                                       2>如果传入的是NO: 那么不用等到主线程中的方法执行完毕,就可以继续执行下面其他行的代码

   注意:虽然有时候可以在子线程中操作UI,但是开发中千万不要这样干.如果是在子线程中操作UI,有时候行,有时候不行.           更新UI一定要在主线程中更新   

 

[self performSelectorInBackground:@selector(download2:) withObject:url];

 

[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];

 

[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:NO];

 


[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];

 

[self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];

3.GCD

   3.1 任务的基本概念:执行什么操作

         队列的基本概念:用来存放任务

         同步函数dispatch_sync 不具备开启新线程的能力

         异步函数dispatch_sync 具备开启线程的能力   

         队列的类型: a>并发队列

                                        1>可以让多个任务并发(同时)执行

                                        2>也可以自己创建

 dispatch_queue_t queue = dispatch_queue_create(<#const char *label#>, <#dispatch_queue_attr_t attr#>)

                                        3>全局并发队列: dispatch_get_global_queue(0 , 0);

                          b>串行队列

                                        1>让任务一个接着一个的执行

          GCD的各种组合:1> 

 同步 + 并行 = 不会开启新的线程

 注意: 能不能开启新的线程, 和并行/串行没有关系, 只要函数是同步还是异步

 */

- (void)syncConcurrent

{

    // 1.创建队列

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    // 2.添加任务

    dispatch_sync(queue, ^{

        NSLog(@"1 - %@", [NSThread currentThread]);

    });

    dispatch_sync(queue, ^{

        NSLog(@"2 - %@", [NSThread currentThread]);

    });

    dispatch_sync(queue, ^{

        NSLog(@"3 - %@", [NSThread currentThread]);

    });

    NSLog(@"%s", __func__);

}

2>

同步 + 串行 = 不会创建新的线程

 注意: 如果是同步函数, 只要代码执行到了同步函数的那一行, 就会立即执行任务, 只有任务执行完毕才会继续往后执行

 3>

/*

 异步 +  并行 = 会开启新的线程

 */

- (void)asynConcurrent

{

    /*

     第一个参数: 队列

     第二个参数: 任务

     */

    // 1.创建队列

    /*

     第一个参数: 队列的名称

     第二个参数: 队列的类型

     */

    //    dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT);

    

    // 其实系统内部已经给我们提供了一个现成的并发队列

    /*

     第一个参数: iOS8以前是线程的优先级/ iOS8以后代表服务质量

     iOS8以前

     *  - DISPATCH_QUEUE_PRIORITY_HIGH: 2

     *  - DISPATCH_QUEUE_PRIORITY_DEFAULT: 0

     *  - DISPATCH_QUEUE_PRIORITY_LOW: -2

     *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND: -32768

     

     iOS8开始, 取值都是十六进制

     *  - QOS_CLASS_USER_INTERACTIVE 用户交互(用户迫切的想执行任务, 不要在这种服务质量下做耗时的操作)

     *  - QOS_CLASS_USER_INITIATED   用户需要

     *  - QOS_CLASS_DEFAULT          默认(重置队列)

     *  - QOS_CLASS_UTILITY          实用工具(耗时的操作放在这里)

     *  - QOS_CLASS_BACKGROUND

     *  - QOS_CLASS_UNSPECIFIED      没有设置任何优先级

     第二个参数: 系统保留的参数, 永远传0

     */

    // 1.获取全局的并发队列

    dispatch_queue_t queue = dispatch_get_global_queue(0 , 0);

    // 2.添加任务到队列

    // 文档说明是FIFO原则, 先进先出

    // 打印结果不正确的原因: 线程的执行速度可能不一样, 有得快一些, 有的慢一些

    dispatch_async(queue, ^{

        NSLog(@"1 - %@", [NSThread currentThread]);

    });

    dispatch_async(queue, ^{

        NSLog(@"2 - %@", [NSThread currentThread]);

    });

    dispatch_async(queue, ^{

        NSLog(@"3 - %@", [NSThread currentThread]);

    });

    NSLog(@"%s", __func__);

}

 4>

/*

 异步 + 串行 = 会创建新的线程, 但是只会创建一个新的线程, 所有的任务都在这一个新的线程中执行

 异步任务, 会先执行完所有的代码, 再在子线程中执行任务

 */

- (void)asyncSerial

{

    // 1.创建队列

    dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_SERIAL);

    

    // 2.添加任务

    dispatch_async(queue, ^{

        NSLog(@"1 - %@", [NSThread currentThread]);

    });

    dispatch_async(queue, ^{

        NSLog(@"2 - %@", [NSThread currentThread]);

    });

    dispatch_async(queue, ^{

        NSLog(@"3 - %@", [NSThread currentThread]);

    });

    NSLog(@"%s", __func__);

}

 ---恢复内容结束---

posted @ 2015-10-26 19:55  大伟?大卫  阅读(84)  评论(0)    收藏  举报