进击的KFC:UI(13)多线程

一.概念.
1.进程:正在活动(或者运行)的一个应用程序就是一个进程
2.主线程 : 每一个进程 都能至少有一条线程 叫 主线程'
3.线程:每一个线程都是相互独立的,可以执行任务 , 除了主线程以外的,都叫子线程.
4.子线程可以有多个,但是线程是耗费资源的. 在我们 ios开发中一般不超过5条,注:3条差不多了.
5.程序退出后 会清空线程的任务, 就是消灭应用程序对应的那条进程
6.主线程 操作什么样的任务??  答:UI界面,按钮的点击,屏幕的滚动(一切用户看的见的 都要在主线程当中去操作). 而那些比较大的耗时的操作或者用户看不到的操作可以放到线程当中去做,比如:下载,解压缩,读取大型数据等 可以在子线程中去操作
7.多线程的原理:CPU在工作时 : 同一时间内,只执行一个任务,之所以可以造成多条线程一起执行任务的假象,是CPU在进行高速的切换(在线程之间进行调度)来达到多个任务一起执行的效果
8说说多线程的优点:1.可以大大提高执行任务的效率2.可以让用户有更好的体验
缺点:如果大量的开辟线程,会造成程序的卡顿(耗费过量的资源) 物极必反
9. (1)查询当前的线程:[NSThread currentThread]    如果输出结果中number = 1,则表示在主线程中,反之,则表示在子线程中
    (2)线程休眠:[NSThread sleepForTimeInterval:5];
    (3)立即结束当前线程 : [NSThread exit];
    (4)判断当前线程 是否是主线程:[NSThread isMainThread];
10.开辟子线程的方法
   // 1.该方法在哪个线程调用 该方法的线程 就是 哪个线程
[self performSelector:@selector(downLoad:) withObject:@"123"];
  //  2.开辟了一个子线程 (在后台执行一个方法) 该方法是基类提供的 只要是对象 都能调用
 [self performSelectorInBackground:@selector(downLoad:) withObject:@"123"];
   #pragma mark --- NSTread
 // 3.NSThread
  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downLoad:)object:@“123”];
  [thread start];  // 开始执行子线程
 // 4.开辟子线程 并且这个方法 不用写开始 , 系统自动执行线程中的任务
  [NSThread detachNewThreadSelector:@selector(downLoad:)toTarget:self withObject:@“123”];
 // tips:(这些开辟的方法,每touch一次,都会开辟一次子线程,都不常用)
#pragma mark — 常用的开启多线程的方法
优势:不用程序员 管理 线程的生命周期
1.NSOperation:封装了一下GCD的方法 提供OC语言来使用GCD
2.GCD:是苹果推荐的 可以最大化的发挥多核CPU 是C语言的函数
NSOperation 是一个抽象类 : 子类 : NSBlockOperation 和 NSInvocationOperation
概念:
1.线程队列:(任务中心 可以执行很多任务)
    串行队列(主线程相当于一个串行队列 队列中的任务需要执行完毕一个后,再执行下一个)
     并行队列:队列中的任务需要并行执行 . 同时开始,但未必同时结束
2.同步:没有开启子线程的能力
   异步:具备开启子线程的能力
