iOS之多线程

  • 程序:由源代码生成的可执行应用
  • 进程:一个正在运行的程序可以看做一个进程,进程拥有独立运行所需的全部资源
  • 线程:程序中独立运行的代码段
  • 一个进程是由一个或多个线程组成,进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行

单线程:

  • 每个正在运行的程序(进程),至少包含一个线程,这个线程叫主线程
  • 主线程在程序启动时被创建,用于执行main函数
  • 只有一个主线程的程序,称作单线程程序
  • 在但线程程序中,主线程负责执行程序的所有代码(UI展现,刷新,网络请求,本地存储等等),这些代码只能顺序执行,无法并发执行

多线程:

  • 拥有多个线程的程序,称作多线程程序
  • iOS允许用户自己开辟新的线程,相对于主线程来讲,这些线程,称作子线程
  • 可以根据需要开辟若干个子线程
  • 子线程和主线程都是独立的运行单元,各自的执行互不影响,因此能够并发执行(伪并发执行,是cpu在多个线程快速的跳跃)
  • 主线程还被称为UI线程,数据的处理放在子线程中,UI的刷新放在主线程中
  • 使用多线程的优点 : 资源利用率更好,程序设计在某些情况下更简单,程序响应更快
  • 使用多线程的缺点 : 多线程尽管提升了性能但是存在一些访问限制,比如线程同步,线程互斥等; 多线程在使用用的时候, 最终要回到主线程刷新UI的, 如果开辟过多的多线程, 会造成CPU的消耗

单线程和多线程的区别:

  • 单线程程序:只有一个线程,即主线程,代码顺序执行,容易出现代码阻塞(页面假死)
  • 多线程程序:有多个线程,线程间独立运行,能有效的避免代码阻塞,并且提高程序的运行性能

    注意:iOS中关于UI的添加和刷新必须在主线程中操作,是为了防止多个线程同时操作UI

iOS平台下的多线程

NSThread

  • NSThread是一个轻量级的多线程,它有两种创建方式
    • 第一种:初始化方式
- (void)threadMethod {
    //初始化tread 当线程是我们手动开辟的时候,需要我们自己来管理内存
    //Target在谁里面实现填谁
    //object : 回调方法的参数
     myThread = [[NSThread alloc] initWithTarget:self selector:@selector(forAction) object:nil];
    //为开辟的子线程命名
    myThread.name = @"first";
    //如果是使用初始化的方式创建的子线程,那么需要我们手动开启线程
    [myThread start];
     //取消当前线程 取消线程之后,子线程中的代码照样会执行,我们需要在合适的地方使用[myThread canceled]来判断当前线程是否被取消
//    [myThread cancel];
}
- 第二种:遍历构造器
//for循环按钮
- (void)forAction {
    //当代码片段是在我们自己手动开辟的线程中执行的,需要我们手动来管理内存,这个时候,我们需要将代码片段放在自动释放池中
    @autoreleasepool {
//        for (int i = 0; i < 100; i++) {
//            //线程被取消,但是代码还在执行,所以需要进行以下的判断
//            if (myThread.cancelled) {
//                break;
//            }
//            NSLog(@"*******%@",[NSThread currentThread]);
//        }
        NSLog(@"我是子线程---%@",[NSThread currentThread]);
        //通过NSObject提供的方法回主线程
        //withObject:回调方法的参数
        //waitUntilDone:如果是YES,那么就会回调方法中所有的代码执行完成了,才会执行当前线程剩余的代码,如果为NO,那么就不会等待回调方法中的代码执行完成
        //如果底下剩余代码跟这行代码有没有依赖关系
        [self performSelectorOnMainThread:@selector(backMainThread) withObject:nil waitUntilDone:NO];
        NSLog(@"疯狂的萨克斯");
    }
}
  • 常用方法
 //获取当前线程
    [NSThread currentThread];
    //获取主线程
    [NSThread mainThread];
    //线程休眠2秒
    [NSThread sleepForTimeInterval:2];

