13、iOS底層探索-GCD單例、阻塞使用

語言: CN / TW / HK

highlight: hybrid

1、單例(dispatch_once)

```swift

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

```swift 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

swift 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

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

```swift 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);

} ```

swift 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

```swift 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、柵欄函式

swift 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

swift 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

```swift 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

swift 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 swift 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 swift 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 swift #define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)

    image.png - 上篇GCD中分析非同步時也找到過這裡(證明了非同步操作了執行緒相關內容),這次看的是 不同的佇列型別有不同的喚醒方式

    • _dispatch_root_queue_wakeup swift 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 ```swift 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

示例

```swift 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 - `柵欄只能柵住同隊列的任務`swift // 稍作修改 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() 進行了封裝,更常用的方法 ```swift #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

```swift 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

```swift 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

示例

```swift 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。 這時候就需要我們的排程組來接受所有任務完成的回調了 ```swift - (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

建立訊號量,指定訊號量初始值 ```swift 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 的數量,最好成對出現 ```swift 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

訊號量阻塞執行緒 swift 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

swift 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 盲等會跳出 swift 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); }

swift 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; }

示例

```swift 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 取消源事件

定時器示例

```swift - (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);

} ```

變數增加示例

```swift

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 ```