iOS——多线程编程详细解析

Posted brave-sailor

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS——多线程编程详细解析相关的知识,希望对你有一定的参考价值。

基本定义:
程序:由代码生成的可执行应用。(例如QQ.app)
进程:一个正在运行的程序可以看做是一个进程。 (例如:正在运行的QQ 就是一个进程),进程拥有独立运行所需要的全部资源。
线程: 程序中独立运行的代码段。(例如: 接收QQ 消息的代码)
一个进程是由一个或者多个线程组成。进程只负责资源的调度和分配,线程才是程序真正懂得执行单元,负责代码的执行。

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

多线程
拥有多个线程的程序,称作多线程程序。
ios 允许用户自己开辟新的线程,相对主线程来讲,这些线程,称作子线程。
可以根据需要开辟若干子线程
子线程和主线程都是独立运行单元,各自的执行互不影响,因此能够并发执行。

单、多线程区别
单线程程序:只有一个线程,代码顺序执行,容易出现代码阻塞(页面假死)。
多线程程序:有多个线程,线程间独立运行,能有效地避免代码阻塞,并且提高程序的运行性能。
注意:
1. iOS 中关于UI 的添加和刷新必须在主线程中操作。
2. iOS 默认给主线程分配1M 的栈空间,默认给子线程分配512k 的空间 (分配字节数必须是4k 的整数倍)
3. 分配的空间用来存放线程中为变量开辟的空间,一般情况下足够使用。
4. 与栈空间使用方式不同,主线程、子线程共用同一块堆空间。
5. 子进程必须要加自动释放池。
原因:
1. 释放池释放的是堆区内存,虽然主子线程的堆区共同管理,但是两者之间并没有牵扯。 程序入口处默认设置了自动释放池,由主线程负责执行代码,子线程新开辟的内存不在主线程管辖范围内,(线程间互不干扰),所以子线程为对象开辟的空间不会被自动释放,要手动为子线程手动书写释放池。
2. 书写@autoreleasepool 原因: 短时间内使用静态方法开辟大量的内存空间或者循环使用便利构造器, 会造成内存瞬间积聚,程序crash 掉。

多线程的种类 2种
脱离线程: 线程执行完之后,不被挂起.线程结束后被销毁
非脱离线程 : 线程执行完之后被挂起。等待唤醒,不销毁。主线程一定是非脱离线程
iOS 实现多线程方法种类
1.NSObject
2.NSThread
3.NSOperationQueue
4.GCD

1. NSObject 实现异步后台执行
NSObject 中存放了一个最简单的后台执行的方法
后台执行某个方法
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)

//withObject数字可以通过 calculate:的参数传到calculate方法中
    [self performSelectorInBackground:@selector(calculate:) withObject:@"参数"];
//    isMainThread 是NSThread 的类方法, 返回值BOOL ,判断是不是主线程
//    主线程中 输出 1
    NSLog(@"main ======%d",[NSThread isMainThread]);
 //子线程中输出 0.
    NSLog(@"isMainThread = %d",[NSThread isMainThread]);

2.NSThread是一个轻量级的多线

//    方式一 需要开启 (实际可以创建多个NSThread)
    NSThread * thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(calculate:) object:nil];
//    手动开启
   [thread1 start];
//     取消 (一般不需要取消)
    [thread1 cancel];
//    方式二 不需要手动开启
   [NSThread detachNewThreadSelector:@selector(calculate:) toTarget:self withObject:nil];
    NSLog(@"isMainThread = %d",[NSThread isMainThread]);

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

NSInvocationOperation是NSInvocation 的子类封装了执行操作的target和要执行的action。

//    方式一:NSInvocationOperation
NSInvocationOperation * invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(calculate:) object:nil];
//    手动开启
  [invocation start];
   此时在 calculate:方法中输出,输出结果为1,原因:calculate方法只是封装了主线程任务的一部分,仍然是主线程。
   NSLog(@"isMainThread = %d",[NSThread isMainThread])

NSBlockOperation是NSOperation的子类封装了需要执行的代码块

//    方式二:NSBlockOperation ,这种方式同上仍然只是封装的任务,并没有实现多线程。
//避免block 的请引用问题,所以使用__weak.
    __weak typeof (self) temp = self;
    NSBlockOperation * block = [NSBlockOperation blockOperationWithBlock:^{
        [temp calculate];
    }];
   [block start];

与NSOperationQueue 结合使用实现多线程 两个子线程并发执行
注意:此时两个 start 需要注释掉 ,到此才算真正实现多线程

