iOS多线程相关,同步异步的问题

1. 进程、线程、多线程

进程 : 可以简单的理解, 一个应用程序就是一个进程;

线程 : 可以理解为在app中往后运行的通道, 一个进程可以有多个线程;

多线程: 并不是所有的框架都支持多线程, 必须要有多核的cpu支持才行, 单核cpu即使开了多线程运行速度也不会有变化, 开的线程数有几种说法, 其一: 线程数为手机核数的2到3倍, 比如一个双核手机, 开线程数为4到6条; 其二: 根据网络状态, 如果不是wifi状态, 一般开3到4条, 如果是wifi状态,可以开启5到6条;

多线程的目的: 将耗时的操作放在后台, 而不影响主线程与用户的交互;

多线程示意图:

iOS多线程相关,同步异步的问题_第1张图片

多线程优缺点:

 优点:

1). 能适当提高程序的执行效率,

2). 能适当提高资源的利用率

3). 线程上的任务执行完成后, 线程会自动销毁

缺点:

1). 开启线程需要占用一定的内存空间 ( 默认情况下,每一个线程都占用512Kb ) ( 以前主线程占用1MB空间,现在主线程和子线程都只占用512KB )

2). 如果开启大量的线程,会占用大量的内存空间,CPU会在N个线程之间切换,消耗大量的CPU资源,每个线程被调度的次数会降低,线程的执行效率降低;

3). 程序设计更加复杂,比如线程间的通信、多线程的数据共享

2 . 串行并行,同步异步

串行简单理解就是一个人负责多件事情, 并行是多个人一起负责多个事件;

同步是多个事件按顺序往下执行, 异步是多个事件在多个通道同时执行;

3 . 异步操作和多线程的联系和区别

异步大多数是多线程, 但也不一定是, block方法回调和dispatch的定时函数也是异步操作, 但不一定是多线程, 视当前的代码环境而定;

4. 多编程的技术方案

方案 简介 语言 线程生命周期 使用频率
pthread
  • 一套通用的多线程API
  • 适用于 Unix / Linux / Windows 等系统
  • 跨平台\可移植
  • 使用难度大
C 程序员管理 几乎不用
NSThread
  • 使用更加面向对象
  • 简单易用,可直接操作线程对象
OC 程序员管理 偶尔使用
GCD
  • 旨在替代NSThread等线程技术
  • 充分利用设备的多核
C 自动管理 经常使用
NSOperation
  • 基于GCD(底层是GCD)
  • 比GCD多了一些更简单实用的功能
  • 使用更加面向对象
OC 自动管理 经常使用


代码演练:

一、pthread

  1. 知道 C 语言中 void * 与 OC 中的 id 类似
  2. 使用 [NSThread currentThread] 能够在任何多线程技术中,查询当前代码执行所在线程
    1. number == 1,表示主线程
    2. number != 1,表示其他线程
  1. pthread 是 POSIX 多线程开发框架,由于是跨平台的 C 语言框架,在苹果的头文件中并没有详细的注释
  2. 要查阅 pthread 有关资料,可以访问 http://baike.baidu.com
  1. 创建工程 pthread
  2. 导入头文件
    #import <pthread.h> 
  3. touchesBegan 创建线程

     - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { /* 参数: 1. pthread_t *restrict: 需要 pthread_t(线程标示符) 类型的指针, C语言中的 _t/Ref,都是结构体 2. const pthread_attr_t *restrict: 线程属性 3. void *(*)(void *): 线程调用的函数 void * (*) (void *) 返回值 函数地址 参数类型 4. void *restrict: 第三个参数的参数 返回值: 0: 表示成功 非0表示失败,一个数字对应一个失败原因 */ // 1> 定义一个 C 字符串 // char *cName = "zhangsan"; // 2> OC 的字符串 NSString *name = @"lisi"; pthread_t threadId = NULL; int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(name)); if (result == 0) { NSLog(@"成功"); } else { NSLog(@"失败"); } } 
  4. pthread 线程调用函数

    /// 线程调用函数 void *demo(void * param) { NSLog(@"线程 = %@", [NSThread currentThread]); // 1.打印C语言字符串 // NSLog(@"param = %s", param); // 2.打印OC字符串 // NSString *name = (__bridge NSString *)(param); NSLog(@"param = %@", name); return NULL; }
pthread小结:

  1. 在 C 语言中,没有对象的概念,对象是以结构体的方式来实现的
  2. 通常,在 C 语言框架中,对象类型以 _t/Ref 结尾,而且声明时不需要使用 *
  3. C 语言中的 void * 和 OC 中的 id 是类似的
  4. 内存管理
    • 在 OC 中,如果是 ARC 开发,编译器会在编译时,根据代码结构,自动添加 retain/release/autorelease
    • 但是,ARC 只负责管理 OC 部分的内存管理,而不负责 C 语言 代码的内存管理
    • 开发过程中,如果使用的 C 语言框架出现 retain/create/copy/new 等字样的函数,大多都需要 release,否则会出现内存泄漏
  5. 在混合开发时,如果在 C 和 OC 之间传递数据,需要使用 __bridge 进行桥接,桥接的目的就是为了告诉编译器如何管理内存
    • 桥接的添加可以借助 Xcode 的辅助功能添加
    • MRC 中不需要使用桥接

二、NSThread

NSThread的3中创建线程方式

  1. NSThread 创建线程的三种方式
    1. NSThread 类的 alloc/init 创建线程
    2. NSThread 的 类方法 创建线程
    3. NSObject 分类方法 创建线程
  2. 创建工程 NSThread创建线程
  3. 创建线程第一种方式: 

    1. 使用 NSThread 类的 alloc/init创建线程

       #pragma mark - 创建线程 /// 使用 alloc / init 创建线程 - (void)thread1 { NSLog(@"thread1 begin %@", [NSThread currentThread]); // 创建 NSThread 对象 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longOperation:) object:@"hello"]; // 启动线程 [thread start]; NSLog(@"thread1 end %@", [NSThread currentThread]); } 
    2. 定义 子线程 执行的方法

       #pragma mark - 线程执行方法 - (void)longOperation:(id)param { NSLog(@"longOperation begin %@", [NSThread currentThread]); NSLog(@"longOperation end %@", [NSThread currentThread]); } 
    3. 在 touchesBegan:withEvent: 调用
       - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self thread1]; } 
    4. 小结:
      1. 在 OC 中,任何一个线程执行代码都是从上向下顺序执行的
      2. [thread start]; 执行后,会在另外一个线程执行 longOperation: 方法
  4. 使用 NSThread 的 类方法 detachNewThreadSelector 创建线程
     /// 使用 NSThread 类方法 - (void)thread2 { [NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"hello2"]; } 
    • detachNewThreadSelector 类方法 不需要启动,会自动创建线程并执行 @selector 方法
  5. 使用 NSObject 分类方法 performSelectorInBackground 创建线程
     /// 使用 NSObject 分类方法 - (void)thread3 { [self performSelectorInBackground:@selector(longOperation:) withObject:@"hello3"]; } 
    • 小结:
      1. performSelectorInBackground 是 NSObject 的分类方法
      2. 会自动在后台线程执行 @selector 方法
      3. 没有 thread 字眼,隐式创建并启动线程
      4. 所有 NSObject 都可以使用此方法,在其他线程执行方法
      5. Swift 中不支持
线程的状态:

示意图:

iOS多线程相关,同步异步的问题_第2张图片

  1. 状态说明
    1. 新建
      • 实例化线程对象
    2. 就绪
      • 向线程对象发送 start 消息,线程对象被加入 可调度线程池 等待 CPU 调度
      • detach 方法和 performSelectorInBackground 方法会直接实例化一个线程对象并加入 可调度线程池
    3. 运行
      • CPU 负责调度可调度线程池中线程的执行
      • 线程执行完成之前,状态可能会在就绪和运行之间来回切换
      • 就绪和运行之间的状态变化由 CPU 负责,程序员不能干预
    4. 阻塞
      • 当满足某个预定条件时,可以使用休眠或锁阻塞线程执行
      • sleepForTimeInterval:休眠指定时长
      • sleepUntilDate:休眠到指定日期
      • @synchronized(self):互斥锁
    5. 死亡
      1. 正常死亡
        • 线程执行完毕
      2. 非正常死亡
        • 当满足某个条件后,在线程内部中止执行
        • 当满足某个条件后,在主线程中止线程对象
      3. 注意:一旦线程停止(死亡)了,就不能再次开启任务

控制线程状态的方法

  1. 启动
    • [thread start]
      • 线程进入就绪状态,线程对象被加入 可调度线程池 等待 CPU 调度, 当线程执行完毕后自动进入死亡状态
  2. 休眠
    • 方法执行过程中,符合某一条件时,可以利用 sleep 方法让线程进入 阻塞 状态
      • sleepForTimeInterval 从现在起睡多少秒
      • sleepUntilDate 从现在起睡到指定的日期
  3. 死亡
    • [NSThread exit]
      • 一旦强行终止线程,后续的所有代码都不会被执行
      • 注意:在终止线程之前,应该注意释放之前分配的对象!
  4. 取消
    • [_thread cancel]
      • 并不会直接取消线程
      • 只是给线程对象添加 isCancelled 标记
      • 需要在线程内部的关键代码位置,增加判断,决定是否取消当前线程
