线程理解

多线程优化

1 进程,线程介绍

进程:

进程是指系统中正在运行的一个应用程序。没个进程之间是独立的。每个进程均运行在其专有且受保护的内存空间内。进程占独立的内存空间。

线程

线程是进程的基本执行单元。一个进程想要执行任务,必须得有线程。进程里面的所有任务都在线程中执行。每一个进程打开程序的时候都会默认开启一条线程。
我们称之为主线程。一个线程包括:独有ID ,程序计数器 (Program Counter),寄存器集合,堆栈。 进程可以有很多线程。他们共享进程的全局变量,堆数据以及内存空间。
我们知道 线程和进程都是虚拟的概念。实际上都是 CPU 在执行任务。程序计数器简称PC 指向的是当前的指令地址。程序通过更新这个PC来让CPU执行任务。
实际上 PC 是 CPU 核心中的寄存器,它是实际存在的,所以也可以说一个 CPU 核心同一时刻只能执行一个线程。同一时间,单核cpu只能处理1条线程,只有1条线程在工作(执行)
当前设备都是多核系统。意义就是具有多个CPU核心。而一个CPU核心同一时间只能执行一个PC,也就是只能执行一个线程。当我们线程不超过多核设备的核心CPU数量之时
确实可以做到多条线程真正意义上同时执行。但是当线程数量超过CPU核心数量的时候,一个核心CPU就需要处理多条线程。这个行为叫做线程调度。此时,CPU会在多条线程之间调度(切换)
执行任务。当CPU切换速度足够快的时候,就造成了假象的同时运行,这就称之为多线程并发(当然实际里面操作会更复杂)。

2 iOS 多线程 NSThread NSOperation CGD

以上这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。

  • NSThread
    优点:NSThread 比其他两个轻量级,使用简单
    缺点:需要自己管理线程的生命周期、线程同步。线程同步对数据的加锁会有一定的系统开销
  • NSOperation
    不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上
  • GCD
    Grand Central Dispatch是由苹果开发的一个多核编程的解决方案。是替代NSThread, NSOperation的高效和强大的技术,用途广泛,苹果公司极力推崇的一个框架。


    Thread.png
  • NSThread
- init方式
- detachNewThreadSelector创建好之后自动启动
- performSelectorInBackground创建好之后也是直接启动
- (void) createNSThread{
   
   NSString *threadName1 = @"NSThread1";
   NSString *threadName2 = @"NSThread2";
   NSString *threadName3 = @"NSThread3";
   NSString *threadNameMain = @"NSThreadMain";
   
   NSThread *thread1 = [[NSThread alloc] initWithTarget:self  
   selector:@selector(doSomething:) object:threadName1];
   [thread1 start];
   
   [NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self   
  withObject:threadName2];
   
   [self performSelectorInBackground:@selector(doSomething:)   
   withObject:threadName3];
   
   //运行在主线程,waitUntilDone:是否阻塞等待@selector(doSomething:)执行完毕
   [self performSelectorOnMainThread:@selector(doSomething:)   
   withObject:threadNameMain waitUntilDone:YES];
   
}

- (void) doSomething:(NSObject *)object{
    NSLog(@"%@:%@", object,[NSThread currentThread]);
}

  1. NSThread的常用类方法
//退出线程
[NSThread exit];
//判断当前线程是否为主线程
[NSThread isMainThread];
//判断当前线程是否是多线程
[NSThread isMultiThreaded];
主线程的对象
NSThread *mainThread = [NSThread mainThread];

//线程是否在执行
thread.isExecuting;
//是否被取消
thread.isCancelled;
//是否完成
thread.isFinished;
//是否是主线程
thread.isMainThread;
//线程的优先级,取值范围0.0-1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高
thread.threadPriority;

  • GCD

任务

任务 :就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。
执行任务有两种方式:同步执行(sync)和异步执行(async)。
两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

队列:

队列(queue):这里的队列执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出) 的原则,即 新任务总是被插入到队列的末尾,而读取任务的时候总是 从队列的头部开始读取 。每读取一个任务,则从队列中释放一个任务。
队列有两种:

  • 串行 :每次只有一个任务被执行,让任务一个接着一个地执行(只开启一个线程)。
  • 并行 :可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)。