NSThread需要注意的有一下几点

  • 每个线程都维护找与自己对应的NSAutoreleasePool对象,将其放在线程栈的栈顶,当线程结束时,会清空自动释放池
  • 为保证对象的及时释放,在多线程方法中需要添加自动释放池
  • 在应用程序打开的时候,系统会为主线程创建一个自动释放池
  • 我们手动创建的子线程需要我们手动添加自动释放池

NSOperation和NSOperationQueue

NSOperation

  • NSOperation类,在MVC中属于M,是用来封装单个任务相关的代码和数据的抽象类
  • 因为它是抽象的,不能够直接使用这个类,而是使用子类(NSInvocationOperation和NSBlockOperation)来执行实际任务
  • NSOperation(含子类),只是一个操作,本身无主线程,子线程之分,可在任意线程中使用,通常与NSOperationQueue结合使用

NSInvocationOperation

  • NSInvocationOperation是NSOperation的子类
  • 封装了执行操作的target和要执行的action
//operation的学习,operation本身跟线程无关,只是一个任务操作
- (void)operationStudy {
    //target - action方式创建一个任务
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printAction) object:nil];
    //如果任务不在队列中,那么就需要我们手动启动任务
    [invocationOperation start];
}
//任务的回调方法
- (void)printAction {
    NSLog(@"当前线程为%@",[NSThread currentThread]);
}

NSBlockOperation

  • NSBlockOperation是NSOperation的子类
  • 封装了需要执行的代码块
- (void)blockOperationStudy {
    //block的方式创建一个任务
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程是:%@,是否主线程:%d",[NSThread currentThread],[[NSThread currentThread] isMainThread]);
    }];

    for (int i = 0; i < 5; i++) {
        //当使用addExecutionBlock为当前任务添加额外的block的时候,那么此block所在的线程就是在当前线程或者其他线程
        [blockOperation addExecutionBlock:^{
            NSLog(@"excu-----%d-----%@",i,[NSThread currentThread]);
        }];
    }
    //收尾
    blockOperation.completionBlock = ^(){
        NSLog(@"我就想看结果");
    };
    //开始执行
    [blockOperation start];
}

NSOperationQueue

  • NSOperationQueue是操作队列,用来管理一组Operation对象的执行,会根据需要自动为Operation开辟合适数量的线程,以完成任务的并行执行
  • 其中NSOperation可以调节它在队列中的优先级(使用addDependency:设置依赖关系)
  • 当最大并发数设置为1的时候,能实现线程同步(串行执行)
//NSOperationQueue学习,操作队列,实现多线程的一种技术手段,thread需要手动管理内存,队列不需要,thread每次只能开辟一个子线程,队列可以调度,分配,管理多个线程

- (void)operationQueueStudy {
    //创建一个其他队列,通过初始化的方式创建的队列只能是其他队列
    NSOperationQueue *otherOperationQueue = [[NSOperationQueue alloc] init];
    //队列中只能添加任务
    //创建一个任务
    NSBlockOperation *blockOperation_1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前所在线程1%@",[NSThread currentThread]);
    }];
    NSBlockOperation *blockOperation_2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前所在线程2%@",[NSThread currentThread]);
    }];
    NSBlockOperation *blockOperation_3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前所在线程3%@",[NSThread currentThread]);
    }];
    NSBlockOperation *blockOperation_4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前所在线程4%@",[NSThread currentThread]);
    }];
    NSBlockOperation *blockOperation_5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前所在线程5%@",[NSThread currentThread]);
    }];
    //希望某几个任务执行的时候有先后关系,那么我们就可以为任务添加依赖,不能互相添加为依赖关系
    [blockOperation_1 addDependency:blockOperation_5];

    //通过设置最大并发数,让一组任务可以有序的执行,最大并发数的默认值为-1,意思为无限