3.什么是任务??  比如下载,打印 都是个任务
4.注意”使用GCD 或者 NSOperation 咱们要做的就是往合适的队列中添加任务,其他的根咱们无关,系统会根据队列的类型 来开启线程去完成任务
5.线程建的通信: 在子线程中 完成耗时的操作 , 完成后 需要回到主线程里进行UI的刷新 这里就需要线程的通信
6.主线程 和 子线程是独立的
7.具体的实现
  - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{   // 需要把 NSInvocationOperation 对象 放进一个队列里 才能开启子线程
    // 创建一个队列
   NSOperationQueue *queue = [[NSOperationQueue alloc] init];
     // 给队列提供了一个 可以设置最大并发数
    // 一次并发执行:两个任务
    // 如果一次并发执行1个任务,就相当于串行队列了(所以我们没有设置具体的串行还是并行)
         [queue setMaxConcurrentOperationCount:2];
         // 开启一个线程 (相当于一个任务,任务是downLoad)
        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downLoad:) object:@"123"];
        // 把任务添加到队列里面
    // 只需要把任务添加进队列中,不需要咱们来操作,会自动执行
    // 可以发现,每次点击self.view不会再开辟线程(比NSThread的好处)
    // 要注意:不是添加任务 就开启一个线程  具体的这个操作 是系统给咱们做的 有的线程会被系统重复利用(优化)
     [queue addOperation:operation];
    // 第二种添加方法:
      [queue addOperationWithBlock:^{
        // block中就是你要添加的任务
        NSLog(@"任务2所在线程为---%@",[NSThread currentThread]);
    }];
    // 第二种NSBlockOperation来创建子线程,并添加到队列当中
       NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
       // block中:是 要添加的任务
        NSLog(@"任务4所在线程为---%@",[NSThread currentThread]);
    }];
    // 添加进队列
    [queue addOperation:blockOperation];
}

  #pragma mark —  开启子线程 请求一张图片 请求完成后 回到主线程 显示图片(刷新UI)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  // 触摸的时候,开启一个子线程
    [self performSelectorInBackground:@selector(imageDownLoad:) withObject:kImageURL];
}
- (void)imageDownLoad:(NSString *)str
{

    NSLog(@"我在子线程%@",[NSThread currentThread]);

    // 实现请求图片的方法

    // 图片的请求 (请求data 就是耗时操作)
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:str]];

    UIImage *image = [UIImage imageWithData:data];

    // 直接在子线程中显示图片
    // self.imageV.image = [UIImage imageWithData:data];

    // 或者 回到主线程里显示图片
    // 回到主线程刷新1
    // waitUntilDone 是否等待回到主线程的方法 yes指:执行完毕后 再向下执行
//    [self performSelectorOnMainThread:@selector(ShowImage:) withObject:image waitUntilDone:NO];
//    NSLog(@"真的回主线程了吗");

    // 回到主线程刷新方法2
    [self performSelector:@selector(ShowImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];

}

- (void)ShowImage:(UIImage *)image
{
    // 显示图片
    self.imageV.image = image;
    NSLog(@"是否回到主线程?---%@",[NSThread currentThread]);

}

  #pragma mark — 重点:GCD
概念:
1.队列: 并行队列:系统提供了一个全局的并行队列(整个应用都可以使用) 如果不想用 也可以自己创建出来
        串行队列:需要自己创建一个出来
2.任务:同步:不具备开启线程的能力
      异步:具有开启线程的能力
分为4种情况:
 1.并行  --- 异步任务 (主要用的)
 2.并行  --- 同步任务
 3.串行  --- 异步任务
 4.串行  --- 同步任务
#pragma mark ----  1.并行  --- 异步任务
// 开启子线程 并且 任务并发
- (void)asyncGlobalQueue
{
    //async 是指异步的    Global并行的
           // 1.获取系统的 全局并行队列
    // dispatch_queue_attr_t 可以理解为队列的类型
         // 参数1 long identifier:是cpu切换的频率高低(切换的优先级)
    // 参数2 unsigned long flags:是个预留参数 可以调0
dispatch_queue_t queue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    // 2.添加异步任务
    // 参数1:给哪一个队列添加任务
     dispatch_async(queue, ^{

        // 参数2:block : 要添加的任务
        NSLog(@"任务1, 在线程 ---- %@",[NSThread currentThread]);
    });
}

#pragma mark ----  2.并行  --- 同步任务
// 没有开启子线程 顺序执行 相当于变成了一个串行的队列
- (void)syncGlobalQueue
{
    // 1.创建一个并行队列
    // 参数1:填你队列的标识符 反向域名 com.lanou3g.www
    // 参数2:填你要创建的队列的类型(串(serial)/并(concurrent))
      dispatch_queue_t queue = dispatch_queue_create("com.lanou3g.www", DISPATCH_QUEUE_CONCURRENT);
     // 2.添加同步任务
        dispatch_sync(queue, ^{

        // 参数2:block : 要添加的任务
        NSLog(@"任务1, 在线程 ---- %@",[NSThread currentThread]);
    });
   // 3.(MRC下)创建地队列是需要释放的 (ARC是不用释放的)
    dispatch_release(queue);
}

