JHHK

欢迎来到我的个人网站
行者常至 为者常成

GCD

目录

概念介绍

旨在替代NSThread等线程技术,充分利用设备的多核。

首先先看几个概念:
同步:在当前线程执行任务,不具备开启新线程的能力

dispatch_sync 

异步:在新线程中执行任务,具备开启新线程的能力

dispatch_async

串行队列:在队列里的多个任务按先后顺序,一个一个取出。

dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);

并发队列:在队列里的多个任务允许同时取出。

dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);

主队列:主线程队列,属于串行队列,放入该队列的任务,在主线程执行。

dispatch_queue_t mainQueue = dispatch_get_main_queue();

全局并发队列:属于并发队列的一种,可全局获得。

dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); 

同步

1.同步串行队列

dispatch_sync(serialQueue, ^{
    NSLog(@"block0");
});

dispatch_sync(serialQueue, ^{
    NSLog(@"block1");
});

下面代码会产生死锁,serialQueue 串行队列要求一个一个执行,block0 执行完之后才会执行block1,但block1的执行包含在block0里边,造成互相等待。block0执行完的前提是执行block1,但block1执行的前提是先执行完block0。

dispatch_sync(serialQueue, ^{//block0
    NSLog(@"block0");
    
    dispatch_sync(serialQueue, ^{//block1
        NSLog(@"block1");
    });
});

下面代码同样会产生死锁,主队列内放置着一个一个待执行的任务,并在主线程按顺序取出执行。dispatch_sync这个代码块就是其中的一个任务,block0是另一个任务。dispatch_sync执行完的前提是执行block0,但block0要想执行需要代码块先执行完毕。所以造成死锁。

//在主线程执行以下代码
dispatch_sync(mainQueue, ^{//block0
    NSLog(@"block0");
});

2.同步并发队列
同样是在当前线程执行,但不会产生死锁。并发队列对任务的取出没有顺序上的要求。

dispatch_sync(concurrentQueue, ^{//block0
    NSLog(@"block0");
});

dispatch_sync(concurrentQueue, ^{//block1
    NSLog(@"block1");
});
dispatch_sync(concurrentQueue, ^{//block0
    NSLog(@"block0");

    dispatch_sync(concurrentQueue, ^{//block1
        NSLog(@"block1");
    });
});

异步

1.异步串行队列
异步会开辟一个子线程,如果是串行队列只会开辟一条子线程

dispatch_async(serialQueue, ^{//block0
    NSLog(@"block0");
});

dispatch_async(serialQueue, ^{//block1
    NSLog(@"block1");
});

开辟一条子线程,任务放在串行队列,但async与sync不同的是不要求任务立即执行,block1可以等block0执行完后再执行,所以不会产生死锁。

dispatch_async(serialQueue, ^{//block0
    NSLog(@"block0");
    
    dispatch_async(serialQueue, ^{//block1
        NSLog(@"block1");
    });
});

mainQueue主队列,不会开辟子线程,在主线程执行,并不会发生死锁。

dispatch_async(mainQueue, ^{//block0
    NSLog(@"block0");
});

//不用等async任务执行,就可以直接来到此处

分析下面两种情况是否会造成死锁:

  • 情形一:
    dispatch_sync(serialQueue, ^{//block0
      NSLog(@"block0");
        
      dispatch_async(serialQueue, ^{//block1
          NSLog(@"block1");
      });
    
      //不用等async任务执行,就可以直接来到此处
    });
    
  • 情形二:
    dispatch_async(serialQueue, ^{//block0
      NSLog(@"block0");
        
      //同步任务,必须执行
      dispatch_sync(serialQueue, ^{//block1
          NSLog(@"block1");
      });
    });
    

    根据上面的分析,情形一不会死锁,情形二会发生死锁。

2.异步并发队列

dispatch_async(concurrentQueue, ^{//block0
    NSLog(@"block0");
});

dispatch_async(concurrentQueue, ^{//block1
    NSLog(@"block1");
});
dispatch_async(concurrentQueue, ^{//block0
    NSLog(@"block0");

    dispatch_async(concurrentQueue, ^{//block1
        NSLog(@"block1");
    });
});

线程组

-(void)GCD_GroupTest{
    //全局队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    //创建线程组
    dispatch_group_t group = dispatch_group_create();
    
    //添加任务
    dispatch_group_async(group, globalQueue, ^{
        NSLog(@"block0-%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, globalQueue, ^{
        NSLog(@"block1-%@",[NSThread currentThread]);
    });
    
    //等待通知
    dispatch_group_notify(group, globalQueue, ^{
        NSLog(@"block2-%@",[NSThread currentThread]);
    });
    
    dispatch_group_notify(group, globalQueue, ^{
        NSLog(@"block3-%@",[NSThread currentThread]);
    });
}
- (void)groupDemo2{
    
    // 问题: 如果 dispatch_group_enter 多 dispatch_group_leave 不会调用通知
    // dispatch_group_enter 少 dispatch_group_leave  奔溃
    // 成对存在
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"第一个走完了");
        dispatch_group_leave(group);
    });
    

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"第二个走完了");
        dispatch_group_leave(group);
    });
    
    //如果没有 dispatch_group_leave(group); 就不会来到dispatch_group_notify
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有任务完成,可以更新UI");
    });
}

其它

1.一次执行代码

-(void)GCD_Once{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"lilog - once");
    });
}

2.延迟执行

-(void)GCD_After{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"大牛");

    });
    NSLog(@"IT民工");
}

3.实现加锁

//利用GCD的串行队列 来实现加锁
-(void)GCD_SerialQueue_Lock{
    __weak typeof(self) weakSelf = self;
    //创建一个串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        [weakSelf.task freeMoney:10];
    });
    
    dispatch_async(serialQueue, ^{
        [weakSelf.task drawMoney:10];
    });
}