//    otherOperationQueue.maxConcurrentOperationCount = 1;

    //将任务添加到队列,在队列中的任务不需要手动启动
    [otherOperationQueue addOperation:blockOperation_1];
    [otherOperationQueue addOperation:blockOperation_2];
    [otherOperationQueue addOperation:blockOperation_3];
    [otherOperationQueue addOperation:blockOperation_4];
    [otherOperationQueue addOperation:blockOperation_5];

    //主队列
    [self mainOperationQueue];
}
#pragma mark - operationQueue主队列
//主队列的作用:返回主线程
- (void)mainOperationQueue {
    NSLog(@"当前线程为----------------%@",[NSThread currentThread]);
    //先获取主队列,在主队列的任务都是在主线程中执行的,其他队列中的任务都是在子线程中执行
    NSOperationQueue *myMainQueue = [NSOperationQueue mainQueue];
    //添加任务
    NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-------1%@",[NSThread currentThread]);
    }];
    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-------2%@",[NSThread currentThread]);
    }];
    NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-------3%@",[NSThread currentThread]);
    }];
    //最大并发数
    myMainQueue.maxConcurrentOperationCount = 1;
    //把任务添加到主队列中
    [myMainQueue addOperation:blockOperation1];
    [myMainQueue addOperation:blockOperation2];
    [myMainQueue addOperation:blockOperation3];
}

GCD(Grand Central Dispatch)

  • Grand Central Dispatch是Apple开发的一种多核编程技术.主要用于优化应用程序以支持多核处理器以及其他对称多处理系统
  • GCD提供函数实现多线程开发,性能更高,功能更强大
  • 它首次发布在Mac OS X 10.6, iOS 4及以上也可用

  • 任务:具有一定功能的代码段. 一般是一个block或者函数

  • 分发队列:GCD以队列的方式进行工作,遵守FIFO
  • GCD会根据分发队列的类型,创建合适数量的线程执行队列中的任务

  • dispatch queue分为以下两种

    • SerialQueue: 一次只执行一个任务.Serial Queue通常用于同步访问特定的资源或数据.当你创建多个serial Queue时,虽然它们各自是同步执行的,但Serial Queue与Serial Queue之间是并发执行的,Serial Queue能实现线程同步
