iOS底层探索-GCD单例、阻塞使用

978 阅读9分钟

1、单例(dispatch_once)

#ifdef __BLOCKS__
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
    // 以 val 做为标记是否被执行过
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
#endif

1.1、dispatch_once_f

DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    // val 转换为 dispatch_once_gate_t 类型
    // 1、执行过的直接返回
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
    if (likely(v == DLOCK_ONCE_DONE)) {
        return;
    }
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    if (likely(DISPATCH_ONCE_IS_GEN(v))) {
        return _dispatch_once_mark_done_if_quiesced(l, v);
    }
#endif
#endif
    // 2、没执行过的先进行判断再进行调用
    if (_dispatch_once_gate_tryenter(l)) {
        return _dispatch_once_callout(l, ctxt, func);
    }
    // 3、正在执行的进入等待
    return _dispatch_once_wait(l);
}

1.2、_dispatch_once_gate_tryenter

DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
    // os_atomic_cmpxchg:原子操作、比较+交换
    return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
            (uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}
  • DLOCK_ONCE_UNLOCKED代表还未被执行过
  • &l->dgo_once 等于 DLOCK_ONCE_UNLOCKED 时将 (uintptr_t)_dispatch_lock_value_for_self() 赋值给 dgo_once 并返回true,否则返回false

1.3、_dispatch_once_callout

DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
dispatch_function_t func)
{
    // 执行方法
    _dispatch_client_callout(ctxt, func);
    // 改变是否执行过的标记
    _dispatch_once_gate_broadcast(l);
}
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
    dispatch_lock value_self = _dispatch_lock_value_for_self();
    uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    v = _dispatch_once_mark_quiescing(l);
#else
    // 改变是否执行过的标记
    v = _dispatch_once_mark_done(l);
#endif
    if (likely((dispatch_lock)v == value_self)) return;
    _dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
}
DISPATCH_ALWAYS_INLINE
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
    // 将 &dgo->dgo_once 的值变为 DLOCK_ONCE_DONE
    return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}

1.4、_dispatch_once_wait

void
_dispatch_once_wait(dispatch_once_gate_t dgo)
{
    dispatch_lock self = _dispatch_lock_value_for_self();
    uintptr_t old_v, new_v;
    dispatch_lock *lock = &dgo->dgo_gate.dgl_lock;
    uint32_t timeout = 1;

    //不断循环
    for (;;) {
        //当 &dgo->dgo_once 值变为 DLOCK_ONCE_DONE 跳出循环
        os_atomic_rmw_loop(&dgo->dgo_once, old_v, new_v, relaxed, {
        if (likely(old_v == DLOCK_ONCE_DONE)) {
            os_atomic_rmw_loop_give_up(return);
        }
        ……
        (void)timeout;
    }
}

小结

主要在 dispatch_once_f 中进行,分为3步:

  1. 执行过方法的直接返回
  2. 未执行过方法的进行判断,若是 DLOCK_ONCE_UNLOCKED 状态则返回true,然后执行方法并将状态变为 DLOCK_ONCE_DONE
  3. 若是正在执行中则进入循环,不停的判断状态,直到状态变成了 DLOCK_ONCE_DONE 退出循环

2、栅栏函数

void
dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work)
{
    uintptr_t dc_flags = DC_FLAG_BARRIER | DC_FLAG_BLOCK;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    }
    // _dispatch_barrier_sync_f
    _dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

底层流程

我们以同步栅栏函数 dispatch_barrier_sync 为例,整体结构与同步函数高度相似;异步栅栏函数更为复杂,但与同步栅栏函数功能区别就是同步函数与异步函数的区别

2.1、_dispatch_barrier_sync_f

DISPATCH_NOINLINE
static void
_dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
    _dispatch_barrier_sync_f_inline(dq, ctxt, func, dc_flags);
}