#pragma mark ----  2.串行  --- 异步任务
// 开启了子线程  串行顺序执行
- (void)asyncSerialQueue
{

    // 创建一个串行的队列
    dispatch_queue_t queue = dispatch_queue_create("com.lanou3g.www", DISPATCH_QUEUE_SERIAL);
    // 添加异步任务
    dispatch_async(queue, ^{

       NSLog(@"任务1, 在线程 ---- %@",[NSThread currentThread]);
    });
    // 释放队列
    dispatch_release(queue);
}

#pragma mark ----  4.串行  --- 同步任务
// 没有开启子线程 都在主线程里顺序执行
- (void)syncSerialQueue
{
    // 创建一个串行的队列
    dispatch_queue_t queue = dispatch_queue_create("com.lanou3g.www", DISPATCH_QUEUE_SERIAL);
    // 添加同步任务
    dispatch_sync(queue, ^{

         NSLog(@"任务1, 在线程 ---- %@",[NSThread currentThread]);

    });
    // 释放队列
    dispatch_release(queue);
}

- (void)viewDidLoad {
    [super viewDidLoad];
{
     #pragma mark --- GCD开启子线程加载图片 回到主线程刷新UI
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^{

        // 做请求的耗时操作 (用同步请求)
        // 在子线程中的同步请求 就 相当于异步请求
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://pic2.ooopic.com/01/03/51/25b1OOOPIC19.jpg"]];


        UIImage *image = [UIImage imageWithData:data];
        // 回到主线程,刷新ui  dispatch_get_main_queue() : 回到主线程
        dispatch_async(dispatch_get_main_queue(), ^{

           // 回到主线程 要执行的任务
            // 刷新UI
            ...
        });
    });

}

     #pragma mark --- 实现模拟卖票
 // 问题:
    // 多个线程 同时对一个数据进行操作
    // 解决方案: 访问数据是 加一个锁 只能有1个线程访问 等这个线程访问结束后 才能让下一个线程再访问 保证数据被访问的安全
    //需要添加同步锁 (互斥锁
//    NSLock  *lock = [[NSLock alloc] init];
//    [lock lock];// 上锁
//
//    // 中间就是被上锁的部分
//
//    [lock unlock];// 开锁

 self.lock = [[NSLock alloc] init];

    // 初始化票的总数
    self.ticketCounts = 100;

    [self tickets];

}

// 模拟卖票
- (void)tickets
{
    // 创建并行队列 添加异步任务

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^{

        // 调用卖票方法
        [self sellTicket];

    });
    dispatch_async(queue, ^{

        [self sellTicket];

    });
    dispatch_async(queue, ^{

        [self sellTicket];

    });

}

// 卖票方法
- (void)sellTicket
{
//
//    while (1) {
//        [_lock lock]; // 上锁
//        if (self.ticketCounts > 0) {
//            self.ticketCounts--;
//            NSLog(@"剩余的票数---%ld      谁丶买走了票---%@",self.ticketCounts,[NSThread currentThread]);
//        } else {
//            // 票没了
//            return;
//        }
//        [_lock unlock]; // 解锁
//    }



//    // 第二种 互斥锁的形式
//    @synchronized(self){
//
//        // 上锁的部分
//
//    }
//
//
//

    while (1) {

        // 互斥锁
        @synchronized(self){

            // 上锁的部分
            if (self.ticketCounts > 0) {
                self.ticketCounts--;
                NSLog(@"剩余的票数---%ld      谁丶买走了票---%@",self.ticketCounts,[NSThread currentThread]);
            } else {
                // 票没了
                return;
            }
        }
    }
}

     #pragma mark ---  // 带线程保护的单例写法
// 利用GCD来初始化单例方法
// 带线程保护的单例写法
+(MyHandle *)shareHandle
{
    static MyHandle *handle = nil;

    // 系统写的代码块 , 输入dispatch_once即可
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 执行的任务 在整个程序运行期间 只执行一次 (第二次 不走这个代码块了,和上面一样的效果)
        // 并且只允许一个线程访问 (自带 线程保护)

        handle = [[MyHandle alloc] init];
    });


    return handle;

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值