iOS 開發之 GCD 不同場景使用


 

iOS 開發之 GCD 不同場景使用

本文在iOS 開發值 GCD 基礎 的基礎上,繼續總結了 GCD 的一些API 和在不同場景下的使用。

GCD 柵欄方法:dispatch_barrier_async

我們有時需要異步執行兩組操作,而且第一組操作執行完之后,才能開始執行第二組操作。這樣我們就需要一個相當於 柵欄 一樣的一個方法將兩組異步執行的操作組給分割起來,當然這里的操作組里可以包含一個或多個任務。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄。

dispatch_barrier_async函數會等待前邊追加到並發隊列中的任務全部執行完畢之后,再將指定的任務追加到該異步隊列中。然后在dispatch_barrier_async函數追加的任務執行完畢之后,異步隊列才恢復為一般動作,接着追加任務到該異步隊列並開始執行.

示意如圖:

 

/**
 * 柵欄方法 dispatch_barrier_async
 */

- (void)barrier {

    // 1. 創建並發隊列隊列
    dispatch_queue_t queue = dispatch_queue_create("com.xiaoyouPrince", DISPATCH_QUEUE_CONCURRENT);

    // 2. 添加異步任務
    dispatch_async(queue, ^{

        // 追加任務1
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });

    dispatch_async(queue, ^{
        // 追加任務2
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });

    // 柵欄任務
    dispatch_barrier_async(queue, ^{
        // 追加任務 barrier
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當前線程
        }
    });


    dispatch_async(queue, ^{
        // 追加任務3
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });

    dispatch_async(queue, ^{
        // 追加任務4
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
}


------------------打印結果-----------------
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
barrier---{number = 4, name = (null)}
barrier---{number = 4, name = (null)}
4---{number = 3, name = (null)}
3---{number = 4, name = (null)}
4---{number = 3, name = (null)}
3---{number = 4, name = (null)}
------------------打印結果-----------------

在dispatch_barrier_async相關代碼執行結果中可以看出:

在執行完柵欄前面的操作之后,才執行柵欄操作,最后再執行柵欄后邊的操作

GCD 延時執行方法:dispatch_after

我們經常會遇到這樣的需求:在指定時間(例如3秒)之后執行某個任務。可以用 GCD 的dispatch_after函數來實現。

需要注意的是:dispatch_after函數並不是在指定時間之后才開始執行處理,而是在指定時間之后將任務追加到主隊列中。嚴格來說,這個時間並不是絕對准確的,但想要大致延遲執行任務,dispatch_after函數是很有效的。

/**
 * 延時執行方法 dispatch_after
 */

- (void)after {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"after---begin");

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        // 2.0秒后異步追加任務代碼到主隊列,並開始執行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印當前線程
    });

}

輸出結果:

2018-08-29 17:53:08.713784+0800 GCD-demo[20282:5080295] currentThread---{number = 1, name = main}

2018-08-29 17:53:08.713962+0800 GCD-demo[20282:5080295] after---begin

2018-08-29 17:53:10.714283+0800 GCD-demo[20282:5080295] after---{number = 1, name = main}

在dispatch_after相關代碼執行結果中可以看出:在打印 after---begin 之后大約 2.0 秒的時間,打印了 after---{number = 1, name = main}

GCD 一次性代碼(只執行一次):dispatch_once

我們在創建單例、或者有整個程序運行過程中只執行一次的代碼時,我們就用到了 GCD 的 dispatch_once 函數。使用dispatch_once 函數能保證某段代碼在程序運行過程中只被執行1次,並且即使在多線程的環境下,dispatch_once也可以保證線程安全。


/**
 * 一次性代碼(只執行一次)dispatch_once
 */

- (void)once {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只執行1次的代碼(這里面默認是線程安全的)
    });
}

GCD 快速迭代方法:dispatch_apply