2.2、_dispatch_barrier_sync_f_inline

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
    dispatch_tid tid = _dispatch_tid_self();

    if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
        DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
    }

    dispatch_lane_t dl = upcast(dq)._dl;
    DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
    if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {
        // _dispatch_sync_f_slow 是下断点流程会进入的方法
        return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,
                DC_FLAG_BARRIER | dc_flags);
    }

    if (unlikely(dl->do_targetq->do_targetq)) {
        return _dispatch_sync_recurse(dl, ctxt, func,
                DC_FLAG_BARRIER | dc_flags);
    }
    _dispatch_introspection_sync_begin(dl);
    _dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
          DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
                    dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));
}
  • 通过查看都有哪些方法调用了func参数(即任务block),然后将这些方法打上符号断点,运行后发现走到了 dispatch_sync_f_slow 方法中
  • dispatch_sync_f_slow

2.3、dispatch_sync_f_slow

image.png

2.3.1、_dispatch_sync_invoke_and_complete_recurse
DISPATCH_NOINLINE
static void
_dispatch_sync_invoke_and_complete_recurse(dispatch_queue_class_t dq,
        void *ctxt, dispatch_function_t func, uintptr_t dc_flags
        DISPATCH_TRACE_ARG(void *dc))
{
    // 调用方法
    _dispatch_sync_function_invoke_inline(dq, ctxt, func);
    _dispatch_trace_item_complete(dc);
    // 拔出栅栏,执行后边代码
    _dispatch_sync_complete_recurse(dq._dq, NULL, dc_flags);
}
  • _dispatch_sync_function_invoke_inline

    DISPATCH_ALWAYS_INLINE
    static inline void
    _dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt,
            dispatch_function_t func)
    {
        dispatch_thread_frame_s dtf;
        _dispatch_thread_frame_push(&dtf, dq);
        // 具体调用的方法
        _dispatch_client_callout(ctxt, func);
        _dispatch_perfmon_workitem_inc();
        _dispatch_thread_frame_pop(&dtf);
    }
    
  • _dispatch_sync_complete_recurse

    DISPATCH_NOINLINE
    static void
    _dispatch_sync_complete_recurse(dispatch_queue_t dq, dispatch_queue_t stop_dq,
            uintptr_t dc_flags)
    {
        bool barrier = (dc_flags & DC_FLAG_BARRIER);
        // do while阻塞线程
        do {
            if (dq == stop_dq) return;
            if (barrier) {
                // 有栅栏时通过 dx_wakeup 唤醒后边队列中的任务
                dx_wakeup(dq, 0, DISPATCH_WAKEUP_BARRIER_COMPLETE);
            } else {
                // 没有栅栏时调用后边代码        
                _dispatch_lane_non_barrier_complete(upcast(dq)._dl, 0);
            }
            dq = dq->do_targetq;
            barrier = (dq->dq_width == 1);
        } while (unlikely(dq->do_targetq));
    }
    

    唤醒方法由 dx_wakeup 转为 dq_wakeup

    #define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
    

    image.png

    • 上篇GCD中分析异步时也找到过这里(证明了异步操作了线程相关内容),这次看的是 不同的队列类型有不同的唤醒方式

    • _dispatch_root_queue_wakeup

      DISPATCH_NOINLINE
      void
      _dispatch_root_queue_wakeup(dispatch_queue_global_t dq,
              DISPATCH_UNUSED dispatch_qos_t qos, dispatch_wakeup_flags_t flags)
      {
          if (!(flags & DISPATCH_WAKEUP_BLOCK_WAIT)) {
              DISPATCH_INTERNAL_CRASH(dq->dq_priority,
                      "Don't try to wake up or override a root queue");
          }
          if (flags & DISPATCH_WAKEUP_CONSUME_2) {
              return _dispatch_release_2_tailcall(dq);
          }
      }
      
      • 全局队列的调用的,并没进行栅栏的判断,因此并不会出现阻塞
    • _dispatch_lane_wakeup

      DISPATCH_NOINLINE
      void
      _dispatch_lane_wakeup(dispatch_lane_class_t dqu, dispatch_qos_t qos,
              dispatch_wakeup_flags_t flags)
      {
          dispatch_queue_wakeup_target_t target = DISPATCH_QUEUE_WAKEUP_NONE;
      
          if (unlikely(flags & DISPATCH_WAKEUP_BARRIER_COMPLETE)) {
              // 如果执行完栅栏中的任务就调用 _dispatch_lane_barrier_complete 方法进行完成操作
              return _dispatch_lane_barrier_complete(dqu, qos, flags);
          }
          if (_dispatch_queue_class_probe(dqu)) {
              target = DISPATCH_QUEUE_WAKEUP_TARGET;
          }
          // 唤醒后边队列中的任务
          return _dispatch_queue_wakeup(dqu, qos, flags, target);
      }
      
      • 对栅栏中任务的状态进行了判断,从而决定是否唤醒队列中后续任务 image.png
      • 操作了 waiter,也对队列宽度进行了判断,dq.width == 1时即串行队列 会等待其他任务完成按顺序执行dq.width > 1时即并发队列 则将栅栏之前的任务执行完成