三、GCD

GCD的方法很多,用法也很多,这里只列举一些常用的方法。常用的方法包括:

  • 同步、非阻塞执行
  • 异步非阻塞执行
  • 一次性执行
  • 延迟执行
  • 线程队列串行执行
  • 线程队列控制(屏障,同步等待,线程暂停和恢复,线程信号量控制等)
<span style="font-family:Open Sans, Clear Sans, Helvetica Neue, Helvetica, Arial, sans-serif;color:#333333;">/*
 *使用GCD 的多线程
 *优点:有很多串行并线队列多线程,block实现线程方法,高级,好用,方法多。
 *缺点:在很多不需要高级控制线程的场景可以不用使用GCD
 */
-(void)GCDFunction{

    NSLog(@"GCDFunction start");

    //获取一个队列
    dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //dispatch_async:异步方式执行方法(最常用)
    //    dispatch_async(defaultQueue, ^{
    //        [self function1];
    //    });

    //dispatch_sync:同步方式使用场景,比较少用,一般与异步方式进行调用
    //    dispatch_async(defaultQueue, ^{
    //       NSMutableArray *array = [self GCD_sync_Function];
    //       dispatch_async(dispatch_get_main_queue(), ^{
    //           //利用获取的arry在主线程中更新UI
    //
    //       });
    //    });

    //dispatch_once:一次性执行,常常用户单例模式.这种单例模式更安全
    //    static dispatch_once_t onceToken;
    //    dispatch_once(&onceToken, ^{
    //        // code to be executed once
    //        NSLog(@"dispatch_once");
    //    });

    //dispatch_after 延迟异步执行
    //    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);
    //    dispatch_after(popTime, defaultQueue, ^{
    //        NSLog(@"dispatch_after");
    //    });


    //dispatch_group_async 组线程可以实现线程之间的串联和并联操作
    //    dispatch_group_t group = dispatch_group_create();
    //    NSDate *now = [NSDate date];
    //    //做第一件事 2秒
    //    dispatch_group_async(group, defaultQueue, ^{
    //        [NSThread sleepForTimeInterval:2];
    //         NSLog(@"work 1 done");
    //    });
    //    //做第二件事 5秒
    //    dispatch_group_async(group, defaultQueue, ^{
    //        [NSThread sleepForTimeInterval:5];
    //        NSLog(@"work 2 done");
    //    });
    //
    //    //两件事都完成后会进入方法进行通知
    //    dispatch_group_notify(group, defaultQueue, ^{
    //        NSLog(@"dispatch_group_notify");
    //        NSLog(@"%f",[[NSDate date]timeIntervalSinceDate:now]);//总共用时5秒,因为2个线程同时进行
    //    });


    //dispatch_barrier_async :作用是在并行队列中,等待前面的队列执行完成后在继续往下执行
    //    dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    //    dispatch_async(concurrentQueue, ^{
    //        [NSThread sleepForTimeInterval:2];
    //        NSLog(@"work 1 done");
    //    });
    //    dispatch_async(concurrentQueue, ^{
    //        [NSThread sleepForTimeInterval:2];
    //        NSLog(@"work 2 done");
    //    });
    //    //等待前面的线程完成后执行
    //    dispatch_barrier_async(concurrentQueue, ^{
    //         NSLog(@"dispatch_barrier_async");
    //    });
    //
    //    dispatch_async(concurrentQueue, ^{
    //        [NSThread sleepForTimeInterval:3];
    //        NSLog(@"work 3 done");
    //    });



    //dispatch_semaphore 信号量的使用,串行异步操作
    //    dispatch_semaphore_create   创建一个semaphore
    //   dispatch_semaphore_signal   发送一个信号
    //   dispatch_semaphore_wait    等待信号


    /*应用场景1:马路有2股道,3辆车通过 ,每辆车通过需要2秒
     *条件分解:
        马路有2股道 <=>  dispatch_semaphore_create(2) //创建两个信号
        三楼车通过 <=> dispatch_async(defaultQueue, ^{ } 执行三次
        车通过需要2秒 <=>  [NSThread sleepForTimeInterval:2];//线程暂停两秒
     */

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);

        dispatch_async(defaultQueue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [NSThread sleepForTimeInterval:2];
            NSLog(@"carA pass the road");
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_async(defaultQueue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [NSThread sleepForTimeInterval:2];
            NSLog(@"carB pass the road");
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_async(defaultQueue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [NSThread sleepForTimeInterval:2];
            NSLog(@"carC pass the road");
            dispatch_semaphore_signal(semaphore);
        });



    //应用场景2 :原子性保护,保证同时只有一个线程进入操作
    //    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    //    for(int i=0 ;i< 10000 ;i++){
    //        dispatch_async(defaultQueue, ^{
    //            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    //            NSLog(@"i:%d",i);
    //            dispatch_semaphore_signal(semaphore);
    //        });
    //    }


    NSLog(@"GCDFunction end");
}</span>