通常我們會用 for 循環遍歷,但是 GCD 給我們提供了快速迭代的函數dispatch_apply。dispatch_apply按照指定的次數將指定的任務追加到指定的隊列中,並等待全部隊列執行結束。

我們可以利用異步隊列同時遍歷。比如說遍歷 0~5 這6個數字,for 循環的做法是每次取出一個元素,逐個遍歷。dispatch_apply可以同時遍歷多個數字。

/**
 * 快速迭代方法 dispatch_apply
 */

- (void)apply {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    NSLog(@"apply---begin");

    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    
    NSLog(@"apply---end");
}

--------------------輸出結果:-------------------

apply---begin

1---{number = 3, name = (null)}
0---{number = 1, name = main}
2---{number = 4, name = (null)}
3---{number = 5, name = (null)}
4---{number = 3, name = (null)}
5---{number = 1, name = main}

apply---end

--------------------輸出結果:-------------------

從 dispatch_apply 相關代碼執行結果中可以看出:

0~5 打印順序不定,最后打印了 apply---end。

因為是在並發隊列中異步隊執行任務,所以各個任務的執行時間長短不定,最后結束順序也不定。但是apply---end一定在最后執行。這是因為 dispatch_apply 函數會等待全部任務執行完畢。

GCD 的隊列組:dispatch_group

有時候我們會有這樣的需求:分別異步執行2個耗時任務,然后當2個耗時任務都執行完畢后再回到主線程執行任務。這時候我們可以用到 GCD 的隊列組。

調用隊列組的 dispatch_group_async 先把任務放到隊列中,然后將隊列放入隊列組中。或者使用隊列組的 dispatch_group_enter、dispatch_group_leave 組合 來實現dispatch_group_async。

調用隊列組的 dispatch_group_notify 回到指定線程執行任務。或者使用 dispatch_group_wait回到當前線程繼續向下執行(會阻塞當前線程)。

dispatch_group_notify

監聽 group 中任務的完成狀態,當所有的任務都執行完成后,追加任務到 group 中,並執行任務。

/**
 * 隊列組 dispatch_group_notify
 */

- (void)groupNotify {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"group---begin");

    // 1. 創建 group
    dispatch_group_t group =  dispatch_group_create();
    
    // 2. 異步執行組內的全局隊列任務
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // 追加任務1
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // 追加任務2
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });



    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        // 等前面的異步任務1、任務2都執行完畢后,回到主線程執行下邊任務
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
        NSLog(@"group---end");
    });
}

-------------------輸出結果:-----------------
currentThread---{number = 1, name = main}
group---begin

 1---{number = 4, name = (null)}
 2---{number = 3, name = (null)}
 2---{number = 3, name = (null)}
 1---{number = 4, name = (null)}
 3---{number = 1, name = main}
 3---{number = 1, name = main}
 
group---end

從dispatch_group_notify相關代碼運行輸出結果可以看出:
當所有任務都執行完成之后,才執行 dispatch_group_notify block 中的任務。

dispatch_group_wait

暫停當前線程(阻塞當前線程),等待指定的 group 中的任務執行完成后,才會往下繼續執行。

/**
 * 隊列組 dispatch_group_wait
 */
- (void)groupWait {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"group---begin");

    // 1. 創建group
    dispatch_group_t group =  dispatch_group_create();

    // 2. 異步執行組中全局隊列任務
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // 追加任務1
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務2
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });

    // 等待上面的任務全部完成后,會往下繼續執行(會阻塞當前線程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group---end");
}

------------------輸出結果:------------------

currentThread---{number = 1, name = main}
group---begin
2---{number = 4, name = (null)}
1---{number = 3, name = (null)}
2---{number = 4, name = (null)}
1---{number = 3, name = (null)}
group---end
------------------輸出結果:------------------

從dispatch_group_wait相關代碼運行輸出結果可以看出:

當所有任務執行完成之后,才執行 dispatch_group_wait 之后的操作。但是,使用dispatch_group_wait 會阻塞當前線程。