barrier

需求:有4个任务{1,2,3,4},执行完前2个再执行后2个

这里我们用到栅栏函数dispatch_barrier_(a)sync,(也可以用队列组),我们要注意的是不能使用全局并发队列(系统提供给我们的)否则会散失栅栏函数的意义

区别:先看官方文档

dispatch_barrier_sync: Submits a barrier block object for execution and waits until that block completes.(提交一个栅栏函数在执行中,它会等待栅栏函数执行完)

dispatch_barrier_async: Submits a barrier block for asynchronous execution and returns immediately.(提交一个栅栏函数在异步执行中,它会立马返回)

作者理解:dispatch_barrier_sync 需要等待栅栏执行完才会执行栅栏后面的任务,而dispatch_barrier_async 无需等待栅栏执行完,会继续往下走(保留在队列里)

原因:在同步栅栏时栅栏函数在主线程中执行,而异步栅栏中开辟了子线程栅栏函数在子线程中执行

一、先看代码dispatch_barrier_sync

-(void)barrierSync{
    NSLog(@"[lilog]:start");
    
    dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"[lilog]:task1");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"[lilog]:task2");
    });
    
    dispatch_barrier_sync(queue, ^{
        NSLog(@"[lilog]:barrier_sync");
    });
    
    
    dispatch_async(queue, ^{
        NSLog(@"[lilog]:task3");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"[lilog]:task4");
    });
    
    NSLog(@"[lilog]:last");
}

打印:

[lilog]:start
[lilog]:task1
[lilog]:task2
[lilog]:barrier_sync
[lilog]:last
[lilog]:task3
[lilog]:task4

再次打印

[lilog]:start
[lilog]:task1
[lilog]:task2
[lilog]:barrier_sync
[lilog]:last
[lilog]:task4
[lilog]:task3

二、看看dispatch_barrier_async

-(void)barrierAsync{
    NSLog(@"[lilog]:start");
    
    dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"[lilog]:task1");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"[lilog]:task2");
    });


    dispatch_barrier_async(queue, ^{
        NSLog(@"[lilog]:barrier_async");
    });
    
    
    dispatch_async(queue, ^{
        NSLog(@"[lilog]:task3");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"[lilog]:task4");
    });
    
    NSLog(@"[lilog]:last");
}

打印:

[lilog]:start
[lilog]:last
[lilog]:task1
[lilog]:task2
[lilog]:barrier_async
[lilog]:task3
[lilog]:task4

再次打印

[lilog]:start
[lilog]:task1
[lilog]:last
[lilog]:task2
[lilog]:barrier_async
[lilog]:task3
[lilog]:task4

栅栏的作用:

1、同步,调整多线程的调用顺序

2、加锁,栅栏块的block是线程安全的,不会被多线程调用。

3、栅栏块之前添加的任务未执行完不会执行栅栏块的任务。栅栏块其后添加的任务,在栅栏块任务执行完之前,永远得不到执行。

三、特别注意

The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_sync function.

官方说明大意:在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用

dispatch_barrier_sync 等同于 dispatch_sync
dispatch_barrier_async 等同于 dispatch_async

semaphore

semaphore叫做”信号量”

信号量的初始值,可以用来控制线程并发访问的最大数量

信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

    //信号量的初始值
    int value = 1;
    
    //初始化信号量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
    
    //如果信号量的值<=0,当前线程就会进入休眠等待(直到信号量>0)
    //如果信号量的值>0,就减1,然后往下执行后面的代码。
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    //让信号量的值加1
    dispatch_semaphore_signal(semaphore);
-(void)semaphoreTest{
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 信号量 -- gcd控制并发数
    //总结:由于设定的信号值为2,先执行三个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2
    //如果信号量为1,则可实现加锁,同步执行
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    
    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务1");
        sleep(1);
        NSLog(@"任务1完成");
        dispatch_semaphore_signal(semaphore);
    });
    
    //任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务2");
        sleep(1);
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(semaphore);
    });
    
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务3");
        sleep(1);
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(semaphore);
    });
}

dispatch_source

参考文章:iOS多线程——Dispatch Source

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    self.queue = dispatch_queue_create("xxxx", 0);
    
    /**
     第一个参数:dispatch_source_type_t type为设置GCD源方法的类型,前面已经列举过了。
     第二个参数:uintptr_t handle Apple的API介绍说,暂时没有使用,传0即可。
     第三个参数:unsigned long mask Apple的API介绍说,使用DISPATCH_TIMER_STRICT,会引起电量消耗加剧,毕竟要求精确时间,所以一般传0即可,视业务情况而定。
     第四个参数:dispatch_queue_t _Nullable queue 队列,将定时器事件处理的Block提交到哪个队列之上。可以传Null,默认为全局队列。注意:当提交到全局队列的时候,时间处理的回调内,需要异步获取UI线程,更新UI...不过这好像是常识,又啰嗦了...
     */
    
    
    self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    
    
    // 保存代码块 ---> 异步 dispatch_source_set_event_handler()
    // 设置取消回调 dispatch_source_set_cancel_handler(dispatch_source_t source,dispatch_block_t _Nullable handler)
    // 封装我们需要回调的触发函数 -- 响应
    dispatch_source_set_event_handler(self.source, ^{
        
        NSUInteger value = dispatch_source_get_data(self.source); // 取回来值 1 响应式

    });
    
    dispatch_resume(self.source);

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    for (NSUInteger index = 0; index < 100; index++) {
        dispatch_async(self.queue, ^{
            
            sleep(2);

            dispatch_source_merge_data(self.source, 1); // source 值响应
        });
    }
}


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.