//可以通过NSOperationQueue 创建对列,向队列中添加操作,完成多任务同时执行。
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
#warning 注意: 一定要提前设置好并发数,当最大并发数为1, 变成了串行
    [queue setMaxConcurrentOperationCount:2];
    [queue addOperation:invocation];
    [queue addOperation:block];
   //添加操作
    NSLog(@"operationCount = %lu",queue.operationCount);
    NSLog(@"operations = %@",queue.operations);

注意:
(1)NSOperationQueue 是操作队列,它用来管理一组Operation 对象的执行,会根据需要自动为Operation 开辟核实数量的线程,已完成任务的并发执行
(2)其中NSOperation 可以调节他在队列中的优先级
(3)当最大并发数设置为1 的时候,实现线程同步。

4.GCD (Grand Central Dispatch)
GCD 介绍:Grand Central Dispatch 简称GCD 。以优化应用程序支持多核心处理器和其他的对称多处理系统的系统。
GCD 属于函数级的多线程,性能更高,功能也更加强大

GCD核心概念
任务: 具有一定功能的代码段,一般是一个block 或者函数
分发队列: GCD 以队列的方式进行工作,FIFO。但并不是我们设置的进入顺序。是系统执行的顺序。
GCD 或根据有分发队列的类型,创建合适数量的线程执行队列中的任务。

GCD 中的两种队列
dispatch queue分为以下两种
1.SerialQueue:一次只执行一个任务。Serial queue通常用于同步访问特定的资源或者数据。 当你创建多个Serial queue时,虽然它们各自是同步执行的,但是Serial queue与Serial queue之间是并发执行的 。SerialQueue能实现线程同步。
2.Concurrent :可以并发地执行多个任务,但是遵守FIFO

GCD 功能*

//1. 主队列 :串行队列,并且执行的是主队列中任务
//2. 全局队列 :并行对列
//3. 自定义队列{串行队列,并行队列}
//   串行与并行没有关系
//  主队列与全局队列都是单例, 主队列串行,全局队列并行。 都是直接获   取,直接使用,不需要创建,也就是一直存在。

// 获取主队列

//     dispatch 分发,派遣 相当于int a = 10;
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

#pragma mark  向主队列中添加任务,任务会排队执行,验证主队列是串行队列
    dispatch_async(mainQueue, ^{
        NSLog(@"第一个任务");
    });
      dispatch_async(mainQueue, ^{
        NSLog(@"第二个任务");
    });

#pragma mark 延时,往队列中添加任务,任务不但会排队,还会在延迟的时间点执行
#warning 有代码块 sinppet
#warning  最好不要往主队列中添加延时 (也不要在串行队列中添加延迟,时间没到程序挂起,后面也没法执行)
//  10 秒 。时间不一定准,程序运行也需要时间
// 首先定义时间 time 10 * NSEC_PER_SEC ,表示10秒
 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,(int64_t)(10 * NSEC_PER_SEC));
    dispatch_after(time, mainQueue, ^{
        NSLog(@"第四个任务,10秒之后执行");
    });

#pragma   重复执行 n 次 参数 t 表示第几次执行。
    dispatch_apply(3, globeQueue, ^(size_t t ) {
        NSLog(@"重要的事情说三遍");
        NSLog(@"%zi",t );
    });

获取全局队列

  #pragma mark 2. 全局队列 :并行队列 执行方式先来先服务。 并行:时间片轮转
    dispatch_queue_t globeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  #pragma  将任务添加到队列中,并添加分组标记
//创建分组 group
    dispatch_group_t  group = dispatch_group_create();
 //添加任务
    dispatch_group_async(group, globeQueue, ^{
        NSLog(@"第一分组第一小队");
    });
    dispatch_group_async(group, globeQueue, ^{
        NSLog(@"第一分组第二小队");
    });
    dispatch_group_async(group, globeQueue, ^{
        NSLog(@"第一分组第三小队");
    });
 #pragma mark 将任务添加到队列中, 当某个分组的所有任务执行完之后,此任务才会执行(此说法经过实践不一定正确)
 #warning 注意:此方法 跟添加的队列的位置有关系,当处于队列末尾的时候,最后执行。位于当前分组的队列最前面时,结果不一定。
    dispatch_group_notify(group, globeQueue, ^{
        NSLog(@"你们先执行,我不着急");
    });

自定义队列
// 第一个参数: 队列标识符,可以获取到某一队列的标识符
// 第二个参数: 队列的类型,(串行或者并行)

自定义串行队列

#warning 注意:第一个参数是C 中的字符串,并不是OC 中的字符对象。其实是C 代码,所以也不会出现*
    dispatch_queue_t serialQueue =  dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
