GCD的简单使用: 队列,任务,信号量, semaphore,group, wait, enter, leave

1. GCD的两个核心概念: 任务和队列

  • 任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。
    在 GCD 中是放在 block 中的。
  • 执行任务有两种方式:同步执行(sync)和异步执行(async)。
  • 两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
  • 创建串行队列:
dispatch_queue_t queue = dispatch_queue_create("自定义队列名", NULL);
//或
dispatch_queue_t queue = dispatch_queue_create("自定义队列名", DISPATCH_QUQUE_SERIAL);
  • 创建并发队列:
dispatch_queue_t queue = dispatch_queue_create("自定队列名", DISPATCH_QUQUE_CONCURRENT);

  • 同步执行(sync):
    同步添加任务到 当前 队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
    只能在当前线程中执行任务,不具备开启新线程的能力。
dispatch_sync(queue, ^{  ...  });
  • 异步执行(async):
    异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
    可以在新的线程中执行任务,具备开启新线程的能力。
dispatch_async(queue, ^{ ... });
  • 死锁: 因为把任务2添加到了主线程队列中,但是串行队列是顺序执行的, 所以任务二必须要等待当前任务执行完了以后才能执行任务2;但是当前任务就是执行这三个任务,所以当前任务无法执行完, 任务2也不会执行。
/// 以下代码在主线程执行
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
     NSLog(@"执行任务2");
});
NSLog(@"执行任务3");

2.GCD 延时执行方法:dispatch_after

- (void)GCDAfter
{
    NSLog(@"初始化完成");
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"两秒后执行这句代码--线程:%@",[NSThread currentThread]);
    });
}

打印结果:

2018-04-01 15:28:30.183169+0800  初始化完成
2018-04-01 15:28:32.361869+0800  两秒后执行这句代码--线程:<NSThread: 0x60400006b7c0>{number = 1, name = main}

3. GCD 一次性代码(只执行一次):dispatch_once

- (void)GCDOnce
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只会执行一次这里的代码, 默认是线程安全的,  主要运用与单例的创建
    });
}

4. GCD 快速迭代方法:dispatch_apply

- (void)GCDApply
{
    NSLog(@"初始化完成");
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"开始");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"index:%zd---线程:%@", index, [NSThread currentThread]);
    });
    NSLog(@"结束");
}

5. GCD 栅栏方法:dispatch_barrier_async

我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。
这样我们就需要一个相当于 栅栏 一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。
这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。

下面代码演示:

- (void)GCDBarrier
{
    NSLog(@"初始化完成");
    dispatch_queue_t queue = dispatch_queue_create("testGCD.lianchen", DISPATCH_QUEUE_CONCURRENT);
    // 第一个任务
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
        NSLog(@"执行第一个任务");
    });
    // 第二个任务
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
        NSLog(@"执行第二个任务");
    });
    // 执行 barrie 任务
    dispatch_barrier_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
        NSLog(@"执行 barrie 任务");
    });
    // 第三个任务
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
        NSLog(@"执行第三个任务");
    });
    // 第四个任务
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
        NSLog(@"执行第四个任务");
    });
}

执行结果:

2018-04-01 10:46:37.446851+0800  初始化完成
2018-04-01 10:46:39.447319+0800  执行第一个任务
2018-04-01 10:46:39.447321+0800  执行第二个任务
2018-04-01 10:46:41.448313+0800  执行 barrie 任务
2018-04-01 10:46:43.453644+0800  执行第四个任务
2018-04-01 10:46:43.453644+0800  执行第三个任务

6.GCD 的队列组:dispatch_group

6.1 dispatch_group_notify

- (void)GCDGroup_notify
{
    NSLog(@"初始化完成");
    dispatch_group_t group =  dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"执行耗时操作1");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"执行耗时操作2");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"执行完上面的两个异步任务后, 才会执行这里");
    });
}

运行结果:

2018-04-01 20:35:57.330323+0800  初始化完成
2018-04-01 20:35:59.335313+0800  执行耗时操作2
2018-04-01 20:35:59.335313+0800  执行耗时操作1
2018-04-01 20:35:59.335694+0800  执行完上面的两个异步任务后, 才会执行这里

6.2 dispatch_group_wait

  • 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