四、NSOperation

NSOperation需要在NSOperationQueue中使用,通过queue可以实现先进先出的队列任务,可以添加或取消任务,NSOperation有2个重要的子类,分别是:NSInvocationOperation,NSBlockOperation,分别表示调用一个方法或调用一个block的任务。 NSOperation是比GCD更高层次的api,相同的线程操作如果能用NSOperation操作就尽量用,不能实现的线程操作才使用GCD.相比GCD,NSOperation还有个好处,就是任务可以被取消,而GCD不可以。

<span style="font-family:Open Sans, Clear Sans, Helvetica Neue, Helvetica, Arial, sans-serif;font-size:14px;color:#333333;">-(void)NSOperationFunction{
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //设置队列最大同时进行的任务数量,1为串行队列
    [queue setMaxConcurrentOperationCount:1];
    //添加一个block任务
    [queue addOperationWithBlock:^{
       sleep(2);
        NSLog(@"block task 1");
    }];
    [queue addOperationWithBlock:^{
        sleep(2);
        NSLog(@"block task 2");
    }];
    //显示添加一个block任务
    NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"block task 3");
    }];
    //设置任务优先级
    //说明:优先级高的任务,调用的几率会更大,但不表示一定先调用
    [block1 setQueuePriority:NSOperationQueuePriorityHigh];
    [queue addOperation:block1];

    NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"block task 4,任务3依赖4");
    }];
    [queue addOperation:block2];
    //任务3依赖4
    [block1 addDependency:block2];
    //设置任务完成的回调
    [block2 setCompletionBlock:^{
         NSLog(@"block task 4 comlpete");
    }];

    //设置block1完成后才会继续往下走
    [block1 waitUntilFinished];
     NSLog(@"block task 3 is waitUntilFinished!");

    //初始化一个子任务
    NSInvocationOperation *oper1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(function1) object:nil];
    [queue addOperation:oper1];

    [queue waitUntilAllOperationsAreFinished];
    NSLog(@"queue comlpeted");

    //    取消全部操作
    //    [queue cancelAllOperations];
    //    暂停操作/恢复操作/是否暂定状态
    //    [queue setSuspended:YES];[queue setSuspended:NO];[queue isSuspended];


    //操作优先级



    //      [queue waitUntilAllOperationsAreFinished];</span>

2016-02-04 15:11:54.283 ThreadAndAsynchronization[28948:3783683] block task 1
2016-02-04 15:11:56.358 ThreadAndAsynchronization[28948:3783684] block task 2
2016-02-04 15:11:58.430 ThreadAndAsynchronization[28948:3783683] block task 4,任务3依赖4
2016-02-04 15:11:58.430 ThreadAndAsynchronization[28948:3783694] block task 4 comlpete
2016-02-04 15:12:00.504 ThreadAndAsynchronization[28948:3783683] block task 3
2016-02-04 15:12:00.504 ThreadAndAsynchronization[28948:3783527] block task 4 is waitUntilFinished!
2016-02-04 15:12:02.573 ThreadAndAsynchronization[28948:3783694] function1 done
2016-02-04 15:12:02.573 ThreadAndAsynchronization[28948:3783527] queue comlpeted

有2个值得注意的地方,第一个是mainQueue,第二个是maxConcurrentOperationCount。

mainQueue是通过 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; 获取到,它代表主队列,也就是UI队列,所以用到mainQueue队列的时候一般用于更新ui界面,且特别注意在这个队列中执行的方法,要考虑到会不会阻塞进程。

maxConcurrentOperationCount:最多有多少个队列可以同时执行,默认是5,当设置为1是,队列是一个串行队列,设置>1时,队列是一个并行队列。但是在主队列上设置同时执行的任务是没有效果的!如果没有设置最大并发数,那么并发的个数是由系统内存和CPU决定的,可能内存多久开多一点,内存少就开少一点。

  • 取消队列的所有操作 [queue cancelAllOperations];
  • 暂停队列恢复
//    [queue setSuspended:YES];
//    [queue setSuspended:NO];
//    [queue isSuspended];

操作依赖

//block1依赖block2
    [block1 addDependency:block2];


操作完成后的回调

  //设置任务完成的回调
    [block2 setCompletionBlock:^{
         NSLog(@"block task 4 comlpete");
    }];




你可能感兴趣的:(多线程,ios,同步异步)