小结

  • 栅栏函数针对队列通过do while阻塞调用队列中后续任务
  • 栅栏函数为什么不阻塞全局并发队列?因为系统也大量使用全局并发队列,如果设计成能阻塞全局并发队列会对系统造成影响
  • 整个流程跳转很多,主要了解一下注释代码的思路即可,调用方法步骤大致如下: image.png

示例

dispatch_queue_t t = dispatch_queue_create("LZ", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t t2 = dispatch_queue_create("LZ2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(t, ^{
    NSLog(@"1");
});
dispatch_barrier_async(t, ^{
    sleep(2);
    NSLog(@"2");
});
dispatch_async(t, ^{
    NSLog(@"3");
});
//不同队列
dispatch_async(t2, ^{
    NSLog(@"4");
});

//打印结果
14 (睡眠2秒) 23
  • 栅栏只能栅住同队列的任务
// 稍作修改
1.    dispatch_queue_t t = dispatch_queue_create("LZ", DISPATCH_QUEUE_CONCURRENT);
2.    dispatch_queue_t t2 = dispatch_queue_create("LZ2", DISPATCH_QUEUE_CONCURRENT);
3.    dispatch_async(t, ^{
4.        NSLog(@"1");
5.    });
6.    dispatch_barrier_async(t, ^{
7.        NSLog(@"2");
8.    });
      //改为sync
9.    dispatch_sync(t, ^{
          // 睡眠改为此处
10.       sleep(2);
11.       NSLog(@"3");
12.   });
13.   dispatch_async(t2, ^{
14.       NSLog(@"4");
15.   });

//打印结果
1 (睡眠2秒) 234
  • 即使是不同队列的异步函数,也会被上边的sync卡住读不到内容而导致添加不到队列中
  • 9-12行的sync不执行完,不会读到13行的async,因此虽然13-15行有穿越栅栏的能力,但也只能最后执行

3、调度组

  • 栅栏函数针对队列 不同,调度组针对 group,类似于引用计数的原理

  • dispatch_group_enter():进组

  • dispatch_group_leave():出组

  • dispatch_group_enter()dispatch_group_leave() 需要成对出现

  • dispatch_group_async():对 dispatch_group_enter()dispatch_group_leave() 进行了封装,更常用的方法

    #ifdef __BLOCKS__
    void
    dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
            dispatch_block_t db)
    {
        dispatch_continuation_t dc = _dispatch_continuation_alloc();
        uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
        dispatch_qos_t qos;
    
        qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
        _dispatch_continuation_group_async(dg, dq, dc, qos);
    }
    #endif
    

    文档也有流程说明 image.png

  • dispatch_group_notify():进出组平衡,计数为0后通知执行

3.1、dispatch_group_enter

void
dispatch_group_enter(dispatch_group_t dg)
{
    // The value is decremented on a 32bits wide atomic so that the carry
    // for the 0 -> -1 transition is not propagated to the upper 32bits.
    
    // os_atomic_sub_orig2o: sub代表将 dg_bits 减1
    uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,
            DISPATCH_GROUP_VALUE_INTERVAL, acquire);
    uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
    if (unlikely(old_value == 0)) {
        _dispatch_retain(dg); // <rdar://problem/22318411>
    }
    if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {
            DISPATCH_CLIENT_CRASH(old_bits,
                    "Too many nested calls to dispatch_group_enter()");
    }
}
  • dg_bits 本质也是 dg_state
  • 进组一个任务,将 dg_bits 减 1