- (void)GCDGroup_wait
{
    NSLog(@"初始化完成");
    for (int i = 0; i < 10; i++) {
        NSLog(@"打印顺序:%d", i);
        if (i == 5) {
            dispatch_group_t group =  dispatch_group_create();
            dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                [NSThread sleepForTimeInterval:2];
                NSLog(@"执行耗时操作1");
            });
            dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                [NSThread sleepForTimeInterval:2];
                NSLog(@"执行耗时操作2");
            });
            dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
            NSLog(@"执行完上面的两个异步任务后, 才会往下执行");
        }
    }
}

6.3dispatch_group enter , leave

  • dispatch_group_enter 标志着一个任务追加到 group, 相当于group中未执行的任务数量 +1
  • dispatch_group_leave 标志着一个任务追加到 group, 相当于group中未执行的任务数量 -1
  • 当 group 中未执行完毕任务数为0的时候
    才会使dispatch_group_wait解除阻塞
    以及执行追加到dispatch_group_notify中的任务
  • 所以 enter与leave可以配合dispatch_group_wait与dispatch_group_notify使用, 两个的区别是一个会阻塞主线程, 一个不会阻塞主线程.
- (void)GCDGroup_enter_leave
{
    NSLog(@"初始化完成");
    for (int i = 0; i < 10; i++) {
        NSLog(@"打印顺序:%d", i);
        if (i == 5) {
            dispatch_group_t group =  dispatch_group_create();
            dispatch_group_enter(group);
            dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                [NSThread sleepForTimeInterval:2];
                NSLog(@"执行耗时操作1");
                dispatch_group_leave(group);
            });
            dispatch_group_enter(group);
            dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                [NSThread sleepForTimeInterval:2];
                NSLog(@"执行耗时操作2");
                dispatch_group_leave(group);
            });
//            dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//                NSLog(@"执行完上面的两个异步任务后, 才会执行这里, 但是这里不会阻塞主线程");
//            });
            dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
            NSLog(@"执行完上面的两个异步任务后, 才会往下执行, 这里会阻塞主线程");
        }
    }
}

打印结果:

2018-04-01 21:04:08.115499+0800  初始化完成
2018-04-01 21:04:08.115753+0800  打印顺序:0
2018-04-01 21:04:08.115892+0800  打印顺序:1
2018-04-01 21:04:08.115997+0800  打印顺序:2
2018-04-01 21:04:08.116150+0800  打印顺序:3
2018-04-01 21:04:08.116320+0800  打印顺序:4
2018-04-01 21:04:08.116484+0800  打印顺序:5
2018-04-01 21:04:10.117688+0800  执行耗时操作2
2018-04-01 21:04:10.117729+0800  执行耗时操作1
2018-04-01 21:04:10.118392+0800  执行完上面的两个异步任务后, 才会往下执行, 这里会阻塞主线程
2018-04-01 21:04:10.118589+0800  打印顺序:6
2018-04-01 21:04:10.118866+0800  打印顺序:7
2018-04-01 21:04:10.119055+0800  打印顺序:8
2018-04-01 21:04:10.119220+0800  打印顺序:9

6.4 GCD 信号量:dispatch_semaphore

6.4.1 semaphore的简单使用:

  • 在 Dispatch Semaphore 中,使用计数来完成这个功能, 计数为0时等待,不可通过。
  • 计数为1或大于1时,计数减1且不等待,可通过。
  • Semaphore 提供三个函数:
1. dispatch_semaphore_t semaphore = dispatch_semaphore_create(long value);
创建一个Semaphore并初始化信号的总量
2. long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
发送一个信号,  使信号总量加 1
3. long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
接收一个信号和时间值, 若信号的信号量为0, 则会阻塞主当前线程, 知道信号量大于0, 程序才会往下执行, 同时信号总数 会 -1

看代码:

- (void)GCDSemaphore
{
    NSLog(@"初始化完成");
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    __block int a = 0;
    dispatch_async(queue, ^{
        a = 99;
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"a = %d", a);
}

结果输出为 a = 99;

6.4.2 Dispatch Semaphore线程安全(为线程加锁):

先看代码:

- (void)GCDSemaphoreLock
{
    NSLog(@"初始化完成");
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    for (int i = 0; i < 100; i++) {
        dispatch_async(queue, ^{
            // 相当于加锁
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [NSThread sleepForTimeInterval:1];
            /// 这里执行耗时操作
            NSLog(@"这里执行耗时操作");
            // 相当于解锁
            dispatch_semaphore_signal(semaphore);
        });
    }
}
  • 当线程1执行到dispatch_semaphore_wait这一行时,semaphore的信号量为1,所以使信号量-1变为0,并且线程1继续往下执行;
    如果当在线程1的耗时操作还没执行完的时候,又有线程2来访问,执行dispatch_semaphore_wait时由于此时信号量为0,且时间为DISPATCH_TIME_FOREVER,所以会一直阻塞线程2(此时线程2处于等待状态),直到线程1执行完耗时操作并执行完dispatch_semaphore_signal使信号量为1后,信号总量为0后, 线程2才能解除阻塞继续住下执行。以上可以保证同时只有一个线程执行耗时操作这代码。
  • 再举个栗子: 关于售票
    三个地方同时售一趟车的票
- (void)GCDSemaphoreLock
{
    NSLog(@"初始化完成: 买票示例");
    self.ticketCount = 50;
    /// queue1 代表北京售票点
    dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // queue2 代表上海售票点
    dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // queue3 代表广州售票点
    dispatch_queue_t queue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf buyTicketSafe:semaphore];
    });
    dispatch_async(queue2, ^{
        [weakSelf buyTicketSafe:semaphore];
    });
    dispatch_async(queue3, ^{
        [weakSelf buyTicketSafe:semaphore];
    });
}
/// 线程安全买票
- (void)buyTicketSafe:(dispatch_semaphore_t)semaphore
{
    // 相当于加锁
    while (1) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        if (self.ticketCount > 0) {
            self.ticketCount -= 1;
            NSLog(@"剩余票数:%ld, 当前线程:%@", self.ticketCount, [NSThread currentThread]);
            [NSThread sleepForTimeInterval:0.3];
        }else {
            // 售完
            NSLog(@"票已售罄!");
            // 相当于解锁
            dispatch_semaphore_signal(semaphore);
            break;
        }
        // 没执行一次解锁一次
        // 相当于解锁
        dispatch_semaphore_signal(semaphore);
    }
}

看输出结果:

2018-04-01 22:29:16.542819+0800  初始化完成: 买票示例
2018-04-01 22:29:16.543259+0800  剩余票数:49, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
2018-04-01 22:29:16.845560+0800  剩余票数:48, 当前线程:<NSThread: 0x600000464ac0>{number = 4, name = (null)}
2018-04-01 22:29:17.148390+0800  剩余票数:47, 当前线程:<NSThread: 0x600000465140>{number = 5, name = (null)}
2018-04-01 22:29:17.453090+0800  剩余票数:46, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
2018-04-01 22:29:17.754377+0800  剩余票数:45, 当前线程:<NSThread: 0x600000464ac0>{number = 4, name = (null)}
2018-04-01 22:29:18.055701+0800  剩余票数:44, 当前线程:<NSThread: 0x600000465140>{number = 5, name = (null)}
2018-04-01 22:29:18.357465+0800  剩余票数:43, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
..........
2018-04-01 22:29:29.290197+0800  剩余票数:7, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
2018-04-01 22:29:29.594207+0800  剩余票数:6, 当前线程:<NSThread: 0x600000464ac0>{number = 4, name = (null)}
2018-04-01 22:29:29.895484+0800  剩余票数:5, 当前线程:<NSThread: 0x600000465140>{number = 5, name = (null)}
2018-04-01 22:29:30.197834+0800  剩余票数:4, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
2018-04-01 22:29:30.498786+0800  剩余票数:3, 当前线程:<NSThread: 0x600000464ac0>{number = 4, name = (null)}
2018-04-01 22:29:30.802413+0800  剩余票数:2, 当前线程:<NSThread: 0x600000465140>{number = 5, name = (null)}
2018-04-01 22:29:31.103086+0800  剩余票数:1, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
2018-04-01 22:29:31.404222+0800  剩余票数:0, 当前线程:<NSThread: 0x600000464ac0>{number = 4, name = (null)}
2018-04-01 22:29:31.708613+0800  票已售罄!
2018-04-01 22:29:31.709075+0800  票已售罄!
2018-04-01 22:29:31.709347+0800  票已售罄!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容