美文网首页
进程、线程、任务、队列

进程、线程、任务、队列

作者: 小白PK大牛 | 来源:发表于2020-03-15 18:11 被阅读0次

    一、 进程

    1.进程是操作系统分配资源的基本单元,是一个具有一定独立功能的程序关于某次数据集合的一次运行活动.
    2.进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app.
    3.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源

    二、 线程

    1.线程是程序执行流的最小单元,是进程中的一个实体.
    2.一个进程要想执行任务,必须至少有一条线程,应用程序启动的时候,系统会默认开启一条线程,也就是主线程

    三、 进程和线程的关系

    1.线程是进程的执行单元,进程的所有任务都在线程中执行
    2.线程是 CPU 分配资源和调度的最小单位
    3.一个程序可以对应多个进程(多进程),一个进程中可有多个线程,但至少要有一条线程
    4.同一个进程内的线程共享进程资源

    四、多线程的优缺点

    多线程的优点:
    能适当提高程序的执行效率
    能适当提高资源利用率(CPU、内存利用率)
    多线程的缺点:
    开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),
    如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    线程越多,CPU在调度线程上的开销就越大
    程序设计更加复杂:比如线程之间的通信、多线程的数据共享

    五、任务

    任务就是执行操作的意思,也就是在线程中执行的那段代码。在 GCD 中是放在 block 中的。

    执行任务有两种方式:同步执行(sync)和异步执行(async)

    同步(Sync):同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行,即会阻塞线程。只能在当前线程中执行任务(是当前线程,不一定是主线程),不具备开启新线程的能力。

    异步(Async):线程会立即返回,无需等待就会继续执行下面的任务,不阻塞当前线程。可以在新的线程中执行任务,具备开启新线程的能力(并不一定开启新线程)。如果不是添加到主队列上,异步会在子线程中执行任务

    六、队列

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

    两者的主要区别是:执行顺序不同,以及开启线程数不同。
    串行队列(Serial Dispatch Queue):
    同一时间内,队列中只能执行一个任务,只有当前的任务执行完成之后,才能执行下一个任务。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)。主队列是主线程上的一个串行队列,是系统自动为我们创建的

    并发队列(Concurrent Dispatch Queue):
    同时允许多个任务并发执行。(可以开启多个线程,并且同时执行任务)。
    注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效

    同步/异步和串行/并发

    //同步分配一个任务到串行队列
    - dispatch_sync(serial_queue,^{//任务});
    //异步分配一个任务到串行队列
    - dispatch_async(serial_queue,^{//任务});
    //同步分配一个任务到并发队列
    - dispatch_sync(concurrent_queue,^{//任务})
    //异步分配一个任务到并发队列
    - dispatch_async(concurrent_queue,^{//任务})
    

    七、多线程

    主要有三种:NSThread、NSoperationQueue、GCD
    \color{red}{一般建议开启线程条数:3至5条最为合理}

    1. NSThread:轻量级的,更加面向对象,可直接操作线程对象

    有三种创建方式分别为:
    \color{red}{-init方法},手动开辟子线程,要手动启动,需要我们自己管理该线程,不只是启动,还有该线程使用完毕后的资源回收。
    \color{red}{-detachNewThreadSelector方法},直接自动启动。
    \color{red}{-performSelectorInBackground方法},直接启动

    // 当使用初始化方法出来的主线程需要start启动
    [thread start];
    // 可以为开辟的子线程起名字
    thread.name = @"线程名称";
    // 调整Thread的权限 线程权限的范围值为0 ~ 1 。越大权限越高,先执行的概率就会越高,由于是概率,所以并不能很准确的的实现我们想要的执行顺序,默认值是0.5
    thread.threadPriority=1;
    // 取消当前已经启动的线程
    [thread cancel];
    // 通过遍历构造器开辟子线程
    [NSThread detachNewThreadSelector:@selector(testAction:) toTarget:self withObject:@"参数"];
    //在当前线程。延迟1s执行。响应了OC语言的动态性:延迟到运行时才绑定方法
    [self performSelector:@selector(testAction:) withObject:nil afterDelay:1];
    // 回到主线程。
    //waitUntilDone:是否将该回调方法执行完在执行后面的代码,
    //如果为YES:就必须等回调方法执行完成之后才能执行后面的代码,说白了就是阻塞当前的线程;
    //如果是NO:就是不等回调方法结束,不会阻塞当前线程
    [self performSelectorOnMainThread:@selector(testAction) withObject:nil waitUntilDone:YES];
    //开辟子线程
    [self performSelectorInBackground:@selector(testAction) withObject:nil];
    //在指定线程执行
    [self performSelector:@selector(testAction) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES]
    //带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的Runloop中。也就是如果当前线程没有开启runloop,该方法会失效。在子线程中,需要启动runloop(注意调用顺序)
    //performSelector:withObject:只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的Runloop中也能执行
    [self performSelector:@selector(testAction) withObject:nil afterDelay:1];
    [[NSRunLoop currentRunLoop] run];
    

    当调用 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
    当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。

    2、GCD,充分利用多核,自动管理生命周期

    队列 执行方式
    串行 同步
    并发 异步
    主队列
    组合方式 执行方式 创建线程
    串行同步 顺序
    串行异步 顺序
    并发同步 顺序
    并发异步 交替
    主队列同步 死锁
    主队列异步 顺序

    有3中queue

    1.主队列 dispatch_main_queue(); 串行,更新UI
    2.全局队列 dispatch_global_queue(); 并行 四个优先级 background、low、default、high
    3.自定义队列 dispatch_queue_t(); 串行或并行 DISPATCH_QUEUE_CONCURRENT、DISPATCH_QUEUE_SERIAL

    3、NSOperation 基于GCD的封装,抽象类,不能直接使用,有两个子类可用

    NSInvocationOperation,开启后在主线程执行.
    NSBlockOperation,开启后在主线程执行,addExecutionBlock方法在子线程中执行
    maxConcurrentOperationCount

    执行方式
    -1 并发执行,开多线程(默认)
    0 无法执行任务
    1 串行执行,不开启线程
    >1 并发执行(设置很大没有意义)

    4、GCD 和 NSOprationQueue的区别

    GCD是面向底层的C语言的API
    NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象,完全面向对象,比GCD简单易用,代码可读性高。
    1、GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构,写起来更方便,GCD没法停⽌已经加⼊queue的block(其实是有的,但需要许多复杂的代码)
    2、GCD只支持FIFO的队列,而NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
    3、NSOperationQueue甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)任务,才能控制执行顺序,较为复杂,我们可以随时取消已经设定要准备执⾏的任务(当然,已经开始的任务就⽆法阻⽌了)
    4、NSOperationQueue因为面向对象,所以支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceld)

    5.实例

    \color{red}{1.串行队列中先异步再同步}

    NSLog(@"start--%@",[NSThreadcurrentThread]);
    for(inti=0; i<10; i++) {
      dispatch_async(q, ^{
        NSLog(@"async--%@ i = %d",[NSThreadcurrentThread],i);
      }); }
     NSLog(@"end1--%@",[NSThreadcurrentThread]);
    for(inti=0; i<10; i++) {
      dispatch_sync(q, ^{
        NSLog(@"sync--%@---%d",[NSThreadcurrentThread],i);
      });
    }
    NSLog(@"end2--%@",[NSThreadcurrentThread]);
    }
    
    执行结果 执行结果.jpg

    分析:先在主线程中执行打印start,然后将异步任务添加到串行队列中(只是添加,并没有立即执行),之后在主线程打印end1,再然后往串行队列里添加了1个同步任务,此时主线程堵塞,等待串行队列里地同步任务执行完毕。此时创建一个子线程开始依次执行异步任务,异步任务结束后,在主线程中执行同步任务(上面的结论)。1个同步任务执行完后(此时串行队列里没有任务了),主线程返回,for循环继续往串行队列里添加1个同步任务,此时主线程继续阻塞,等待串行队列里同步任务执行完毕,此时主线程执行这个同步任务,执行完毕后,主线程返回继续for循环……。等for循环结束后,最后在主线程执行打印end2的操作。
    2.并行队列中先异步再同步

    NSLog(@"start--%@",[NSThreadcurrentThread]);
    for(inti=0; i<10; i++) {
      dispatch_async(q, ^{
      NSLog(@"async--%@---%d",[NSThreadcurrentThread],i);
    });
    }
    NSLog(@"end1--%@",[NSThreadcurrentThread]);
    for(inti=0; i<10; i++) {
      dispatch_sync(q, ^{
      NSLog(@"sync--%@---%d",[NSThreadcurrentThread],i);
    });
    }
    NSLog(@"end2--%@",[NSThreadcurrentThread]);
    
    执行结果 执行结果.jpg

    分析:上面代码创建了一个并行队列,然后往并行队列中添加了10个异步任务和10个同步任务,上面只是某一次的打印结果,每次的打印结果都不一样,从打印结果可以看出:由于是并行队列,会开启多个子线程执行异步任务,所以异步任务的打印结果是无序的,而同步任务由于都是在主线程中执行,所有总体是有序的。而且同步与异步任务是交叉着执行完毕的。
    3.死锁

    dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
          dispatch_sync(serialQueue, ^{
              NSLog(@"123");
          });
    });
    

    分析:外面的函数无论是同步还是异步都会造成死锁。 这是因为里面的任务和外面的任务都在同一个 serialQueue 队列内,又是同步,解决方法将里面的同步改成异步 dispatch_async,或者将 serialQueue 换成其他串行或并 行队列,都可以解决

    NSLog(@"任务1");
        dispatch_sync(dispatch_get_main_queue(), ^{
           NSLog(@"任务2");
        });
        NSLog(@"任务三");
    

    分析:先执⾏任务1,接下来,程序遇到了同步线程,它会进⼊等待,等待任务2执⾏完,然后执⾏任务3。但这是队列,有任务来,会将任务加到队尾,然后遵循FIFO原则执⾏任务,现在任务2就会被加到最后,任务3排在了任务2前⾯.
    问题来了:任务3要等任务2执⾏完才能执⾏,任务2⼜排在任务3后⾯,意味着任务2要在任务3执⾏完才能执⾏,所以他们进⼊了互相等待的局⾯。
    \color{red}{4.performSelector}

      dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self performSelector:@selector(test:) withObject:nil afterDelay:0];
        });
    

    分析:这里的 test 方法是不会去执行的,原因在于performSelector方法要创建提交任务到 runloop 上的,而GCD底层创建的线程默认是没有开启对应 runloop 的,所已 这个方法就会失效。 而如果将 dispatch_get_global_queue 改成主队列,由于主队列所在的主线程是默认开启runloop的, 就会去执行(将 dispatch_async 改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,test 方法也是会去执行的),也可以使用 GCD的dispatch_after来实现。
    \color{red}{5.dispatch_barrier_async}
    可以实现多读单写,多读单写的意思是可以多个读者同时读取数据,而在读的时候,不能取写入数据。并且在写的过程中,不能有其他写者去写。即读者之间是并发的,写者与读者或其他写者是互斥的。 这里的写处理就是通过栅栏的形式去写。

    dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
        for (NSInteger i = 0; i < 10; i++) {
            dispatch_sync(concurrentQueue, ^{
                NSLog(@"%zd",i);
            });
        }
        dispatch_barrier_sync(concurrentQueue, ^{
            NSLog(@"------barrier------");
        });
        for (NSInteger i = 10; i < 20; i++) {
            dispatch_sync(concurrentQueue, ^{
               NSLog(@"%zd",i);
            });
        }
    

    分析:这里的 dispatch_barrier_sync 上的队列要和需要阻塞的任务在同一队列上,否则是无效的。 从打印上看,任务 0-9 和任务任务 10-19 因为是异步并发的原因,彼此是无序的。而由于栅栏函数的存在, 导致顺序必然是先执行任务 0-9,再执行栅栏函数,再去执行任务 10-19。
    dispatch_barrier_sync: 提交一个栅栏函数在执行中,它会等待栅栏函数执行完
    dispatch_barrier_async: 提交一个栅栏函数在异步执行中,它会立马返回)
    而 dispatch_barrier_sync 和 dispatch_barrier_async 的区别也就在于会不会阻塞当前线程
    比如,上述代码如果在 dispatch_barrier_async 后随便加一条打印,则会先去执行该打印,再去执 行任务 0-9 和栅栏函数;而如果是 dispatch_barrier_sync,则会在任务 0-9 和栅栏函数后去执行这 条打印。
    \color{red}{6.dispatch_group_async}
    在 n 个耗时并发任务都完成后,再去执行接下来的任务。比如,在 n 个网络请求完成后去刷新 UI 页 面。

    dispatch_queue_t concurrentQueue = dispatch_queue_create("test",DISPATCH_QUEUE_CONCURRENT);
        dispatch_group_t group = dispatch_group_create();
        for (NSInteger i = 0; i < 10; i++) {
            dispatch_group_async(group, concurrentQueue, ^{
                sleep(1);
                NSLog(@"%zd",i);
            });
        }
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"主线程");
        });
    

    \color{red}{7.Dispatch Semaphore}
    GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号,保持线程同步,将异步执行任务转换为同步执行任务,保证线程安全,为线程加锁
    Dispatch Semaphore 提供了三个函数

    • dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
    • dispatch_semaphore_signal:发送一个信号,让信号总量加 1
    • dispatch_semaphore_wait:可以使总信号量减 1,当信号总量为 0 时就会一直等待(阻塞所在线程),否 则就可以正常执行。
      \color{red}{8.dispatch_after}
      延时函数
      dispatch_after 能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指 定时间追加处理到 dispatch_queue
      内部使用的是 dispatch_time_t 管理时间,而不是 NSTimer。 所以如果在子线程中调用,相比 performSelector:afterDelay,不用关心 runloop 是否开启
      \color{red}{9.dispatch_once}
      实现单利
    + (instancetype)sharInstance{
        static dispatch_once_t onceToken;
        static id instance = nil;
        dispatch_once(&onceToken, ^{
            instance = [[self alloc] init];
        });
        return instance;
    }
    

    \color{red}{10.NSThread+runloop 实现常驻线程}
    NSThread 在实际开发中比较常用到的场景就是去实现常驻线程。
    由于每次开辟子线程都会消耗 cpu,在需要频繁使用子线程的情况下,频繁开辟子线程会消耗大量的 cpu,而且创建线程都是任务执行完成之后也就释放了,不能再次利用,最好是用常驻线程

    + (NSThread *)shareThread{
        static NSThread *shareThread = nil;
        static id instance = nil;
        static dispatch_once_t oncePredicate;
        dispatch_once(&oncePredicate, ^{
            shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest1) object:nil];
            [shareThread setName:@"threadTest"];
            [shareThread start];
        });
        return shareThread;
    }
    
    + (void)threadTest{
        @autoreleasepool {
            NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
            [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
            [runLoop run];
        }
    }
    
    //然后调用 performSelector 就可以啦
    [self performSelector:@selector(test) onThread:[ViewController shareThread] withObject:nil waitUntilDone:NO];
    
    - (void)test {
        NSLog(@"test:%@",[NSThread currentThread]);
    }
    
    结构图.png

    相关文章

      网友评论

          本文标题:进程、线程、任务、队列

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