• 线程相关


    线程相关

    异步任务&同步任务

    两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

    • 同步执行(sync)
      • 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
      • 只能在当前线程中执行任务,不具备开启新线程的能力。
    • 异步执行(async)
      • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
      • 可以在新的线程中执行任务,具备开启新线程的能力。
    1
    2
    3
    4
    GCD同步
    dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
    GCD异步
    dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
    队列(Dispatch Queue)

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

    • 串行队列(Serial Dispatch Queue)
      • 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
      • DISPATCH_QUEUE_SERIAL 表示串行队列
      • dispatch_get_main_queue() 属于串行队列
    • 并发队列(Concurrent Dispatch Queue)
      • 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
      • DISPATCH_QUEUE_CONCURRENT 表示并发队列。
      • dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);属于并发队列。第一个参数表示队列优先级,一般用 DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用 0 即可
    1
    2
    3
    4
    5
    6
    7
    8
    第一个参数表示队列的唯一标识符,用于 DEBUG,可为空。队列的名称推荐使用应用程序 ID 这种逆序全程域名。
    第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列
    dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)
    // 串行队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("com.testQueue", DISPATCH_QUEUE_SERIAL);
    // 并发队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("com.testQueue", DISPATCH_QUEUE_CONCURRENT);
    区别 并发队列 串行队列 主队列
    同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁卡住不执行
    异步(async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务
    区别 『异步执行+并发队列』嵌套『同一个并发队列』 『同步执行+并发队列』嵌套『同一个并发队列』 『异步执行+串行队列』嵌套『同一个串行队列』 『同步执行+串行队列』嵌套『同一个串行队列』
    同步(sync) 没有开启新的线程,串行执行任务 没有开启新线程,串行执行任务 死锁卡住不执行 死锁卡住不执行
    异步(async) 有开启新线程,并发执行任务 有开启新线程,并发执行任务 有开启新线程(1 条),串行执行任务 有开启新线程(1 条),串行执行任务

    开辟线程的个数和异步+并发队列有关。

    队列是依托于线程而存在的,只有一个线程串行队列并行队列并没有什么不同。所以可以先分析线程的个数再分析队列从而得到执行的步骤。

    GCD 延时执行方法:dispatch_after

    我们经常会遇到这样的需求:在指定时间(例如 3 秒)之后执行某个任务。可以用 GCD 的dispatch_after方法来实现。
    需要注意的是:dispatch_after方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after方法是很有效的

    1
    2
    3
    4
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2.0 秒后异步追加任务代码到主队列,并开始执行
    NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
    });
    GCD 栅栏方法:dispatch_barrier_async

    dispatch_barrier_async 方法会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在 dispatch_barrier_async 方法追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
    // 追加任务 1
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
    });
    dispatch_async(queue, ^{
    // 追加任务 2
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
    });
    dispatch_barrier_async(queue, ^{
    // 追加任务 barrier
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
    });
    dispatch_async(queue, ^{
    // 追加任务 3
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
    });

    实现同步锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    dispatch_queue_t quene = dispatch_get_global_queue(0, 0);
    - (void)setTestStr:(NSString *)testStr {
    dispatch_barrier_async(quene, ^{
    _testStr = testStr;
    });
    }
    - (NSString *)testStr {
    __block NSString *tempStr;
    dispatch_sync(quene, ^{
    tempStr = _testStr;
    });
    return tempStr;
    大专栏  线程相关ass="line">}
    GCD 一次性代码(只执行一次):dispatch_once

    整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。

    1
    2
    3
    4
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    // 只执行 1 次的代码(这里面默认是线程安全的)
    });
    GCD 快速迭代方法:dispatch_apply

    dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。

    如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。

    我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以 在多个线程中同时(异步)遍历多个数字。

    还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。

    1
    2
    3
    4
    5
    6
    7
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
    NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
    GCD队列组:dispatch_group

    调用队列组的 dispatch_group_async先把任务放到队列中,然后将队列放入队列组中异步执行。如不调用队列组则是顺序执行。或者使用队列组的 dispatch_group_enterdispatch_group_leave组合来实现 dispatch_group_async

    调用队列组的 dispatch_group_notify回到指定线程执行任务。或者使用 dispatch_group_wait回到当前线程继续向下执行(会阻塞当前线程)

    dispatch_group_enter标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1

    dispatch_group_leave标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。

    当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait解除阻塞,以及执行追加到 dispatch_group_notify中的任务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
    NSLog(@"group---begin");
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 追加任务 1
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 追加任务 2
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
    NSLog(@"group---end");
    });
    //或
    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group---end");
    GCD 信号量:dispatch_semaphore

    信号量是基于计数器的一种多线程同步机制,用来管理对资源的并发访问。(类似于NSOperationQueue的设置最大并发量)

    Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时等待,阻塞线程。计数为 0 或大于 0 时,计数减 1 ,继续运行。

    dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量

    dispatch_semaphore_signal:发送一个信号,让信号总量加 1

    dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。

    实现线程同步,将异步执行任务转换为同步执行任务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    /**
    * semaphore 线程同步
    */
    - (void)semaphoreSync {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
    NSLog(@"semaphore---begin");
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    __block int number = 0;
    dispatch_async(queue, ^{
    // 追加任务 1
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
    number = 100;
    dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
    }

    执行顺如下:

    1. semaphore 初始创建时计数为 0。
    2. 异步执行任务 1追加到队列之后,不做等待,接着执行 dispatch_semaphore_wait方法,semaphore 减 1,此时 semaphore == -1,当前线程进入等待状态。
    3. 然后,异步任务 1 开始执行。任务 1 执行到 dispatch_semaphore_signal之后,总信号量加 1,此时 semaphore == 0,正在被阻塞的线程(主线程)恢复继续执行。
    4. 最后打印 semaphore---end,number = 100

    这样就实现了线程同步,将异步执行任务转换为同步执行任务。

  • 相关阅读:
    centos7.3下安装pip和virtualenv以及配置virtualenvwarpper
    win10环境:python虚拟环境的安装和配置与scrapy工程创建
    centos6.5腾讯云django环境部署---2、Gunicorn+Django+nginx+mysql部署
    centos6.5腾讯云django环境部署记录---1、系统准备
    js观察者模式发布/订阅
    【水文】帝都实习前夜
    git指令整理
    nodeJs爬取网页数据
    domReady和onload
    js零碎知识
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12275643.html
Copyright © 2020-2023  润新知