GCD总结:将任务(要在线程中执行的操作block)添加到队列(自己创建或使用全局并发队列),并且制定执行任务的方式(异步或同步)。
总结 : GCD 主要 先看 任务 是同步 还是异步。 只要是 同步的 就代表 并不会开辟线程。 所有代码 只在当前线程里面执行 (就是 dispatch_sync()方法调用的线程执行),如果是异步的 代表 具备开辟线程能力了。 并且代码 是异步执行的。并不会堵塞当前线程代码。 然后 看队列。 串行 队列 代表着 这个队列里的代码 都是 同步执行的。按顺序执行的,如果是同步添加的 同步添加并不会开辟线程。如果是异步添加的 ,串行队列 只会开辟一个线程。 所有添加进来的代码 都只是串行执行。并行队列: 如果是 同步的 也不会开辟线程。乖乖的变成了同步执行。 如果是异步执行 就可以开辟多条线程

就是下边这个图。
GCD.png

简单的代码事例
- (void)syncSerial {
    NSLog(@"currentThread: %@", [NSThread currentThread]);
        NSLog(@"syncSerial begin");
   
    
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"task1--%@", [NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"task2--%@", [NSThread currentThread]);// 打印当前线程
        }
        
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"task3--%@", [NSThread currentThread]);// 打印当前线程
        }
        
    });
    NSLog(@"syncSerial end");
}

