线程总结

作者: 咖啡凯 | 来源:发表于2017-07-19 14:42 被阅读49次

1、NSThread

NSThread 是一套比较轻量级的多线程方案,可以直观的观察线程对象,一个 NSThread 代表一条线程,但是需要自己管理生命周期和线程同步等问题。

NSThread 使用示例:

    NSLog(@"normal thread %@",[NSThread currentThread]);
    
    // NSThread 类方法
    [NSThread detachNewThreadSelector:@selector(newThread) toTarget:self withObject:nil];
    
    [NSThread detachNewThreadWithBlock:^{
        NSLog(@"new block thread %@",[NSThread currentThread]);
    }];
    
    // NSThread 实例化方法
    NSThread *newThred1 = [[NSThread alloc] initWithTarget:self selector:@selector(newThred1) object:nil];
    //设置线程的优先级 && 线程优先级需要在 start 方法之前设置
    newThred1.qualityOfService = NSQualityOfServiceUserInteractive;
    //实例化 NSThread 的时候需要手动调用 start
    [newThred1 start];
    
    NSThread *newThread2 = [[NSThread alloc] initWithBlock:^{
        NSLog(@"new thread 2 %@", [NSThread currentThread]);
    }];
    newThread2.qualityOfService = NSQualityOfServiceUserInitiated;
    [newThread2 start];

其他的一些线程操作

    // 线程休眠,休眠线程会阻塞当前线程
    [NSThread sleepForTimeInterval:5.0];
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
    
    // 线程停止,执行此方法会立即终止主线程外其他所有线程,所以调用请慎用。
    [NSThread exit];
    
    //线程取消
    [newThread2 cancel];
    
    // 获取当前线程
    [NSThread currentThread];
    // 获取主线程
    [NSThread mainThread];
    
    //是否是主线程
    [NSThread isMainThread];

2、GCD

  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

串行队列

serial queue(串行队列)特点:执行完queue中第一个任务,执行第二个任务,执行完第二个任务,执行第三个任务,以此类推,任何一个任务的执行,必须等到上个任务执行完毕。

获得serial queue的方式有2种

  1. 获得mainQueue。mainQueue会在主线程中执行,即:主线程中执行队列中的各个任务
  2. 自己创建serial queue。(自己创建的serial queue不会在主线程中执行,queue会开辟一个子线程,在子线程中执行队列中的各个任务)
