GCD之栅栏函数、信号量、调度组、单例

一、单例

1.1 单例示例

    static dispatch_once_t onceToken;
    dispatch_once(onceToken, ^{
        <#code#>
    });

1.2 如何使用

参考链接:https://juejin.cn/post/7103015785685057572#heading-10

//单例初始化
+ (ClassType *)sharedManager {
    static ClassType *sharedManager = nil;
    static dispatch_once_t oneToken;
    dispatch_once(&oneToken,^{
        sharedManager = [[ClassType alloc] init];
    });
    return sharedManager;
}
//方法交换(保证只交换一次)
+ (void)load {
    static dispatch_once_t dispatchOnce;
    dispatch_once(&dispatchOnce, ^{
        Class cls = [self class];
        SEL originalSel = @selector(viewDidLoad);
        SEL swizzledSel = @selector(viewDidLoadSwizzled);
        Method originalMethod = class_getClassMethod(cls, originalSel);
        Method swizzledMethod = class_getClassMethod(cls, swizzledSel);
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

1.3 源码解析

我们可以看到这里分为了三个分支,我们逐步解析:

1.3.1 未执行分支

DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
        dispatch_function_t func)
{
        //执行block
    _dispatch_client_callout(ctxt, func);
        //设置执行状态
    _dispatch_once_gate_broadcast(l);
}

1.3.2 正在执行的分支

1.4 单例原理

1.使用dispatch_once_t来标记block执行的状态
2.如果是未执行的状态 ,则直接执行
3.如果是已经被执行过的,直接返回
4.如果是正在被其他线程执行的则等待

二、栅栏函数

2.1 栅栏函数的作用

等待栅栏函数前添加到队列里面的任务全部执行完成之后,才会执行栅栏函数里面的任务,栅栏函数里面的任务执行完成之后才会执行栅栏函数后面的队列里面的任务。

1. 栅栏函数只对同一队列起作用。
2. 栅栏函数对全局并发队列无效。
dispatch_barrier_async
dispatch_barrier_sync

2.2 栅栏函数示例

//(12) 3位于两者之间 (45)
    //栅栏函数栅不住全局函数
    //为什么无效:
    //因为全局并发队列也是系统要使用的,为了避免开发人员对系统的影响
    dispatch_queue_t t = dispatch_queue_create("lg", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(t, ^{
        NSLog(@"1");
    });
    dispatch_async(t, ^{
        NSLog(@"2");
    });
    // 栅栏函数
    dispatch_barrier_async(t, ^{
        NSLog(@"3");
    });
    NSLog(@"4");
    dispatch_async(t, ^{
        NSLog(@"5");
    });

2.3 栅栏函数的使用 (实现多读单写)

//栅栏函数实现多读单写(读写异步、读写互斥、写写互斥、读读可并行)
- (void)initReadWrite {
    self.readWriteQueue = dispatch_queue_create("com.demo.readWrite", DISPATCH_QUEUE_CONCURRENT);
}

- (void)write:(NSString *)str {
    dispatch_barrier_async(self.readWriteQueue, ^{
        usleep(40);
        NSLog(@"我是一个写任务");
    });
}

- (void)readWithId:(NSString *)taskId completion:(void (^)(NSString *str, NSError *error))completion{
    dispatch_async(self.readWriteQueue, ^{
        usleep(40);
        NSLog(@"我是一个读任务");
        completion(@"result",nil);
    });
}

注意:这里如果有多个读任务,再有一个写任务进来的时候,写任务会等读任务都完成了,再进行写操作,反之,如果队列里面有多个写任务,他们会一一执行完成,再读任务进入后会等写任务执行完,并发执行读任务。

三、调度组

3.1 调度组的作用:

等待调度组前面的任务执行完才会执行dispatch_group_notify函数里面的任务。 调度组和队列没有关系,只要是同一调度组就可以。

dispatch_group_create()
dispatch_group_enter
dispatch_group_leave
dispatch_group_notify

3.2 调度组的示例

在开发的过程中,当我们在一个界面中有多个请求的时,我们需要等所有请求全部回来后才能展示或者刷新UI,这时就需要用到我们的调度组了

- (void)groupTest01 {
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    [self asyncInvoke:^{
        NSLog(@"网络请求A回来了");
        dispatch_group_leave(group);
    }];
    dispatch_group_enter(group);
    [self asyncInvoke:^{
        NSLog(@"网络请求B回来了");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_enter(group);
    [self asyncInvoke:^{
        NSLog(@"网络请求C回来了");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"绘制UI");
    });
}

//模仿异步执行
- (void)asyncInvoke:(dispatch_block_t)block {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, block);
}

3.3 调度组的函数示例

dispatch_group_t g = dispatch_group_create();
    dispatch_queue_t que1 = dispatch_queue_create("lg1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t que2 = dispatch_queue_create("lg2", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_enter(g);
    dispatch_async(que1, ^{
        sleep(2);
        NSLog(@"1");
        dispatch_group_leave(g);
    });

    dispatch_group_enter(g);
    dispatch_async(que2, ^{
        sleep(3);
        NSLog(@"2");
        dispatch_group_leave(g);
    });
    
    dispatch_group_enter(g);
    dispatch_async(dispatch_get_global_queue(0, 0 ), ^{
        sleep(4);
        NSLog(@"3");
        dispatch_group_leave(g);
    });
    
    dispatch_group_enter(g);
    dispatch_async(dispatch_get_main_queue(), ^{
        sleep(5);
        NSLog(@"4");
        dispatch_group_leave(g);
    });

    dispatch_group_notify(g, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"5");
    });
    //12345

四、信号量

4.1 信号量的功能

//    dispatch_semaphore_create 创建信号量 指定信号量的大小
//    dispatch_semaphore_signal 发送信号量 将信号量+1
//    dispatch_semaphore_wait 等待信号量 当信号量为0 阻塞线程一直等待 ,当信号量的值大于等于1的时候 信号量-1
使用dispatch_semaphore_create 和一个初始值来创建一个信号量,当dispatch_semaphore_wait的时候,如果这个初始值大于0,初始值减1,代码继续往下执行,如果值等于0 则代码等待(忙等),知道值大于0的时候再减1继续执行,当dispatch_semaphore_signal的时候,信号量的值加1。

4.2 信号量的示例

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        dispatch_semaphore_signal(sem);
    });

    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        dispatch_semaphore_signal(sem);
    });

    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"3");
        dispatch_semaphore_signal(sem);
    });

    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSLog(@"4");
        dispatch_semaphore_signal(sem);
    });

    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"5");
        dispatch_semaphore_signal(sem);
    });
    //12345

4.3 信号量的作用-控制并发队列的数量

//信号量控制并发队列线程数量
- (void)semaphoreControlThreadCount {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.osDemo.semaphore.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    for (int i = 0; i < 10; i++) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(concurrentQueue, ^{
            usleep(arc4random_uniform(1000));
            NSLog(@"做完了一个耗时任务%@",[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
    }
}

4.4 信号量的源码解析

4.4.1 dispatch_semaphore_create

dispatch_semaphore_t
dispatch_semaphore_create(intptr_t value)
{
    dispatch_semaphore_t dsema;

    // If the internal value is negative, then the absolute of the value is
    // equal to the number of waiting threads. Therefore it is bogus to
    // initialize the semaphore with a negative value.
    //初始值必须大于等于0
    if (value < 0) {
        return DISPATCH_BAD_INPUT;
    }
    //开辟内存
    dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
            sizeof(struct dispatch_semaphore_s));
    dsema->do_next = DISPATCH_OBJECT_LISTLESS;
    dsema->do_targetq = _dispatch_get_default_queue(false);
    //保存初始值
    dsema->dsema_value = value;
    //初始化方法
    _dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    dsema->dsem_orig = value;
    return dsema;
}
  • 首先如果信号为小于0,则返回一个DISPATCH_BAD_INPUT类型对象,也就是返回个_Nonnull
  • 如果信号大于等于0,就会dispatch_semaphore_t对象dsema进行初始化,并返回dsema对象

4.4.2 dispatch_semaphore_wait

intptr_t
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
    long value = os_atomic_dec2o(dsema, dsema_value, acquire);
    if (likely(value >= 0)) {
        return 0;
    }
    return _dispatch_semaphore_wait_slow(dsema, timeout);
}
  • 对信号量的值-1,如果值大于等于0(原来的值大于0),啥也不干。
  • 果-1以后的值小于0,执行_dispatch_semaphore_wait_slow
DISPATCH_NOINLINE
static intptr_t
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
        dispatch_time_t timeout)
{
    long orig;

    _dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    switch (timeout) {
    default:
        if (!_dispatch_sema4_timedwait(&dsema->dsema_sema, timeout)) {
            break;
        }
        // Fall through and try to undo what the fast path did to
        // dsema->dsema_value
    case DISPATCH_TIME_NOW:
        orig = dsema->dsema_value;
        while (orig < 0) {
            if (os_atomic_cmpxchgv2o(dsema, dsema_value, orig, orig + 1,
                    &orig, relaxed)) {
                return _DSEMA4_TIMEOUT();
            }
        }
        // Another thread called semaphore_signal().
        // Fall through and drain the wakeup.
    case DISPATCH_TIME_FOREVER:
        _dispatch_sema4_wait(&dsema->dsema_sema);
        break;
    }
    return 0;
}
  • 这里对传入的timeout进行了判断,我们一般都是使用的DISPATCH_TIME_FOREVER
  • 执行_dispatch_sema4_wait