GCD : 群组group:
1 . 先 创建 dispatch_group_t group = dispatch_group_create(); 调度群组

  1. 创建 dispatch_queue_t queue= dispatch_queue_create("groupQueue", DISPATCH_QUEUE_CONCURRENT); 群组队列。 (队列 可以是串行 也可以是并行队列。跟正常GCD一样理解)
  2. 调用 dispatch_group_async 开始加入任务。 (群组只有异步执行。添加队列之后 线程创建跟正常GCD一样理解,)
  3. 调用 dispatch_group_notify 监听 任务执行完毕。
 //创建群组队列
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue= dispatch_queue_create("groupQueue", DISPATCH_QUEUE_CONCURRENT);
    
    
    //开一条线程 做事情
    dispatch_group_async(group, queue, ^{
        for (NSInteger i = 0; i<5; i++) {
        NSLog(@"s1 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
                              [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"s2 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
        }
        
        
    });
    // 开多个线程做事情
//    for (NSInteger i = 0; i<5; i++) {
//        dispatch_group_async(group, queue, ^{
//            NSLog(@"s1 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
//                  [NSThread sleepForTimeInterval:3.0f];
//            NSLog(@"s2 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
//
//        });
//    }
    dispatch_group_notify(group, queue, ^{
        NSLog(@"执行后续任务");
    });

********** 这里的dispatch_group_async 是放在for in 里面 或者是在dispatch_group_async的BLOCK里面执行循环都可以。 按照正常GCD理解就行。 dispatch_group_async的block 里面执行for in 就是 跟GCD一样解释 异步执行 并行队列。 会开线程。 因为只是执行一次 就开了一个线程。 如果只是考虑只需要异步操作 这样即可。 但是 如果是 for in 里面 添加 dispatch_group_async 那么 理解成为 异步 并行队列 由于 循环添加几次。 就会开辟线程。 至于最后开辟几条线程 由系统控制。*************

dispatch_group_enter 与 dispatch_group_leave 的使用

如果在调度群组关联的block内直接异步提交新的任务,group 不会等待嵌套的异步任务执行完毕后在进入 dispatch_group_notify 和 dispatch_group_wait 状态 (说人话就是 我们在group群组里面再开线程 并不会等我们这个线程执行完毕 在执行dispatch_group_notify)

- (void)groupEnterAndLeave {
    //创建群组队列
    //如果在调度群组关联的block内直接异步提交新的任务,group 不会等待嵌套的异步任务执行完毕后在进入 dispatch_group_notify 和 dispatch_group_wait 状态
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue= dispatch_queue_create("groupQueue", DISPATCH_QUEUE_CONCURRENT);
    for (NSInteger i = 0; i<5; i++) {
        dispatch_group_async(group, queue, ^{
        NSLog(@"s1 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
        [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"s2 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
        
        
        dispatch_async(queue, ^{
           [NSThread sleepForTimeInterval:2.0f];
            NSLog(@"s3 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
        });
        
        });
    }
    dispatch_group_notify(group, queue, ^{
        NSLog(@"执行后续任务");
    });
    
}

既上述代码 巡行效果 可以得出 我们在 dispatch_group_async 任务里面 如果另外开辟了线程 做了异步操作 那么 dispatch_group_notify 并不会等我们 这个异步操作执行完毕 再执行 而知忽略, 直接执行dispatch_group_notify。 如何解决这个问题。 就用到了 dispatch_group_enter 与 dispatch_group_leave 来控制。

- (void)groupEnterAndLeave {
    //创建群组队列
    //如果在调度群组关联的block内直接异步提交新的任务,group 不会等待嵌套的异步任务执行完毕后在进入 dispatch_group_notify 和 dispatch_group_wait 状态
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue= dispatch_queue_create("groupQueue", DISPATCH_QUEUE_CONCURRENT);
    for (NSInteger i = 0; i<5; i++) {
        dispatch_group_async(group, queue, ^{
        NSLog(@"s1 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
        [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"s2 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
        //计数 +1
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
           [NSThread sleepForTimeInterval:2.0f];
            NSLog(@"s3 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
            //计数-1
            dispatch_group_leave(group);
        });
        
        });
    }
    dispatch_group_notify(group, queue, ^{
        NSLog(@"执行后续任务");
    });
    
}

等待群组任务完成disaptch_group_wait

disaptch_group_wait :
同步等待事先提交到群组中的任务完成。在指定的超时期限过去之前,返回这些block是否完成。当发生超时时,这个群组将恢复到原来的状态。
如果这个调度群组是空的(没有block与这个群组相关联),这个函数立即返回。

在这个函数成功返回之后,这个调度群组是空的。它既可以使用dispatch_release释放掉,也可以重新添加block。

成功(在指定的超时期限内,所有与群组相关联的block完成)将返回零。失败(超时发生)返回非零。

group,等待完成的调度群组。不可以为NULL。

timeout,超时时间(参考dispatch_time)。常量DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER被提供使用很方便。 一般我们使用DISPATCH_TIME_FOREVER 一直等下去。超时时间不限。
disaptch_group_wait 会卡住当前线程。 叫后面的代码不执行 知道 之前添加到群组里面的任务 完成 再执行后面代码。

GCD 信号量

理解信号量我们必须了解一下三个函数:

  • dispatch_semaphore_create(long value);创建信号量,参数为设置信号量的初始值。
  • dispatch_semaphore_signal(dispatch_semaphore_t dsema);发送当前信号量,参数为当前创建的信号量。
  • dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);等待信号量,第一个为当前等待的信号量,第二个参数为超时时间。当等待时间超过超时时间就不会继续等待了。

信号量 可以理解为 创建信号的时候 填写一个value 默认值为0 当我们调用 dispatch_semaphore_wait 的时候 会使这个value 值-1 . 而在我们 调用dispatch_semaphore_signal 的时候 会使 这个value +1 我们在写dispatch_semaphore_wait 代码的地方 作为一个节点。 当这个value值 大于等于 0 的时候 才可以 继续执行dispatch_semaphore_wait 后面的代码。 并且 dispatch_semaphore_wait的代码 算是一个监控。 随时等到 dispatch_semaphore_wait 调用 使这个值+1 到达 0的时候 dispatch_semaphore_wait 后面的代码 才可以执行。

代码实例 我们在 dispatch_semaphore_wait 的地方 此时 value 为0 dispatch_semaphore_wait会使 value -1 所以此时 value 值为 - 1 所以 dispatch_semaphore_wait后面代码不能执行。需要等待 dispatch_semaphore_signal 虽然 是 dispatch_async异步执行的并行队列 按理开线程的。 但是这里就堵塞了。(从代码运行效果来看 既然是前一个执行完毕 后一个才执行 所以线程都是一个)
- (void)xinhaoliang {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        dispatch_queue_t queue1 =    dispatch_queue_create("queueSemaphore1", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t queue2= dispatch_queue_create("queueSemaphore2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并行队列信号1:%d", i);
            NSLog(@"s1 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
        }
        dispatch_semaphore_signal(semaphore);
    });
     
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
     
    dispatch_async(queue2, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并行队列信号2:%d", i);
            NSLog(@"s2 线程:%@ -- index = %ld",[NSThread currentThread],(long)i);
        }
    });
}

归总:GCD 信号量 是可以做的事

  • 1 . 可以将 异步执行的并行线程(不管是同队列 还是不同队列的)queue 都可以做到串行 执行。(参考上图)可以将AFN请求的时候 不同接口 搞成同步请求。
    1. 可以做到控制最大并发量。 (变为控制线程数量)GCD 不能控制线程开辟个数。 但是通过变相控制最大并发量 达到控制控制线程个数。(只是变相控制。 因为并不能完全控制线程数量 只是大大使线程重用);
- (void)xinhaoNumber {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
     
    for (int i = 0; i < 11; i++) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"并发线程数量:%d 当前线程:%@",i,[NSThread currentThread]);
            sleep(5);
            dispatch_semaphore_signal(semaphore);
        });
    }
}