dispatch_group_enter、dispatch_group_leave

dispatch_group_enter 標志着一個任務追加到 group,執行一次,相當於 group 中未執行完畢任務數+1

dispatch_group_leave 標志着一個任務離開了 group,執行一次,相當於 group 中未執行完畢任務數-1。

當 group 中未執行完畢任務數為0的時候,才會使dispatch_group_wait解除阻塞,以及執行追加到dispatch_group_notify中的任務。

/**
 * 隊列組 dispatch_group_enter、dispatch_group_leave
 */
- (void)groupEnterAndLeave
{

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"group---begin");

    // 1.創建 group
    dispatch_group_t group = dispatch_group_create();

    // 2. 獲取全局並發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 3. 進入 group 中執行
    dispatch_group_enter(group);

    // 3.1. 向隊列中異步執行任務
    dispatch_async(queue, ^{

        // 追加任務1
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
        
        // 3.2. 任務執行之后,離開組【leave 和 enter 必須成對使用】
        dispatch_group_leave(group);
    });


    // 4. 再次進入組執行新的任務
    dispatch_group_enter(group);
    // 4.1. 向隊列中異步執行任務
    dispatch_async(queue, ^{

        // 追加任務2
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
        
        // 4.2. 任務執行之后,離開組【leave 和 enter 必須成對使用】
        dispatch_group_leave(group);
    });


    // group 內部的任務都執行完之后通知執行下面代碼
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        // 等前面的異步操作都執行完畢后,回到主線程.
        for(inti = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
        NSLog(@"group---end");
    });
}


-------------------輸出結果------------------
currentThread---{number = 1, name = main}
group---begin
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
3---{number = 1, name = main}
3---{number = 1, name = main}
group---end
-------------------輸出結果------------------

從dispatch_group_enter、dispatch_group_leave相關代碼運行結果中可以看出:當所有任務執行完成之后,才執行 dispatch_group_notify 中的任務。這里的dispatch_group_enter、dispatch_group_leave組合,其實等同於dispatch_group_async。

GCD 信號量:dispatch_semaphore

GCD 中的信號量是指 Dispatch Semaphore,是持有計數的信號。類似於過高速路收費站的欄桿。可以通過時,打開欄桿,不可以通過時,關閉欄桿。在 Dispatch Semaphore 中,使用計數來完成這個功能,計數為0時等待,不可通過。計數為1或大於1時,計數減1且不等待,可通過。Dispatch Semaphore 提供了三個函數。

dispatch_semaphore_create:創建一個Semaphore並初始化信號的總量

dispatch_semaphore_signal:發送一個信號,讓信號總量加1

dispatch_semaphore_wait:可以使總信號量減1,當信號總量為0時就會一直等待(阻塞所在線程),否則就可以正常執行。

注意:信號量的使用前提是:想清楚你需要處理哪個線程等待(阻塞),又要哪個線程繼續執行,然后使用信號量。

Dispatch Semaphore 在實際開發中主要用於:

保持線程同步,將異步執行任務轉換為同步執行任務

保證線程安全,為線程加鎖

Dispatch Semaphore 線程同步