_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
    kern_return_t kr;
    do {
        kr = semaphore_wait(*sema);
    } while (kr == KERN_ABORTED);
    DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
  • 这里进行了do-while循环,相当于忙等,所以回到最初,如果wait的时候,信号量的值-1之后小于0,将会等待

4.4.3 dispatch_semaphore_signal

DISPATCH_NOINLINE
intptr_t
_dispatch_semaphore_signal_slow(dispatch_semaphore_t dsema)
{
    _dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    _dispatch_sema4_signal(&dsema->dsema_sema, 1);
    return 1;
}

intptr_t
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
    long value = os_atomic_inc2o(dsema, dsema_value, release);
    if (likely(value > 0)) {
        return 0;
    }
    if (unlikely(value == LONG_MIN)) {
        DISPATCH_CLIENT_CRASH(value,
                "Unbalanced call to dispatch_semaphore_signal()");
    }
    return _dispatch_semaphore_signal_slow(dsema);
}
  • 首先信号量的值+1
  • 如果加完,值大于0,直接返回
  • 否则执行 _dispatch_semaphore_signal_slow

4.4.5 信号量的crash(如果在create后直接调用wait则会crash)

    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    //这里会崩溃 这个函数是dispose

4.4.5 注意点

  • dispatch_semaphore可以控制GCD的最大并发数量
  • 信号量在使用的时候需要注意: dispatch_semaphore_wait 和 dispatch_semaphore_signal 一定要成对出现。因为在信号量释放的时候,如果dsema_orig初始信号量的大小大于dsema_value(通过dispatch_semaphore_wait和dispatch_semaphore_signal改变之后的信号量的大小)就会触发崩溃。

五、dispatch_source

dispatch_source是用来监听事件的,可以创建不同类型的dispatch_source来监听不同的事件。

5.1 dispatch_source直接监听的事件类型:

5.2 dispatch_source的几个方法

dispatch_source_create(创建源)
dispatch_source_set_event_handler(设置源文件回调)
dispatch_source_merge_data(源事件设置数据)
dispatch_source_get_data(获取源事件数据)
dispatch_resume(继续)
dispatch_suspend(挂起)
dispatch_source_cancel(取消源事件)

5.3 dispatch_source的示例(进度条)

#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIButton *iBt;
@property (weak, nonatomic) IBOutlet UIProgressView *iProgress;
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, assign) NSUInteger totalComplete;
@property (nonatomic ,assign) int iNum;

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.totalComplete = 0;
    self.queue = dispatch_queue_create("lg", DISPATCH_QUEUE_SERIAL);
    self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    dispatch_source_set_event_handler(self.source, ^{
        NSUInteger value = dispatch_source_get_data(self.source);
        self.totalComplete += value;
        NSLog(@"进度: %.2f",self.totalComplete/100.0);
        self.iProgress.progress = self.totalComplete/100.0;
    });
    
}

- (IBAction)btClick:(id)sender {
    
    if ([self.iBt.titleLabel.text isEqualToString:@"开始"]) {
        dispatch_resume(self.source);
        NSLog(@"开始了");
        self.iNum = 1;
        [sender setTitle:@"暂停" forState:UIControlStateNormal];
        
        for (int i= 0; i<1000; i++) {
            dispatch_async(self.queue, ^{
                sleep(1);
                dispatch_source_merge_data(self.source, self.iNum);
            });
        }
        
    } else {
        dispatch_suspend(self.source);
        NSLog(@"暂停了");
        self.iNum = 0;
        [sender setTitle:@"开始" forState:UIControlStateNormal];
    }
}

- (void)iTimer {
    
    __block int timeout = 60;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
    dispatch_source_set_event_handler(_timer, ^{
        
        if(timeout <= 0){
            dispatch_source_cancel(_timer);
        }
        else{
            timeout--;
            NSLog(@"倒计时:%d", timeout);
        }
    });
    dispatch_resume(_timer);
}

@end

posted on 2022-06-03 20:23  suanningmeng98  阅读(384)  评论(0编辑  收藏  举报