NSOperation

  • NSOperation简介:NSOperation是基于GCD之上的更高一层封装,NSOperation需要配合NSOperationQueue来实现多线程。
  • NSOperatino实现多线程的步骤如下:
    ···
  • 1.创建任务:先将需要执行的操作封装到NSOperation对象中。
  • 2.创建队列:创建NSOperationQueue。
  • 3.将任务加入到队列中:将NSOperation对象添加到NSOperationQueue中。
    ···
    需要注意的是,NSOperation是个抽象类,实际运用时中需要使用它的子类,有三种方式:
    ···
  • 1.NSInvocationOperation
  • 2.NSBlockOperation
  • 3.运用继承自NSOperation的子类
    ···
- (void)nsoperationQueue {
   NSLog(@"nsoperationQueue start");
   //创建队列
   NSOperationQueue * queue = [[NSOperationQueue alloc]init];
   //运用最大并发数实现串行 运用队列的属性maxConcurrentOperationCount(最大并发数)来实现串行,值需要把它设置为1就可以了,下面我们通过代码验证一下。
   queue.maxConcurrentOperationCount = 2;
   //创建任务 操作类 NSOperation 子类化 //创建NSInvocationOperation
   NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationAddOperation) object:nil];
   //创建NSBlockOperation
   NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
       for (int i = 0; i < 3; i++) {
                   NSLog(@"NSBlockOperation: %@", [NSThread currentThread]);
               }
   }];
   NSBlockOperation * blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
       for (int i = 0; i < 3; i++) {
                   NSLog(@"NSBlockOperation22222====: %@", [NSThread currentThread]);
               }
   }];
   [queue addOperation:invocationOperation];
   [queue addOperation:blockOperation];
   [queue addOperation:blockOperation2];
   NSLog(@"nsoperationQueue end");
}

GCD 死锁理解

想要了解死锁,必须充分理解以下两个概念!
  • 同步 sync
  • 异步 async
  • 串行队列
  • 并行队列
同步 sync: 概念是指: block 里的任务 需要同步执行, 执行完毕 block 里面的任务 才能执行block后面的代码。(这里的block后边代码是指跟sync 在同一队列的代码)
异步 async:概念是指: block 里面的任务 跟后边的代码可以同时执行, 不需要等待。
串行队列 :概念是指:任务是依次执行的 ,先加到队列的 先执行, 后加到队列的后执行, (什么时间加到队列 看什么时候sync进来的任务)
并行队列:概念是指:添加到队列的任务 不分先后, 加进来 就可以异步执行。

使用sync函数 往当前串行队列里面添加任务 会造成死锁现象! 解释当前串行队列: 就是当前的线程开辟的时候使用的串行队列生成的线程。然后 再在线程里面利用sync函数用生成本身运行线程的串行队列里面添加任务。就会形成死锁。

  • 形成死锁现象 1. 使用sync函数 2.往当前串行队列添加任务 造成 当前任务等带薪任务 薪任务等待当前任务完成再执行, 导致死锁。
线程锁。 是为了解决多线程带来的资源安全问题的类。 系统自带的atomics是互斥锁

GCD群组参考文章
GCD群组参考文章
多线程概念文章
多线程优化文章

你可能感兴趣的:(线程理解)