//    添加task
    dispatch_async(serialQueue, ^{
        NSLog(@"第一个任务");
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"第二个任务");
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"第三个任务");
    });
    //输出s 是serial 
    const char *s = dispatch_queue_get_label(serialQueue);
    NSLog(@"%s",s);

自定义并行队列

    dispatch_queue_t concurrentQueue = dispatch_queue_create("current", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });

dispatch_barrier_async()

#pragma mark 将此函数添加到队列的任务,需要等其执行完成后才可以执行其他队列任务
#warning 前提:必须是自己定义的并行队列,使用系统提供的不可以。
//    自定义并行队列
    dispatch_queue_t concurrentQueue2 = dispatch_queue_create("current2", DISPATCH_QUEUE_CONCURRENT);
//    必须等待barrier 函数执行完成, 余下的队列任务才可以执行
    dispatch_barrier_async(concurrentQueue2, ^{
        int sum = 0;
       for (int i = 0; i < 63555000; i++) {
           sum += i;
       }
        NSLog(@"执行完成");
    });

dispatch_sync()

#pragma mark 必须等待sync 里面的block 代码执行完成,余下的队列中函数才可以执行 dispatch_sync
    dispatch_sync(concurrentQueue2, ^{
        int sum = 0;
        for (int i = 0; i < 63555000; i++) {
            sum += i;
        }
        NSLog(@"都等着我。。。。。。。。。。。。");
    });

dispatch_async_f()

#pragma mark 将任务添加到队列中,任务是函数非 block
//    GCD 可以使用此函数,向队列中添加函数,让函数执行。
//    函数类型 void (*)(void *).无返回值有参数,但是参数类型是void
例如:
void function(void *name){
    NSLog(@"%s",name);
}
//    第二个参数:函数参数的内容。
//    第三个参数: 函数名字。
   dispatch_async_f(concurrentQueue2, "string", function);

dispatch_once()

#pragma mark 任务添加到队列中,但是任务在程序运行过程中,只执行一次 有代码快。
//    经验证得非常顽强 ,即使是在循环中也是只执行一次。
    static dispatch_once_t onceToken;
    for (int i = 0; i < 5 ; i++) {
        dispatch_once(&onceToken, ^{
            NSLog(@"单例");
        });
    }
    dispatch_once(&onceToken, ^{
        NSLog(@"单例2");
    });
}

注意:
// 只有主队列是 name = main,其它name = null
// 只有主队列是 number = 1, 其他不是1.number 顺序就是内部执行的顺序,并不是添加的顺序。串行队列中number是一样的值

//    主线程
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"currentThreadMain = %@",[NSThread currentThread]);
        NSLog(@"Main%d",[NSThread isMainThread]);
    });
    //    全局队列
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      NSLog(@"Globel = %@",[NSThread currentThread]);
      NSLog(@"globel = %d",[NSThread isMainThread]);
  });

//   并行
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent",DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
            NSLog(@"concurrentQueue = %@",[NSThread currentThread]);
            NSLog(@"concurrentQueue = %d",[NSThread isMainThread]);
    });
//    串行
    dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
     NSLog(@"serialQueue 1 = %@",[NSThread currentThread]);
    });

    dispatch_async(serialQueue, ^{
        NSLog(@"serialQueue2 = %@",[NSThread currentThread]);
    });
//输出结果:

技术分享图片
技术分享图片

总结:
1. GCD 功能实现有很多方法,虽然乱但是有规律
dispatch_async() 队列中添加
dispatch_after() 队列中添加 ,延迟执行
dispatch_apply()队列中添加 ,重复执行
dispatch_group_async() 先创建分组,在将任务添加到队列中,为分组标记
dispatch_group_notify() 在将任务添加到队列中,当某个分组的所有任务执行完之后,此任务才可以执行。
dispatch_barrier_async() ,此任务执行时其他任务等待。必须自己创建的多线程
dispatch_sync(),block 不执行完,下面代码不执行。功能同上类似
dispatch_once() 只执行一次 ,单例
dispatch_async_f() 可以添加任务是函数而不是block,也就是说函数可以作为功能参数
2. dispatch_once(),dispatch_after() 都有代码块。


































































以上是关于iOS——多线程编程详细解析的主要内容,如果未能解决你的问题,请参考以下文章

转:PHP并发IO编程之路

PHP并发IO编程之路

Linux多线程编程实例解析

Linux多线程编程实例解析

Linux多线程编程实例解析

Linux多线程编程实例解析