//gcd的串行队列  有序的执行一组任务
- (void)serialQueue {
    //创建一个串行队列
    //第一个参数:当前队列的标签
    //第二个参数:当前队列的类型
    dispatch_queue_t serialQueue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);
    //创建任务
    //第一个参数:当前任务所在的队列
    //第二个参数:任务的block回调,逻辑代码在block中
    dispatch_async(serialQueue, ^{
        NSLog(@"马克打鸟窝");
        NSLog(@"---------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"抓到鸟蛋烤着吃");
        NSLog(@"---------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"一个不够吃两个");
        NSLog(@"---------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"两个不够继续打");
        NSLog(@"---------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"鸟蛋没了");
        NSLog(@"---------%@",[NSThread currentThread]);
    });
}
  • Concurrent: 可以并发地执行多个任务,但是遵守FIFO
    • 第一种方式
//gcd的并行队列
- (void)concurrent {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
    //通过回调函数的方式来实现任务
    //第二个参数:回调函数的参数
    dispatch_async_f(concurrentQueue, @"赵薇和乐乐开始愉快的生活", function);

    dispatch_async(concurrentQueue, ^{
        NSLog(@"赵薇和乐乐睡了");
        NSLog(@"---------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"赵薇哭,乐乐郁闷的抽烟");
        NSLog(@"---------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"赵薇去了泰国,为了真爱");
        NSLog(@"---------%@",[NSThread currentThread]);
    });
}

//声明一个函数
void function(void* content) {
    NSLog(@"%@",content);
    NSLog(@"---------%@",[NSThread currentThread]);
}
  • 第二种方式
//在实际使用中我们常用GCD的方式:
- (void)usefulMethod {
    //系统提供的一个全局队列
    //第一个参数:全局队列的优先级
    //第二个参数:预留参数,当前并没有用
    dispatch_queue_t oneQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(oneQueue, ^{
        //在此处执行耗时的操作.......
        //当操作执行完成之后我们需要回到主线程刷新UI,gcd回主线程的方式
        dispatch_async(dispatch_get_main_queue(), ^{
            //已经回到主线程,在此处刷新UI
        });
    });
}
  • GCD功能
- (void)otherFunctionsOfGCD {
//    dispatch_group_t 主要用于把一些不想关的任务归为一组
//    组里面放的是队列
//    dispatch_group_async 作用是往组里面的队列添加任务
//    dispatch_group_notify 作用: 监听组里面的任务,等到组里面的任务全部执行完成之后,才会执行其他任务
    //1.创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //2.创建组
    dispatch_group_t group = dispatch_group_create();
    //3.往组里面的队列添加任务(注意:在执行notify之前最起码要向队列中放置一个任务才可以,否则,notify里面的任务不会等待小组里面的其他任务执行完成才执行)
    dispatch_group_async(group, queue, ^{
        NSLog(@"这是第一个任务,线程是:%@, 是否主线程%d",[NSThread currentThread],[[NSThread currentThread] isMainThread]);
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"这是最后一个任务,组里面的其他任务都执行完毕之后,我就会执行");
    });
}
- (void)sqliteAndGCD {
     //数据库的读取   可以并发执行通过GCD里面的并行队列去实现
     //数据库的写入    只能串行执行,通过GCD里面的串行队列去实现
     //但是真正的项目,肯定是既有数据的读取,也有数据库的写入,如何解决这个问题: dispatch_barrier_async 在它之前的任务可以并发去执行,再他之后的任务也可以并发去执行
    dispatch_queue_t queue = dispatch_queue_create("concurrentTest", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"这是第一个读取数据的任务 线程是:%@, 是否主线程: %d",[NSThread currentThread],[[NSThread currentThread] isMainThread]);
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@"哥哥我正在给数据库写东西");
    });

    dispatch_async(queue, ^{
        NSLog(@"这是第二个读取数据的任务 线程是: %@, 是否主线程: %d",[NSThread currentThread],[[NSThread currentThread] isMainThread]);
    });
}
//dispatch_once : 该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用). 它还接收一个希望在应用的声明周期内仅被调度一次的代码块
//dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要诸如@synchronize之类的来防止使用多个线程或者队列时不同步的问题,通常用于单例

//static SharedData *data = nil;
//+ (SharedData*)defaultData {
//    //block中的代码只会执行一次
//    static dispatch_once_t onceToken;
//    dispatch_once(&onceToken, ^{
//        data = [[SharedData alloc] init];
//    });
//    return data;
//}

//async 不等block执行完,就去执行下面的代码
//sync 会等待block执行完成之后 才会执行block外面的代码
- (void)asyncAndSync {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
        NSLog(@"第一个任务");
    });
    NSLog(@"呵呵");

    dispatch_async(queue, ^{
        NSLog(@"第二个任务");
    });
    NSLog(@"哈哈");
}

线程间通信

线程间的通信分为两种:

  • 主线程进入子线程(前面的方法都可以)
  • 子线程回到主线程
//子线程回到主线程:
//第一种方法:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执⾏耗时的异步操作...
    dispatch_async(dispatch_get_main_queue(), ^{
    // 回到主线程,执⾏UI刷新操作
    });
});

//第二种方法:
[self performSelectorOnMainThread:@selector(backMainThread) withObject:nil waitUntilDone:NO];

线程互斥

  • 多线程并行编程中,线程间同步和互斥是一个很有技巧的也很容易出错的额地方
  • 线程间互斥应对的是这种场景
    • 多个线程操作同一个资源(即某个对象), 需要保证线程在对资源的状态(即对象的成员变量)进行一些非原子性的操作后, 状态仍然正确.
    • 典型的例子是”售票厅售票应用”. 售票厅剩余100张票, 10个窗口去卖这些票,这10个窗口,就是10条线程, 售票厅就是他们共同操作的资源,其中剩余的100张票就是这个资源的一个状态,线程买票的过程失去递减这个剩余数量的过程.

线程互斥的解决方案

  • 方法一 : @synchronized自动对参数对象加锁,保证临界区内的代码线程安全
  • 方法二 : NSLock
  • 方法三 : NSConditionLock 条件锁 可以设置条件
  • 方法四 : NSRecursiveLock 递归锁 多次调用不会阻塞已获取该锁的线程
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值