我們在開發中,會遇到這樣的需求:異步執行耗時任務,並使用異步執行的結果進行一些額外的操作。換句話說,相當於,將將異步執行任務轉換為同步執行任務。比如說:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通過引入信號量的方式,等待異步執行任務結果,獲取到 tasks,然后再返回該 tasks。

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {

    __block NSArray *tasks = nil;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {

        if([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {

            tasks = dataTasks;

        } else if([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {

            tasks = uploadTasks;

        } else if([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {

            tasks = downloadTasks;

        } else if([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {

            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];

        }
        
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    return tasks;
}

下面,我們來利用 Dispatch Semaphore 實現線程同步,將異步執行任務轉換為同步執行任務。

/**
 * semaphore 線程同步
 */

- (void)semaphoreSync {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程

    NSLog(@"semaphore---begin");

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    __block int number = 0;

    dispatch_async(queue, ^{

        // 追加任務1
        [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
         
        number = 100;

        dispatch_semaphore_signal(semaphore);
    });

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    NSLog(@"semaphore---end,number = %zd",number);

}


----------------------輸出結果-----------------
currentThread---{number = 1, name = main}
semaphore---begin
1---{number = 3, name = (null)}
semaphore---end, number = 100

從 Dispatch Semaphore 實現線程同步的代碼可以看到:

semaphore---end 是在執行完number = 100; 之后才打印的。而且輸出結果 number 為 100。這是因為異步執行不會做任何等待,可以繼續執行任務。異步執行將任務1追加到隊列之后,不做等待,接着執行dispatch_semaphore_wait方法。此時 semaphore == 0,當前線程進入等待狀態。然后,異步任務1開始執行。任務1執行到dispatch_semaphore_signal之后,總信號量加1,此時 semaphore == 1,dispatch_semaphore_wait方法檢測到總信號量為1,正在被阻塞的線程(主線程)恢復繼續執行。最后打印semaphore---end,number = 100。這樣就實現了線程同步,將異步執行任務轉換為同步執行任務。

Dispatch Semaphore 線程安全和線程同步(為線程加鎖)

線程安全:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。

線程同步:可理解為線程 A 和 線程 B 一塊配合,A 執行到一定程度時要依靠線程 B 的某個結果,於是停下來,示意 B 運行;B 依言執行,再將結果給 A;A 再繼續操作。

舉個簡單例子就是:兩個人在一起聊天。兩個人不能同時說話,避免聽不清(操作沖突)。等一個人說完(一個線程結束操作),另一個再說(另一個線程再開始操作)。

下面,我們模擬火車票售賣的方式,實現 NSThread 線程安全和解決線程同步問題。

場景:總共有50張火車票,有兩個售賣火車票的窗口,一個是北京火車票售賣窗口,另一個是上海火車票售賣窗口。兩個窗口同時售賣火車票,賣完為止。

非線程安全(不使用 semaphore)

先來看看不考慮線程安全的代碼:

/**
 * 非線程安全:不使用 semaphore
 * 初始化火車票數量、賣票窗口(非線程安全)、並開始賣票
 */
- (void)initTicketStatusNotSave {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"semaphore---begin");

    self.ticketSurplusCount = 50; // 初始化車票 50 張

    // queue1 代表北京火車票售賣窗口
    dispatch_queue_t queue1 = dispatch_queue_create("com.xiaoyouPrince1", DISPATCH_QUEUE_SERIAL);

    // queue2 代表上海火車票售賣窗口
    dispatch_queue_t queue2 = dispatch_queue_create("com.xiaoyouPrince2", DISPATCH_QUEUE_SERIAL);

    // 北京窗口異步執行賣票操作
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketNotSafe];
    });

    // 上海窗口異步執行賣票操作
    dispatch_async(queue2, ^{
        [weakSelf saleTicketNotSafe];
    });
}



/**
 * 售賣火車票(非線程安全)
 */

- (void)saleTicketNotSafe {

    while(1) {
    
        if(self.ticketSurplusCount > 0) {  //如果還有票,繼續售賣
            self.ticketSurplusCount--;

            NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
            
        } else{ //如果已賣完,關閉售票窗口

            NSLog(@"票已經售完");
            break;
        }
    }
}


----------------- 輸出結果(部分打印) -------------------
currentThread <NSThread: 0x608000079280>{number = 1, name = main}
non_semaphore_begin
剩余票數 48,窗口:{number = 4, name = (null)}
剩余票數 49,窗口:{number = 3, name = (null)}
剩余票數 47,窗口:{number = 4, name = (null)}
剩余票數 46,窗口:{number = 3, name = (null)}
剩余票數 45,窗口:{number = 4, name = (null)}
剩余票數 44,窗口:{number = 3, name = (null)}
剩余票數 43,窗口:{number = 4, name = (null)}
剩余票數 42,窗口:{number = 3, name = (null)}
剩余票數 40,窗口:{number = 4, name = (null)}
剩余票數 41,窗口:{number = 3, name = (null)}
剩余票數 39,窗口:{number = 4, name = (null)}
剩余票數 0,窗口:{number = 3, name = (null)}
...\
票已經售完
票已經售完
----------------- 輸出結果(部分打印) -------------------

可以看到在不考慮線程安全,不使用 semaphore 的情況下,得到票數是錯亂的,這樣顯然不符合我們的需求,所以我們需要考慮線程安全問題。

線程安全(使用 semaphore 加鎖)

考慮線程安全的代碼:

/**

 * 線程安全:使用 semaphore 加鎖
 * 初始化火車票數量、賣票窗口(線程安全)、並開始賣票
 */

- (void)initTicketStatusSave {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"semaphore---begin");
    
    // 有成員變量 dispatch_semaphore_t semaphreLock
    semaphreLock = dispatch_semaphore_create(1);
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火車票售賣窗口
    dispatch_queue_t queue1 = dispatch_queue_create("com.xiaoyouPrince1", DISPATCH_QUEUE_SERIAL);

    // queue2 代表上海火車票售賣窗口
    dispatch_queue_t queue2 = dispatch_queue_create("com.xiaoyouPrince2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });

    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

/**
 * 售賣火車票(線程安全)
 */
- (void)saleTicketSafe {

    while(1) {

        // 相當於加鎖
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);

        if(self.ticketSurplusCount > 0) {  //如果還有票,繼續售賣

            self.ticketSurplusCount--;

            NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);

            [NSThread sleepForTimeInterval:0.2];

        } else{ //如果已賣完,關閉售票窗口

            NSLog(@"車票已經售完");
            
            // 相當於解鎖
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相當於解鎖
        dispatch_semaphore_signal(semaphoreLock);
    }
}

----------------- 輸出結果(部分打印) -------------------
currentThread <NSThread: 0x608000079280>{number = 1, name = main}
non_semaphore_begin
剩余票數 49,窗口:{number = 4, name = (null)}
剩余票數 48,窗口:{number = 3, name = (null)}
剩余票數 47,窗口:{number = 4, name = (null)}
剩余票數 46,窗口:{number = 3, name = (null)}
剩余票數 45,窗口:{number = 4, name = (null)}
....\
剩余票數 4,窗口:{number = 3, name = (null)}
剩余票數 3,窗口:{number = 4, name = (null)}
剩余票數 2,窗口:{number = 3, name = (null)}
剩余票數 1,窗口:{number = 4, name = (null)}
剩余票數 0,窗口:{number = 3, name = (null)}
票已經售完
----------------- 輸出結果(部分打印) -------------------

可以看出,在考慮了線程安全的情況下,使用dispatch_semaphore 機制之后,得到的票數是正確的,沒有出現混亂的情況。我們也就解決了多個線程同步的問題。

創建一個 semaphoreLock 對象。后續可能有多個線程同時訪問被車票余量,當有線程訪問到票余量時進行 dispatch_semaphore_wait 操作。使得 semaphoreLock 總信號量 減 1 (等於0 ,相當於加鎖) ,並繼續操作票余量,進行一次 self.ticketSurplusCount-- 操作。當票數量減少之后執行 dispatch_semaphore_signal 使得 semaphoreLock 總信號量 + 1 (等於1 ,相當於解鎖)

此過程中如果有其他並發線程要訪問 票余量。同樣會先來到 dispatch_semaphore_wait 操作。此時 semaphoreLock 的總信號量為 0 ,直接阻塞線程。當上一個訪問余量的線程操作完成之后執行 dispatch_semaphore_signal 操作解鎖之后,就會繼續本線程的訪問。從而確保了票余量的線程安全。

資料:
最新官方文檔

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM