多线程1

常用操作

多行注释: Command + option + /
获取当前的时间:CACurrentMediaTime()
后台运行程序:[self performSelectorInBackground:@selector(longOperation) withObject:nil];
创建子线程,让longOperation方法在子线程异步执行
target-select:

多线程基础

  1. 空的for循环不耗时
  2. 操作栈区内存空间不耗时,因为栈区的内存空间是连续的,不需要寻址
  3. 操作常量区内存空间不耗时,但是比操作栈区空间耗时一些,寻址只做一次
  4. 操作堆区的内存空间相对于栈区和常量区是耗时的,因为堆区的内存空间不是连续的,需要寻址
  5. I/O操作也是非常耗时的
  6. 耗时操作对UI影响:会卡死UI
  7. 如何解决耗时操作卡死UI的问题:使用多线程技术
  8. 多线程的核心思想: 就是把耗时操作放在后台执行,避免耗时操作卡死UI
  9. 在实际开发中,网络操作时非常耗时的,一般会把网络操作(下载,上传...)放在后台执行
  10. 学习多线程就是为了学习网络做准备的
    I/O操作:是把内存的数据输出到外接设备(屏幕,磁盘)output,把外接设备的数据输入到内存input

同步&异步

同步异步 是任务执行的两种方式
同步:多个任务按序依次执行
异步:多个任务同时执行,就是异步执行(后台执行就是异步执行)

进程&线程

进程:
在系统中 正在运行 的一个应用程序就是一个进程
通过 活动监视器 可以查看MAC系统中 正在运行 的所有应用程序
每个进程之间都是 独立 的,均运行在其 专用受保护 的内存空间
两个进程之间是无法通信的,迅雷无法帮助我们下载正在播放的音乐
进程可以类比成正在 正常运行 的公司
线程:
线程可以类比成公司中的员工
进程要想执行任务,必须要有线程,且每个进程至少有一个线程
线程是进程的 基本执行单元,进程中的所有任务都在线程中执行
程序启动(进程开启)会默认开启一条线程
1个进程中可以有多个线程
多线程:
一个进程中可以开启多条线程,多条线程可以同时执行不同的任务
进程-公司,线程-员工
多线程可以解决程序阻塞的问题
多线程可以提高程序的执行效率,给用户良好的使用体验
多线程执行原理:
单核CPU同一时间,CPU只能处理1个线程,只有1个线程在执行任务
多线程的同时执行:其实就是CPU在多线程之间快速切换(调度任务)
如果CPU调度线程的速度足够快,就造成了多线程同时 执行 的假象
如果线程非常多,CPU会在多线线程之间不断的调度任务,结果就是消耗了大量的CPU资源,CPU会趴下
每个线程调度的频率会降低
线程的执行效率会下降
多线程的优缺点
实际开发中,能不用多线程就不用,主线程够用
如果必须使用,就简单使用
优点

  1. 能够适当提高程序的执行效率
  2. 能适当提高CPU和内存的利用率
  3. 线程上的任务执行完成后,线程会自动销毁,节省内存

缺点

  1. 开启线程需要占用一定的内存空间,如果开启的线程过多,会占用大量的CPU资源,降低程序的性能
    2.占用内存空间:默认情况下,子线程512K,主线程1M,PS:IOS8中,主线程521K
  2. 线程越多,CPU调度线程的开销就越大
    时间开销
    空间开销
  3. 程序设计更加复杂:比如多线程之间的通信,多线程的数据共享

主线程:

  1. 程序一起动就会创建主线程,主线程会执行main函数,
  2. 一个程序运行后,默认会开启1个线程,称为主线程UI线程
  3. 主线程一般用来刷新UI界面,处理UI事件
  4. 处理UI事件:点击 滚动 拖拽
  5. 主线程使用注意:
    别将耗时的操作放在主线程中
    耗时操作会卡主主线程,严重影响UI的流畅度,给用户一种卡的坏体验,影响UI交互质量

凡是跟UI相关的都是在主线程执行的(子线程创建就有,不创建就没有)

pthread

pthread创建线程

/*
     创建一个线程
     
      参数1:子线程的ID(标识)
     在C语言中,一般带`_t`/`_ref`标识数据类型
      参数2:子线程的一个属性,一般传入NULL
            NULL:表示空地址,一般在C语言中使用
            nil:表示空对象,一般在OC中使用
            其实,NULL和nil本质上没有半点区别
      参数3:子线程需要执行的函数
        void *(*)(void *)表示指向函数的指针,即函数名:函数名就是表示函数地址;数组地址就是数组名或者数组第0个角标元素的地址
        void*:表示可以指向任何地址的指针,代表任意数据类型,类似于OC的id
     void *   (*)  (void *):
     返回值   函数名   函数参数
      参数4:子线程需要执行的函数的参数
     返回值:int;在很多C语言框架中,不是遵守非零即真,因为成功的结果只有一个,0是唯一的;失败的原因有很多
     线程调试:查看方法执行的线程是否是主线程或者子线程
     {number = 1, name = main}:表示主线程
     {number = 3, name = (null)}:表示子线程,主要number != 1,就表示子线程
     提示:千万不要纠结number到底等于几,系统分配的
    __bridge:在c语言和oc语言混合开发时,需要做数据类型转换,有时候需要使用桥接
     桥接作用:在做数据类型转换时,告诉编译器如何管理c语言的内存
     提示:在ARC环境下,编译器在编译时,不会自动管理C语言申请的内存空间
     提示:加上__bridge 表示告诉编译器,c语言申请的内存也是自动管理的,因为大环境是ARC的
     MRC环境下,不需要使用__bridge,因为手动管理内存
     */
     NSLog(@"%@",[NSThread currentThread]);
    //参数1
    pthread_t ID;
    //参数4
    NSString *str = @"hello";
    //创建了一个子线程
   int result =  pthread_create(&ID, NULL, demo, (__bridge void *)(str));
    //判断子线程创建是否成功
    if(result == 0){
        NSLog(@"创建子线程成功");
    }else{
        NSLog(@"创建子线程失败");
    }
    


/**
 子线程执行的函数

 */
void *demo(void *param){
    
    NSString *str = (__bridge NSString *)(param);
    //currentThread:查看当前线程
    NSLog(@"demo %@ - %@",[NSThread currentThread],str);
    return NULL;
}

NSThread

创建线程的3种方式

//分类方法创建子线程
//不可以拿到线程对象
//不需要手动启动线程
-(void)threadDemo3{
    //方便所有继承自NSObject的对象,可以直接调用线程的方法(swift里面没有这个分类,也没有GCD)
    [self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
}
//构造方法创建子线程
//不可以拿到线程对象
//不需要手动启动线程
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [self threadDemo2];
}
-(void)threadDemo2{
    [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
}
//构造方法创建子线程
//可以拿到线程对象
//需要手动启动线程
-(void)threadDemo1{
    //创建线程对象
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
    //启动线程
    [thread start];
}
-(void)demo:(id)param{
    //子线程执行的方法
    NSLog(@"%@-%@",param,[NSThread currentThread]);
}

target和selector的关系

  • 执行哪个对象的哪个方法
  • 需求:执行Person对象的run方法
NSThread *thread = [[NSThread alloc] initWithTarget:_p selector:@selector(run:) object:@"alloc"];

线程生命周期/线程状态

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"喜大普奔,神舟十一");
   
    //注意:千万不要在主线中,使用这个方法,会使主线程死亡
     //[NSThread exit];
    [self threadDemo];
}
-(void)threadDemo{
    //提示:程序员只能够做新建和就绪,其他的都由系统来处理
    
    //创建线程对象:新建状态
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    //启动线程:就绪状态(把线程对象添加到可调度线程池,等待被CPU调度执行)
    [thread start];
    
}
-(void)demo{
    //NSLog(@"%@",[NSThread currentThread]);
    for (NSInteger i = 0; i < 5; i++) {
        //线程每次执行到这里就休眠1秒钟:for循环,每循环一次就休息1秒钟
        //sleepForTimeInterval:使当前的线程休眠到指定时长
        //sleepForTimeInterval:使用场景就是模拟网络延迟操作,仅仅是模拟,开发中不会使用的
        [NSThread sleepForTimeInterval:1.0];
        
        NSLog(@"%zd - %@",i,[NSThread currentThread]);
        if(i == 2){
            //sleepUntilDate:使当前线程休眠到指定日期
            [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
            NSLog(@"蓝瘦,香菇");
        }
        if(i == 3){
            //使当前线程强制死亡
            [NSThread exit];
        }
    }
}

线程属性

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"主:%tu",[NSThread currentThread].stackSize/1024);

    //新建线程
   NSThread *thread =  [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    //设置线程对象的name属性,标识一个唯一的线程对象,方便跟踪
    thread.name = @"t1";
    //设置线程对象的优先级:浮点数0.0-1.0,最高是1.0,默认是0.5
    //线程优先级不能决定线程执行的先后顺序,只能决定某个线程有更多机会被CPU先调度执行完,概率事件
    //注意:实际开发中,千万不要设置优先级,或者服务器质量,会出现意想不到的问题;使用默认的,让系统自己来处理
    //thread.threadPriority = 1.0;
    //threadPriority:在目前即将被废弃,使用qualityOfService替代
    thread.qualityOfService = NSQualityOfServiceUserInteractive;
    
    
    //就绪状态
    [thread start];
    //新建线程
    NSThread *thread2 =  [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    thread2.name = @"t2";
    thread2.threadPriority = 0.1;
    //就绪状态
    [thread2 start];
}
-(void)demo{
    //stackSize:线程占用内存空间大小
    NSLog(@"子:%tu",[NSThread currentThread].stackSize/1024);
    
    
    for (NSInteger i = 0; i < 5; i++) {
        NSLog(@"%zd -- %@",i,[NSThread currentThread]);
    }
    /*模拟崩溃:演示name属性
    NSMutableArray *arrM = [NSMutableArray array];
    NSObject *obj = nil;
    [arrM addObject:obj];
     */
    
    
}

资源共享-线程安全

共享资源
资源:一个全局对象,一个全局变量,一个文件
共享:可以被多个对象访问
共享资源:可以被多个对象访问的资源,比如全局对象,变量,文件
在多线程的环境下,共享的资源,可能被多个线程共享,也就是多个线程可能会操作同一块资源

@interface ViewController ()

//总票数:共享资源
@property (assign,nonatomic) NSInteger tickets;

@end
//需求驱动开发:没有需求,无从开发
//需求:开发买票系统
//先要有开发逻辑,后有开发代码
//开发逻辑:先干什么,后干什么
//分析需求,得到发的逻辑
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化总票数
    self.tickets = 20;
    
    
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    //[self sellTickers];
    //售票口1
   NSThread *thread1 =  [[NSThread alloc] initWithTarget:self selector:@selector(sellTickers) object:nil];
    thread1.name = @"t1";
    [thread1 start];
    //售票口1
    NSThread * thread2 =  [[NSThread alloc] initWithTarget:self selector:@selector(sellTickers) object:nil];
    thread2.name = @"t2";
    [thread2 start];
}
//卖票主方法
-(void)sellTickers{
    
    while (YES) {
        //互斥锁/同步锁:使用了线程同步技术
        //特点:可以保证被锁定的代码,同一时间只有一个线程可以访问
        //self:表示互斥锁的参数,互斥锁的参数,又叫做锁对象
        //锁对象:任何继承自NSObject的对象,都可以作为互斥锁的参数,内部有把锁,默认是开启的
        //锁对象必须是全局的对象;self是最方便获取的全局的锁对象
        //局部锁对象是锁不住的,因为每次线程进来之前会新建一把锁
        //提示:加锁的事情,不能再客户端操作;是服务器加锁的,多线程资源共享,绝大多数是在服务器发生;
        //提示:加锁是牺牲了性能,保证了安全,客户端的性能不能轻易牺牲
        
        //创建一个局部的锁对象
        NSObject *obj = [[NSObject alloc] init];
        @synchronized (self) {
            //判断是否有余票
            if(self.tickets > 0 ){
                //模拟网络延迟:没有实际意义,仅仅是模拟延迟而已,可以忽略
                //[NSThread sleepForTimeInterval:1.0];
                //如果有余票,卖一张
                self.tickets = self.tickets - 1;
                //提醒余票
                NSLog(@"%zd--%@",self.tickets,[NSThread currentThread]);
                
            }else{
                //如果没有余票,提醒用户无票
                NSLog(@"无票了");
                break;
            }

        }
    }
    
}

原子属性

@interface ViewController ()

//非原子属性
@property (nonatomic,strong) NSObject *obj1;
//原子属性
@property (strong) NSObject *obj2;//一旦重写了getter和setter方法,系统不会自动生成带下划线的成员变量
//需要自己合成带下划线的成员变量

@end
/*
 原子属性:单写多读
 单写多读:同一时间只有一个线程可以访问setter方法,但是可以有多个线程访问getter方法
 注意:原子属性的setter方法是线程安全的,getter方法是线程非安全的
 setter方法内部有自旋锁
 自旋锁:看不见的,由系统封装的
    可以保证被锁定的代码,同一时间只能有一个线程可以访问
 一旦外面的线程,发现代码被自旋锁锁定,外面的线程会以死循环的方式等待开锁
 互斥锁:
    可以保证锁定的代码,同一时间只能有一个线程可以访问
    一旦外面的线程,发现代码被互斥锁锁定,外面的线程就会进入就绪状态,
 */

@implementation ViewController

@synthesize obj2 = _obj2;
-(void)setObj2:(NSObject *)obj2{
    //由于自旋锁看不见,所以可以使用互斥锁替换,演示单写多读
    @synchronized (self) {
         _obj2 = obj2;
    }
   
}
-(NSObject *)obj2{
    return _obj2;
}

异步下载网络图片

@interface ViewController ()

//根视图
@property (strong,nonatomic) UIScrollView *scrollView;

//图片子视图
@property (nonatomic,weak) UIImageView *imgView;

@end
//需求:异步下载网络图片,图片可以滚动,滚动视图要是根视图
//分析需求:下载是耗时的操作,需要在子线程异步执行
//准备控件的工作UIImageView/UIScrollView(根视图)
//
@implementation ViewController
/*
 loadView:优先于viewDidLoad
 loadView:当self.view == nil 时调用
 loadView:不需要调用super
 */
-(void)loadView{
    //创建根视图
    self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    //把根视图替换成scrollView
    self.view = self.scrollView;
    self.scrollView.backgroundColor = [UIColor redColor];
    
    //创建图片子视图
    UIImageView *imgView = [[UIImageView alloc] init];
    [self.view addSubview:imgView];
    //给属性赋值:一定不能少
    self.imgView = imgView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //[self laodImageData];
    [self performSelectorInBackground:@selector(loadImageData) withObject:nil];
}

//在子线程下载图片,在主线程更新UI,是线程间通信的一种
//线程间通信:一个线程把他执行的结果,传递到另外的一个线程

//下载图片的主方法
-(void)loadImageData{
    //URL
    NSURL *url = [NSURL URLWithString:@"http://img05.tooopen.com/images/20150202/sy_80219211654.jpg"];
    //发送网络请求,获取图片二进制数据,是个耗时的操作
    NSData *data = [NSData dataWithContentsOfURL:url];
    //image就是子线程执行的结果,需要传递到主线程
    UIImage *image = [UIImage imageWithData:data];
    
    //下载完成后,通知主线程刷新UI
    //waitUntilDone:是否等到updateUI执行完,再执行后面的代码,一般传入NO
    [self performSelectorOnMainThread:@selector(updataUI:) withObject:image waitUntilDone:NO];
    NSLog(@"后面的代码");
}
-(void)updataUI:(UIImage *)image{
    //下载完成后,刷新UI
    self.imgView.image = image;
    [self.imgView sizeToFit];
    self.scrollView.contentSize = image.size;
}

异步下载

info.plist中添加代码,允许请求网络
NSAppTransportSecurity

NSAllowsArbitraryLoads

你可能感兴趣的:(多线程1)