dispatch_queue_t mySerialQueue = dispatch_queue_create("GCDDemo", DISPATCH_QUEUE_SERIAL);
    dispatch_async(mySerialQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第1个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_async(mySerialQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第2个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_async(mySerialQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第3个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_async(mySerialQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第4个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_async(mySerialQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第5个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });

并行队列

concurrent queue(并行队列)特点:队列中的任务,第一个先执行,不等第一个执行完毕,第二个就开始执行了,不等第二个任务执行完毕,第三个就开始执行了,以此类推。后面的任务执行的晚,但是不会等前面的执行完才执行。

获得concurrent queue的方法有2种:

  1. 获得global queue。两个参数。第一个参数控制 globalQueue 的优先级。第二个参数是苹果预留参数,一般设置为 0。

  2. 自己创建concurrent queue。

    • DISPATCH_QUEUE_PRIORITY_HIGH
    • DISPATCH_QUEUE_PRIORITY_LOW
    • DISPATCH_QUEUE_PRIORITY_DEFAULT
    • DISPATCH_QUEUE_PRIORITY_BACKGROUND

    global queue会根据需要开辟若干个线程,并行执行队列中的任务(开始较晚的任务未必最后结束,开始较早的任务未必最先完成),开辟的线程数量取决于多方面因素,比如:任务的数量,系统的内存资源等等,会以最优的方式开辟线程---根据需要开辟适当的线程。

    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        dispatch_async(globalQueue, ^{//在block里写要执行的任务(代码)
            NSLog(@"第1个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
        });
        dispatch_async(globalQueue, ^{//在block里写要执行的任务(代码)
            NSLog(@"第2个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
        });
        dispatch_async(globalQueue, ^{//在block里写要执行的任务(代码)
            NSLog(@"第3个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
        });
        dispatch_async(globalQueue, ^{//在block里写要执行的任务(代码)
            NSLog(@"第4个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
        });
        dispatch_async(globalQueue, ^{//在block里写要执行的任务(代码)
            NSLog(@"第5个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
        });
        dispatch_async(globalQueue, ^{//在block里写要执行的任务(代码)
            NSLog(@"第6个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
        });
        dispatch_async(globalQueue, ^{//在block里写要执行的任务(代码)
            NSLog(@"第7个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
        });
        dispatch_async(globalQueue, ^{//在block里写要执行的任务(代码)
            NSLog(@"第8个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
        });
        dispatch_async(globalQueue, ^{//在block里写要执行的任务(代码)
            NSLog(@"第9个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
        });
        dispatch_async(globalQueue, ^{//在block里写要执行的任务(代码)
            NSLog(@"第10个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
        });

GCD延时执行

  1. dispatch_after

dispatch_after函数是延迟执行某个任务,任务既可以在 mainQueue 中进行也可以在其他 queue 中进行。既可以在 serial 队列里执行也可以在 concurrent 队列里执行

```
double delayInSenconds = 3.0;
    dispatch_time_t deplayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSenconds * NSEC_PER_SEC));
    dispatch_queue_t myDelayQueue = dispatch_queue_create("GCDDemo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_after(deplayTime, myDelayQueue, ^(void){
        NSLog(@"delayGCD");
    });
```
  1. GCD 信号量控制并发

    当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,但如何在GCD中快速的控制并发呢?答案就是dispatch_semaphore。
    信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。
    在GCD中有三个函数是semaphore的操作,分别是:

    1. dispatch_semaphore_create   创建一个semaphore
    2. dispatch_semaphore_signal   发送一个信号
    3. dispatch_semaphore_wait   等待信号

    dispatch_group_t testGroup = dispatch_group_create();
    dispatch_semaphore_t testSemaphore = dispatch_semaphore_create(10);
    dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < 100; i++) {
        dispatch_semaphore_wait(testSemaphore, DISPATCH_TIME_FOREVER);
        dispatch_group_async(testGroup, myQueue, ^{
            
            NSLog(@"第%i个任务,所在线程%@,是否是主线程:%d", i, [NSThread currentThread], [[NSThread currentThread] isMainThread]);
            sleep(3);
            dispatch_semaphore_signal(testSemaphore);
        });
    }
    dispatch_group_wait(testGroup, DISPATCH_TIME_FOREVER);

GCD异步执行

在实际开发中,需要开启N个异步线程,(如异步下载N张图片,下载结束后需要继续执行某项任务),需要依赖N个线程返回的数据,需要接收所有线程任务执行完成的通知

    dispatch_group_t jwGroup = dispatch_group_create();
    dispatch_queue_t myQueue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
    
    //dispatch_group_async用于把不同的任务归为一组
    dispatch_group_async(jwGroup, myQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第1个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_group_async(jwGroup, myQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第2个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_group_async(jwGroup, myQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第3个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_group_async(jwGroup, myQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第4个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_group_async(jwGroup, myQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第5个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_group_async(jwGroup, myQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"第6个任务,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    
    //dispatch_group_notify当指定组的任务执行完毕之后,执行给定的任务
    dispatch_group_notify(jwGroup, myQueue, ^{
        NSLog(@"group中的任务都执行完毕之后,执行此任务。所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });

dispatch barrier async

  • 为了保证访问同一个数据时,数据的安全,我们可以使用serial queue解决数据安全访问的问题。
  • serial queue的缺陷是:后面的任务 必须等待 前面的任务 执行完毕 才能执行
  • 对于往数据库写入数据 使用serial queue无疑能保证数据的安全。
  • 对于从数据库中读取数据,使用serial queue就不太合适了,效率比较低。使用concurrent queue无疑是最合适的。
  • 真实的项目中,通常既有对数据库的写入,又有数据库的读取。如何处理才最合适呢?
  • 下面给出了既有数据库数据读取,又有数据库数据写入的处理方法dispatch_barrier_async
    dispatch_queue_t myConcurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(myConcurrentQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"读取第1个数据,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_async(myConcurrentQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"读取第2个数据,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_async(myConcurrentQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"读取第3个数据,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_async(myConcurrentQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"读取第4个数据,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    
    //dispatch_barrier_async就像一道墙,之前的任务都并行执行,执行完毕之后,执行barrier中的任务,之后的任务也是并行执行。
    dispatch_barrier_async(myConcurrentQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"写入某些数据,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    
    dispatch_async(myConcurrentQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"读取第5个数据,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_async(myConcurrentQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"读取第6个数据,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_async(myConcurrentQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"读取第7个数据,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });
    dispatch_async(myConcurrentQueue, ^{//在block里写要执行的任务(代码)
        NSLog(@"读取第8个数据,所在线程%@,是否是主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });

dispatch_apply 快速迭代

类似 for 循环,但是在并发队列的情况下 dispatch_apply 会并发执行 block 任务。因为可以并行执行,所以使用 dispatch_apply 运行地更快。需要注意的是,dispatch_apply 这个是会阻塞主线程的

    //GCD中提供了API让某个任务执行若干次。
    NSArray *array = [NSArray arrayWithObjects:@"红楼梦", @"水浒传", @"三国演义", @"西游记", nil];
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(array.count, queue, ^(size_t index){
        NSLog(@"%@,所在线程%@,是否是主线程:%d", [array objectAtIndex:index], [NSThread currentThread], [[NSThread currentThread] isMainThread]);
    });

dispatch_once单例

dispatch_once对于某个任务执行一次,且只执行一次

    static dispatch_once_t once;
    dispatch_once(&once, ^{
        NSLog(@"只执行一次");
    });

同步执行和异步执行

  • dispatch_sync必须等block执行完,才继续执行dispatch_sync后面的代码
  • dispatch_async无需等block执行完,继续执行dispatch_async后面的代码
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"%d", i);
        }
    });
    NSLog(@"JustinDemo_GCD");
    
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"%d", i);
        }
    });
    NSLog(@"JustinDemo_GCD-async");

函数指针dispatch_async_f

dispatch_async_f将函数以异步的方式提交给指定队列

//dispatch_async_f往队列里放函数指针,队列控制相应函数的执行,不在是控制block的执行
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //函数指针对应的函数类型:必须没有返回值,参数必须是void *。函数指针对应的参数,由dispatch_async_f第二个参数提供,可以是任意对象类型。
    dispatch_async_f(queue, @"JustinDemo_GCD", function);
void function(void *context) {
    NSLog(@"%@,所在线程%@,是否是主线程:%d", context, [NSThread currentThread], [[NSThread currentThread] isMainThread]);
}

GCD demo 代码传送门

3、NSOperation

  • NSOpertion 与 NSOperationQueue 结合使用; NSOperationQueue 相当于一个管理器, 来管理线程操作,只要将一个NSOperation(实际开发中需要使用其子类 NSInvocationOperation、NSBlockOperation)放到NSOperationQueue这个队列中线程就会依次启动,NSOperationQueue负责管理、执行所有的NSOperation,在这个过程中可以更加容易的管理线程总数和控制线程之间的依赖关系。
  • NSOperation是一个抽象类,有两个常用子类用于创建线程操作:NSInvocationOperation 和 NSBlockOperation 两种方式本质没有区别,但是后者使用Block形式进行代码组织,使用对象方便。
  • NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。也可以将NSOperation添加到一个NSOperationQueue(操作队列)中执行,系统会自动异步执行NSOperation中的操作。添加操作到NSOperationQueue中,自动执行操作,自动开启线程。

NSInvocationOperation

    //不加入队列中默认在主线程中 
    NSInvocationOperation * operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
    [operation start];//必须得调用run方法,不然那不会执行

NSBlockOperation

只要NSBlockOperation封装的操作数 > 1,就会异步执行操作,等于1的时候默认是同步的

    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        //在主线程
        NSLog(@"0---%@", [NSThread currentThread]);
    }];
    //一下额外的任务都在子线程里面执行
    [operation addExecutionBlock:^{
        NSLog(@"1---%@", [NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@"2---%@", [NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@"3---%@", [NSThread currentThread]);
    }];
    [operation start];
    NSLog(@"执行了一个线程 --- %@", [NSThread currentThread]);

NSOperation异步并行处理事务

    //创建一个操作队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //创建多个任务
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载图片1---%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载图片2---%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"图片下载完了,在这合成吧---%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载图片3---%@", [NSThread currentThread]);
    }];
    
    //设置线程依赖
    [operation2 addDependency:operation1];
    [operation4 addDependency:operation2];
    [operation3 addDependency:operation4];
    
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
    
    //任务执行完了  是不是要去主线程刷新UI了
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        NSLog(@"图片处理好了 --- %@", [NSThread currentThread]);
    }];

使用NSOperationQueue来执行任务与之前的区别在于,首先创建一个非主队列。然后用addOperation方法替换start方法。NSOperationQueue会为每一个NSOperation创建线程并调用它们的start方法。

假装敲黑板啦

①有a、b、c、d 4个异步请求,如何判断a、b、c、d都完成执行?②如果需要a、b、c、d顺序执行,该如何实现?


方法一 (GCD):

       dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
       dispatch_group_t group = dispatch_group_create();
       dispatch_group_async(group, queue, ^{
            /*任务a */
       });
       dispatch_group_async(group, queue, ^{
           /*任务b */
       });
       dispatch_group_async(group, queue, ^{
           /*任务c */
       });
       dispatch_group_async(group, queue, ^{
           /*任务d */
       });
       dispatch_group_notify(group,dispatch_get_main_queue(), ^{
           // 在a、b、c、d异步执行完成后,会回调这里
       });

方法二 (NSOperation):

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载图片1---%@", [NSThread currentThread]);
    }];
    [operation1 addExecutionBlock:^{
        NSLog(@"下载图片2---%@", [NSThread currentThread]);
    }];
    [operation1 addExecutionBlock:^{
        NSLog(@"下载图片3---%@", [NSThread currentThread]);
    }];
    [operation1 addExecutionBlock:^{
        NSLog(@"下载图片4---%@", [NSThread currentThread]);
    }];
    [queue addOperation:operation1];
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        NSLog(@"图片处理好了 --- %@", [NSThread currentThread]);
    }];


方法一:GCD放到串行队列里面执行,然后在主线程刷新UI
方法二:NSOperation创建一个队列和多个任务,设置任务的依赖

相关文章

  • iOS多线程:『GCD』详尽总结

    iOS多线程:『GCD』详尽总结 iOS多线程:『GCD』详尽总结

  • 线程的总结

    线程的总结 摘要:github网站github的线程的总结 内容是: 谈iOS多线程(NSThread、NSOpe...

  • 进程、线程、多线程区别

    原文:Android面试总结:进程、线程、多线程相关总结[https://cloud.tencent.com/de...

  • 无标题文章

    ### java多线程总结

  • Android进阶之路——线程机制

    今天leo主要总结线程方面的,分为以下几个知识点: • 认识线程 • 线程的基本用法 • 线程同步 • 子线程...

  • 线程安全性(一)

    参考线程安全性总结 CountDownLatchCountDownLatch 可以阻塞线程并保证线程在满足某种特定...

  • 线程池

    线程、多线程与线程池总结[https://www.jianshu.com/p/b8197dd2934c]: 线程池...

  • Java并发之线程组ThreadGroup介绍

    线程组介绍 线程组的构造 ThreadGroup方法介绍查看线程组信息终止线程组中的所有线程 总结 Links作者...

  • iOS多线程之NSThread

    前面总结了多线程基本概念和iOS多线程PThread的使用,下面接着总结iOS多线程的另外一种实现方案NSThre...

  • RxJava2.x-线程调度

    一、线程调度 日志 总结 1、Schedules线程:① io②newThread③computation ④s...

网友评论

    本文标题:线程总结

    本文链接:https://www.haomeiwen.com/subject/cxulkxtx.html