3.2、dispatch_group_leave

void
dispatch_group_leave(dispatch_group_t dg)
{
    // The value is incremented on a 64bits wide atomic so that the carry for
    // the -1 -> 0 transition increments the generation atomically.
    
    // os_atomic_add_orig2o: add代表将 dg_state 加1
    uint64_t new_state, old_state = os_atomic_add_orig2o(dg, dg_state,
            DISPATCH_GROUP_VALUE_INTERVAL, release);
    uint32_t old_value = (uint32_t)(old_state & DISPATCH_GROUP_VALUE_MASK);

    if (unlikely(old_value == DISPATCH_GROUP_VALUE_1)) {
        old_state += DISPATCH_GROUP_VALUE_INTERVAL;
    do {
        new_state = old_state;
        if ((old_state & DISPATCH_GROUP_VALUE_MASK) == 0) {
            new_state &= ~DISPATCH_GROUP_HAS_WAITERS;
            new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
        } else {
            // If the group was entered again since the atomic_add above,
            // we can't clear the waiters bit anymore as we don't know for
            // which generation the waiters are for
new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
        }
        if (old_state == new_state) break;
    } while (unlikely(!os_atomic_cmpxchgv2o(dg, dg_state,
            old_state, new_state, &old_state, relaxed)));
        return _dispatch_group_wake(dg, old_state, true);
    }

    if (unlikely(old_value == 0)) {
        DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
                "Unbalanced call to dispatch_group_leave()");
    }
}
  • 出组一个任务,将 dg_state 加 1

3.3、_dispatch_group_notify

image.png

示例

dispatch_group_t g = dispatch_group_create();
dispatch_queue_t t = dispatch_queue_create("LZ", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t t2 = dispatch_queue_create("LZ2", DISPATCH_QUEUE_CONCURRENT);

// 进组
dispatch_group_enter(g);
dispatch_async(t, ^{
    sleep(1);
    NSLog(@"1");
    // 出组
    dispatch_group_leave(g);
});

// 对进出组封装后的简写,更常用
dispatch_group_async(g, t2, ^{
    sleep(2);
    NSLog(@"2");
});

dispatch_group_async(g, dispatch_get_main_queue(), ^{
    sleep(3);
    NSLog(@"3");
});

dispatch_group_async(g, dispatch_get_global_queue(0, 0), ^{
    sleep(4);
    NSLog(@"4");
});
// 出入组平衡后通知执行
dispatch_group_notify(g, dispatch_get_global_queue(0, 0), ^{
    NSLog(@"5");
});
  • 例中任务都来自不同的队列,依然能按预期阻塞排序成功,说明任务来自哪个队列的调度组并不关心

使用场景

在开发过程中,经常遇到一个页面里面有多个请求,而这多个请求之间还有关联关系。以一个视频播放页面为例,上一级页面传入参数为节目ID,需要从A接口获取视频播放的地址,从B接口获取节目的信息,从C接口获取大数据推荐节目表。需要等这3个接口的数据都回来才能展示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);
}

小结

  • 栅栏函数关注队列 不同 调度组关注group,设置了一个标识dp_state,进组(enter)-1,出组(leave)+1,当任务都执行完毕最后出组后值为0时,此时会立即执行dispatch_group_notify中的任务

  • dispatch_group_enter 与 dispatch_group_leave 必须成对使用dispatch_group_leave 次数多于 dispatch_group_enter 会导致崩溃;

4、信号量

  • dispatch_semaphore_create:创建信号量,通过指定信号量值的大小可以用来控制并发数

  • dispatch_semaphore_dispose:释放信号量,在出作用域时调用

  • dispatch_semaphore_wait:等待信号量,当信号量的值为0时,阻塞线程一直等待,当信号量的值大于等于1时,对信号量-1

  • dispatch_semaphore_signal:发送信号量,将信号量的值+1

4.1、dispatch_semaphore_create

创建信号量,指定信号量初始值

dispatch_semaphore_t
dispatch_semaphore_create(long 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.
    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->dsema_orig = value;
    return dsema;
}
  • dsema_value:当前信号量值,后续 wait、signal 会改变这个值
  • dsema_orig:设定的信号量初始值

4.2、_dispatch_semaphore_dispose

释放信号量,出作用域时调用,会判断当前信号量是否小于初始信号量,如果小于会引发崩溃,因此 dispatch_semaphore_wait 数量要少于 dispatch_semaphore_signal 的数量,最好成对出现

void
_dispatch_semaphore_dispose(dispatch_object_t dou,
        DISPATCH_UNUSED bool *allow_free)
{
    dispatch_semaphore_t dsema = dou._dsema;
    // 若当前信号量 < 初始信号量会抛出报错 
    if (dsema->dsema_value < dsema->dsema_orig) {
        DISPATCH_CLIENT_CRASH(dsema->dsema_orig - dsema->dsema_value,
            "Semaphore object deallocated while in use");
    }

    _dispatch_sema4_dispose(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
}

4.3、dispatch_semaphore_wait

信号量阻塞线程

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
    // os_atomic_dec2o 进行-1操作
    long value = os_atomic_dec2o(dsema, dsema_value, acquire);
    if (likely(value >= 0)) {
        // 信号量值>=0不阻塞线程
        return 0;
    }
    // 阻塞线程方法
    return _dispatch_semaphore_wait_slow(dsema, timeout);
}
  • 对信号量的值进行-1操作,得到的 值>=0 的话直接返回,不用进行等待
  • _dispatch_semaphore_wait_slow image.png
_dispatch_sema4_wait
void
_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
    kern_return_t kr;
    // 通过do while盲等阻塞线程
    do {
        kr = semaphore_wait(*sema);
    // 一直等kr,即信号量的值>=0后
    } while (kr == KERN_ABORTED);
    DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
  • 使用 do while 盲等到信号量的值>=0

4.4、dispatch_semaphore_signal

对信号量当前值+1,若其大于等于0时,dispatch_semaphore_wait 中的 do while 盲等会跳出

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
    // os_atomic_inc2o 进行+1操作
    long value = os_atomic_inc2o(dsema, dsema_value, release);
    if (likely(value > 0)) {
        return 0;
    }
    // LONG_MIN 为-1,即是说如果进行了+1操作后值等于-1就抛出错误
    if (unlikely(value == LONG_MIN)) {
        DISPATCH_CLIENT_CRASH(value,
                "Unbalanced call to dispatch_semaphore_signal()");
    }
    // 创建个 sema4 的同时返回值为1
    return _dispatch_semaphore_signal_slow(dsema);
}
DISPATCH_NOINLINE
long
_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;
}

示例

dispatch_semaphore_t sem = dispatch_semaphore_create(0);

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"1");
    // 因为创建初始信号量为0,若注释此处会无法打印任何内容
    dispatch_semaphore_signal(sem);
});

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(2);
    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);
});

//打印结果
1 (睡眠2秒) 23

小结

  • 信号量初始值可以控制线程池中的最多并发数量
  • 保持线程同步,可以将异步任务转换为同步任务
  • 保证线程安全,为线程加锁,相当于自旋锁

5、dispatch_source

可以监听事件 1487527-f704603d4fa77969.png

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

定时器示例

- (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);
}

变量增加示例

#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); // 每次去获取iNum的值
        self.totalComplete += value;
        NSLog(@"进度: %.2f",self.totalComplete/100.0);
        self.iProgress.progress = self.totalComplete/100.0;
    });
    
//    [self iTimer];
}

- (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); // 传递iNum触发hander
            });
        }
    } else {
        dispatch_suspend(self.source);
        NSLog(@"暂停了");
        self.iNum = 0;
        [sender setTitle:@"开始" forState:UIControlStateNormal];
    }
}
@end