關(guān)于GCD的知識

GCD是什么萌踱?

Grand Central Dispatch或者GCD,是一套低層API槽地,提供了一種新的方法來進(jìn)行并發(fā)程序編寫迁沫。從基本功能上講,GCD有點像NSOperationQueue捌蚊,他們都允許程序?qū)⑷蝿?wù)切分為多個單一任務(wù)然后提交至工作隊列來并發(fā)地或者串行地執(zhí)行集畅。GCD比之NSOpertionQueue更底層更高效,并且它不是Cocoa框架的一部分缅糟。

GCD的原理挺智?

GCD是純C語言的,但它被組建成面向?qū)ο蟮娘L(fēng)格窗宦。GCD對象被稱為dispatch object赦颇。Dispatch object像Cocoa對象一樣是引用計數(shù)的。使用dispatch_release和dispatch_retain函數(shù)來操作dispatch object的引用計數(shù)來進(jìn)行內(nèi)存管理赴涵。

GCD提供很多超越傳統(tǒng)多線程編程的優(yōu)勢:
易用: GCD比之thread跟簡單易用媒怯。由于GCD基于work unit而非像thread那樣基于運算,所以GCD可以控制諸如等待任務(wù)結(jié)束髓窜、監(jiān)視文件描述符扇苞、周期執(zhí)行代碼以及工作掛起等任務(wù)〖淖荩基于block的血統(tǒng)導(dǎo)致它能極為簡單得在不同代碼作用域之間傳遞上下文鳖敷。
效率: GCD被實現(xiàn)得如此輕量和優(yōu)雅,使得它在很多地方比之專門創(chuàng)建消耗資源的線程更實用且快速程拭。這關(guān)系到易用性:導(dǎo)致GCD易用的原因有一部分在于你可以不用擔(dān)心太多的效率問題而僅僅使用它就行了编整。
性能: GCD自動根據(jù)系統(tǒng)負(fù)載來增減線程數(shù)量养筒,這就減少了上下文切換以及增加了計算效率。

GCD的使用

1.GCD的隊列

GCD有三種隊列類型:

1.主隊列:與主線程功能相同叙赚。實際上,提交至 主隊列 的任務(wù)會在主線程中執(zhí)行。 主隊列 可以調(diào)用dispatch_get_main_queue()來獲得。因為 主隊列 是與主線程相關(guān)的,所以這是一個串行隊列鳍徽。

2.全局隊列:全局隊列是 并發(fā)隊列 ,并由整個進(jìn)程共享敢课。進(jìn)程中存在三個全局隊列:高阶祭、中(默認(rèn))、低三個優(yōu)先級隊列直秆”裟迹可以調(diào)用dispatch_get_global_queue函數(shù)傳入優(yōu)先級來訪問隊列。

3.自建隊列: 是用函數(shù) dispatch_queue_create創(chuàng)建的隊列圾结。 可控制創(chuàng)建的隊列是串行還是并行瑰剃。

//DISPATCH_QUEUE_SERIAL 串行
//DISPATCH_QUEUE_CONCURRENT 并行
dispatch_queue_t queue = dispatch_queue_create("LMQueue", DISPATCH_QUEUE_CONCURRENT);

//獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
//獲取全局隊列
/**
第一個參數(shù)
設(shè)置全局隊列的優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
QOS_CLASS_USER_INTERACTIVE: 最高優(yōu)先級,交互級別筝野。使用這個優(yōu)先級會占用幾乎所有的系統(tǒng)CUP和I/O帶寬晌姚,僅限用于交互的UI操作,比如處理點擊事件歇竟,繪制圖像到屏幕上挥唠,動畫等
QOS_CLASS_USER_INITIATED: 次高優(yōu)先級,用于執(zhí)行類似初始化等需要立即返回的事件
QOS_CLASS_DEFAULT: 默認(rèn)優(yōu)先級焕议,當(dāng)沒有設(shè)置優(yōu)先級的時候宝磨,線程默認(rèn)優(yōu)先級。一般情況下用的都是這個優(yōu)先級
QOS_CLASS_UTILITY: 普通優(yōu)先級盅安,主要用于不需要立即返回的任務(wù)
QOS_CLASS_BACKGROUND: 后臺優(yōu)先級唤锉,用于用戶幾乎不感知的任務(wù)。
QOS_CLASS_UNSPECIFIED: 未知優(yōu)先級宽堆,表示服務(wù)質(zhì)量信息缺失
第二個參數(shù)
flags: 預(yù)留字段腌紧,傳入任何非0的值都可能導(dǎo)致返回NULL
*/
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

2.同步與異步(Synchronous,Asynchronous)

同步茸习,意味著在當(dāng)前線程中執(zhí)行任務(wù)畜隶,不具備開啟新的線程的能力,同步的都是串行執(zhí)行号胚。

異步籽慢,在新的線程中執(zhí)行任務(wù),具備開啟新的線程的能力猫胁。

3.并行和串行(Concurrent,Serial)

任務(wù)串行箱亿,意味著在同一時間,有且只有一個任務(wù)被執(zhí)行弃秆,即一個任務(wù)執(zhí)行完畢之后再執(zhí)行下一個任務(wù)届惋。

任務(wù)并發(fā)髓帽,意味著在同一時間,有多個任務(wù)被執(zhí)行脑豹。

dispatch_queue_t queue = dispatch_queue_create("LMQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
   NSLog(@"1->%@",[NSThread currentThread]);
   //同步在當(dāng)前線程執(zhí)行郑藏,如果當(dāng)前線程是主線程,會在主線程運行瘩欺”馗牵或者執(zhí)行線程在主線程的話也會返回主線程。
   dispatch_sync(queue, ^{
       NSLog(@"4->%@",[NSThread currentThread]);
   });
   dispatch_sync(queue, ^{
       NSLog(@"5->%@",[NSThread currentThread]);
   });
   dispatch_sync(mainQueue, ^{
       NSLog(@"6->%@",[NSThread currentThread]);
   });

});
    
dispatch_async(queue, ^{
   NSLog(@"2->%@",[NSThread currentThread]);
});
    
dispatch_async(queue, ^{
   NSLog(@"3->%@",[NSThread currentThread]);
});

1-><NSThread: 0x600000266900>{number = 3, name = (null)} 3-><NSThread: 0x608000263680>{number = 5, name = (null)} 2-><NSThread: 0x618000265cc0>{number = 4, name = (null)} 4-><NSThread: 0x600000266900>{number = 3, name = (null)} 5-><NSThread: 0x600000266900>{number = 3, name = (null)} 6-><NSThread: 0x60000007bc40>{number = 1, name = main}

可以看出如果是同步操作的話俱饿,是在當(dāng)前線程操作不會創(chuàng)建新線程歌粥。異步并行的話,在不同線程中操作拍埠,而且是沒有循序的失驶。

線程執(zhí)行順序.png

4.死鎖

線程互相等待,即死鎖械拍。

所謂的死鎖是指它們都卡住了突勇,并等待對方完成或執(zhí)行其它操作。第一個不能完成是因為它在等待第二個的完成坷虑。但第二個也不能完成甲馋,因為它在等待第一個的完成。

這個時候就是發(fā)生了死鎖迄损,我們禁止在主隊列(iOS開發(fā)中定躏,主隊列是串行隊列)中,在同步使用主隊列執(zhí)行任務(wù)芹敌,同理痊远,禁止在同一個同步串行隊列中,再使用該串行隊列同步的執(zhí)行任務(wù)氏捞,因為這樣會造成死鎖碧聪。

//1.創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("LMQueue", DISPATCH_QUEUE_SERIAL);

//2.同步執(zhí)行
dispatch_async(queue, ^{
   NSLog(@"1");
   //再用該隊列執(zhí)行同步任務(wù)會導(dǎo)致死鎖。
   //導(dǎo)致死鎖的原因是液茎,隊列是串行要等到前一個任務(wù)完成后才能執(zhí)行下一個逞姿,當(dāng)前串行隊列中再創(chuàng)建一個串行任務(wù),導(dǎo)致隊列一直無法完成任務(wù)出現(xiàn)死鎖現(xiàn)象捆等。
   dispatch_sync(queue, ^{
       NSLog(@"2");
   });
});

死鎖1.png

4.dispatch_set_target_queue

-(void) testGCD2{
    dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    

    dispatch_set_target_queue(serialQueue, targetQueue);
    dispatch_set_target_queue(concurrentQueue, targetQueue);
    
    dispatch_async(serialQueue, ^{
        NSLog(@"1>>>%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:3.f];
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2>>>%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.f];
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"3>>>%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.f];
    });
}

1>>><NSThread: 0x618000075900>{number = 3, name = (null)} 2>>><NSThread: 0x618000075900>{number = 3, name = (null)} 3>>><NSThread: 0x618000075900>{number = 3, name = (null)}

調(diào)用dispatch_set_target_queueretain新目標(biāo)隊列queue滞造,release原有目標(biāo)隊列。設(shè)置目標(biāo)隊列之后栋烤,block將會在目標(biāo)隊列中執(zhí)行谒养。注意:當(dāng)目標(biāo)隊列串行時,任何在目標(biāo)隊列中執(zhí)行的block都會串行執(zhí)行明郭,無論原隊列是否串行

set_target.png

5.dispatch_after

dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
第一個參數(shù)為DISPATCH_TIME_NOW表示當(dāng)前买窟。第二個參數(shù)的delta表示納秒丰泊,一秒對應(yīng)的納秒為1000000000,系統(tǒng)提供了一些宏來簡化

 #define NSEC_PER_SEC 1000000000ull //每秒有多少納秒
 #define USEC_PER_SEC 1000000ull    //每秒有多少毫秒
 #define NSEC_PER_USEC 1000ull      //每毫秒有多少納秒
這樣如果要表示一秒就可以這樣寫

dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);

需要注意的是這里的延時是不精確的始绍,因為加入隊列不一定會立即執(zhí)行趁耗。延時1s可能會1.5s甚至2s之后才會執(zhí)行。

6.dispatch_barrier

dispatch_barrier其阻礙的作用疆虚,在線程任務(wù)沒有完成的時候不允許繼續(xù)執(zhí)行苛败,確保提交的閉包是指定隊列中在特定時段唯一在執(zhí)行的一個,可解決多線程并發(fā)讀寫同一個資源發(fā)生死鎖。

-(void) testBarrier{
    dispatch_queue_t queue = dispatch_queue_create("LMBarrier", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)1>>%@",[NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"barrier>>%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2>>%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)3>>%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)4>>%@",[NSThread currentThread]);
    });
}

任務(wù)1>><NSThread: 0x60800007d280>{number = 3, name = (null)} barrier>><NSThread: 0x60800007d280>{number = 3, name = (null)} 任務(wù)2>><NSThread: 0x60800007d280>{number = 3, name = (null)} 任務(wù)4>><NSThread: 0x618000262b80>{number = 5, name = (null)} 任務(wù)3>><NSThread: 0x61000007c600>{number = 4, name = (null)}

任務(wù)2径簿,3罢屈,4要在barrier完成后才能執(zhí)行

-(void) testBarrier2{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 5; i++) {
            [self setObject:@(i) forKey:[NSString stringWithFormat:@"%d",i]];
        }
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i< 5; i++) {
            [self objectForKey:[NSString stringWithFormat:@"%d",i]];
        }
    });

    
}

- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
{
    dispatch_sync(self.queue, ^{
        
        [self.dataDic setObject:anObject forKey:aKey];
        NSLog(@"setObject->%@>>>%@",anObject,[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1];
    });
}

- (id)objectForKey:(id)aKey
{
    __block id object = nil;
    dispatch_sync(self.queue, ^{
        object = [self.dataDic objectForKey:aKey];
        NSLog(@"getObject->%@>>>%@",object,[NSThread currentThread]);
    });
    return  object;
}

getObject->0>>><NSThread: 0x60000026afc0>{number = 4, name = (null)} setObject->0>>><NSThread: 0x6080002609c0>{number = 3, name = (null)} getObject->(null)>>><NSThread: 0x60000026afc0>{number = 4, name = (null)} getObject->(null)>>><NSThread: 0x60000026afc0>{number = 4, name = (null)} getObject->(null)>>><NSThread: 0x60000026afc0>{number = 4, name = (null)} getObject->(null)>>><NSThread: 0x60000026afc0>{number = 4, name = (null)} setObject->1>>><NSThread: 0x6080002609c0>{number = 3, name = (null)} setObject->2>>><NSThread: 0x6080002609c0>{number = 3, name = (null)} setObject->3>>><NSThread: 0x6080002609c0>{number = 3, name = (null)} setObject->4>>><NSThread: 0x6080002609c0>{number = 3, name = (null)}

NSMutableDictionary在多個線程中如果同時寫入,或者一個線程寫入一個線程讀取篇亭,會發(fā)生無法預(yù)料的錯誤缠捌。

- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
{
    dispatch_barrier_async(self.queue, ^{
        
        [self.dataDic setObject:anObject forKey:aKey];
        NSLog(@"setObject->%@>>>%@",anObject,[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1];
    });
}

我們使用dispatch_barrier_async,讓其單獨執(zhí)行寫入操作译蒂,不允許其他寫入操作或者讀取操作同時執(zhí)行曼月。當(dāng)讀取的時候,我們只需要直接使用dispatch_sync柔昼,讓其正常讀取即可哑芹。這樣就可以保證寫入時不被打擾,讀取時可以多個線程同時進(jìn)行捕透。

setObject->0>>><NSThread: 0x608000077ac0>{number = 3, name = (null)} setObject->1>>><NSThread: 0x608000077ac0>{number = 3, name = (null)} setObject->2>>><NSThread: 0x608000077ac0>{number = 3, name = (null)} setObject->3>>><NSThread: 0x608000077ac0>{number = 3, name = (null)} setObject->4>>><NSThread: 0x608000077ac0>{number = 3, name = (null)} getObject->0>>><NSThread: 0x600000071ec0>{number = 4, name = (null)} getObject->1>>><NSThread: 0x600000071ec0>{number = 4, name = (null)} getObject->2>>><NSThread: 0x600000071ec0>{number = 4, name = (null)} getObject->3>>><NSThread: 0x600000071ec0>{number = 4, name = (null)} getObject->4>>><NSThread: 0x600000071ec0>{number = 4, name = (null)}

barrier.png

7.dispatch_apply

類似for循環(huán)聪姿,但是在并發(fā)隊列的情況下dispatch_apply會并發(fā)執(zhí)行block任務(wù),會等迭代其中的任務(wù)全部完成以后,才會返回。
dispatch_apply可進(jìn)行快速迭代乙嘀,因為可以并行執(zhí)行末购,所以使用dispatch_apply可以運行的更快。
如果在主線程調(diào)用dispatch_apply會阻塞主線程虎谢,可以dispatch_apply放在子線程中執(zhí)行盟榴。

-(void) testApply{
    
    __block int a = 0;
    dispatch_apply(9, dispatch_get_global_queue(0, 0), ^(size_t index) {
        a += index;
    });
    NSLog(@"%d",a);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_apply(9, dispatch_get_global_queue(0, 0), ^(size_t index) {
            a += index;
        });
        NSLog(@"%d",a);
    });
    
}

8.set_specific & get_specific

有時候我們需要將某些東西關(guān)聯(lián)到隊列上,比如我們想在某個隊列上存一個東西婴噩,或者我們想?yún)^(qū)分2個隊列擎场。GCD提供了dispatch_queue_set_specific方法,通過key讳推,將context關(guān)聯(lián)到queue上

const void * tag1="tag1";
const void * tag2="tag2";
- (void)viewDidLoad {
    [super viewDidLoad];

    _queue1 = dispatch_queue_create(tag1, DISPATCH_QUEUE_CONCURRENT);
    _queue2 = dispatch_queue_create(tag2, DISPATCH_QUEUE_CONCURRENT);
    
    NSString *string1 = @"string1";
    NSString *string2 = @"string2";
    //context 將程序中定義的上下文傳遞給參數(shù)顶籽。
    dispatch_queue_set_specific(_queue1, tag1, (__bridge void * _Nullable)(string1), NULL);
    dispatch_queue_set_specific(_queue2, tag2, (__bridge void * _Nullable)(string2), NULL);
    
    for (int i = 0; i < 5; i++) {
        dispatch_async(_queue1, ^{
            [self getSpecific];
        });
        dispatch_async(_queue2, ^{
            [self getSpecific];
        });
    }
    
}


-(void)getSpecific{
    if (dispatch_get_specific(tag1)) {
        NSLog(@"tag1->%@",[NSThread currentThread]);
        //根據(jù)queue和key取出context玩般,queue參數(shù)不能傳入全局隊列
       NSString *context = (__bridge NSString*)(dispatch_queue_get_specific(_queue1, tag1));
        NSLog(@"tag1Value->%@",context);
    }else if (dispatch_get_specific(tag2)){
        NSLog(@"tag2->%@",[NSThread currentThread]);
        NSString * context = (__bridge NSString*)(dispatch_queue_get_specific(_queue2, tag2));
        NSLog(@"tag2Value->%@",context);
    }
    
}

9.dispatch_set_context和dispatch_set_finalizer_f

context他是將上下文傳遞給參數(shù)银觅,而這個context所需要的內(nèi)容是一個基于C語言的指針,可以通過橋接(__bridge)來把OC的對象轉(zhuǎn)換坏为。

-(void) testContext{
    LMContextTest *context = [[LMContextTest alloc] init];
    context.name = @"lemon";
    
    dispatch_set_context(_queue1, (__bridge void *)context);
    
    dispatch_async(_queue1, ^{
        LMContextTest *context = (__bridge LMContextTest*)dispatch_get_context(_queue1);
        NSLog(@"%@",context.name);
    });
    
}

這樣寫會導(dǎo)致context出了作用域就會銷毀究驴,可能導(dǎo)致線程queue1中的context成為野指針而崩潰镊绪。

所以要用到(__bridge_retained)把context管理權(quán)從ARC中移除,但這樣子context就不能自定釋放洒忧,而我們不知道釋放的時機蝴韭。這時候我們就引入dispatch_set_finalizer_f方法,在線程銷毀的時候把context也銷毀掉熙侍,dispatch_set_finalizer_f函數(shù)為dispatch queue設(shè)置清理函數(shù)榄鉴,當(dāng)dispatch queue的引用計數(shù)達(dá)到0時,其所指定的清理函數(shù)就會被調(diào)用蛉抓。

-(void) testContext{
      
    LMContextTest *context = [[LMContextTest alloc] init];
    context.name = @"lemon";
    
    //用__bridge_retained增加context的引用計算庆尘,因為context是局部變量出了作用域就會銷毀導(dǎo)致線程中的context成為野指針
    dispatch_set_context(_queue1, (__bridge_retained void *)context);
    
    
    dispatch_set_finalizer_f(_queue1, &cleanContext);
    
    dispatch_async(_queue1, ^{
        LMContextTest *data = (__bridge LMContextTest*)dispatch_get_context(_queue1);
        NSLog(@"%@",data.name);
    });
    
}

void cleanContext(void *context){
    //__bridge_transfer 類型轉(zhuǎn)換Core Foundation->OC對象,同時將管理權(quán)移回ARC
    LMContextTest *ct = (__bridge_transfer LMContextTest*)context;
    ct = nil;
    NSLog(@"clean the data");
}

10.dispatch_once

dispatch_once的作用:對于某個任務(wù)執(zhí)行一次,且只執(zhí)行一次巷送。 dispatch_once函數(shù)有兩個參數(shù)驶忌,第一個參數(shù)predicate用來保證執(zhí)行一次,第二個參數(shù)是要執(zhí)行一次的任務(wù)block笑跛。

+(id) shareMananger {
    static dispatch_once_t onceToken;
    static LMManager *manager = nil;
    dispatch_once(&onceToken, ^{
        manager = [[LMManager alloc] init];
    });
    return manager;
}

使用dispatch_once可以簡化代碼并且徹底保證線程安全付魔,開發(fā)者無需擔(dān)心加鎖或同步。此外飞蹂,dispatch_once更高效几苍,它沒有使用重量級的同步機制,若是那樣做的話陈哑,每次運行代碼前都要獲取鎖擦剑。相反,此函數(shù)采用“原子訪問”來查詢標(biāo)記芥颈,以判斷其所對應(yīng)的代碼原來是否已經(jīng)執(zhí)行過

11.dispatch_groups

dispatch_groups是專門用來監(jiān)視多個異步任務(wù)惠勒。dispatch_group_t實例用來追蹤不同隊列中的不同任務(wù)。

當(dāng)group里所有事件都完成,GCD API有兩種方式發(fā)送通知爬坑,第一種是dispatch_group_wait纠屋,會阻塞當(dāng)前進(jìn)程,等所有任務(wù)都完成或等待超時盾计。第二種方法是使用dispatch_group_notify售担,異步執(zhí)行閉包,不會阻塞署辉。

-(void) testGroup{
 
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t queue = dispatch_queue_create("LMQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"%d->%@",i,[NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"222222");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"333333");
    });
    
    //分組中的任務(wù)完成后會調(diào)用dispatch_group_notify方法
    dispatch_group_notify(group, queue, ^{
        NSLog(@"任務(wù)全部完成");
    });
    
}

關(guān)于dispatch_group_enter和dispatch_group_leave間的關(guān)系族铆。
dispatch_group_enter是通知dispatch group任務(wù)開始了。dispatch_group_leave保持和dispatch_group_enter配對,通知任務(wù)已經(jīng)完成哭尝。dispatch_group_enter和dispatch_group_leave是成對調(diào)用,如果缺少了dispatch_group_leave哥攘,group不會結(jié)束。

-(void) testGroup3{
    dispatch_group_t group = dispatch_group_create();
    for(int i = 0; i < 5; i++)
    {
        
        dispatch_group_enter(group);
        [self block:^{
            dispatch_group_leave(group);
        }];
        
    }
    // dispatch_group_wait等待所有任務(wù)都完成直到超時。如果任務(wù)完成前就超時了逝淹,函數(shù)會返回一個非零值耕姊,可以通過返回值判斷是否超時。也可以用DISPATCH_TIME_FOREVER表示一直等栅葡。
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"done");
}


-(void) block:(void(^)())block{
    if (block) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"dddd");
            block();
        });
    }
}

12.dispatch_semaphore

另外一種保證同步的方法茉兰。使用dispatch_semaphore_signal加1dispatch_semaphore_wait減1,為0時等待的設(shè)置方式來達(dá)到線程同步的目的和同步鎖一樣能夠解決資源搶占的問題欣簇。

信號量是一個整形值并且具有一個初始計數(shù)值规脸,并且支持兩個操作:信號通知和等待。當(dāng)一個信號量被信號通知熊咽,其計數(shù)會被增加燃辖。當(dāng)一個線程在一個信號量上等待時,線程會被阻塞(如果有必要的話)网棍,直至計數(shù)器大于零黔龟,然后線程會減少這個計數(shù)。

當(dāng)我們在處理一系列線程的時候滥玷,當(dāng)數(shù)量達(dá)到一定量氏身,可以用dispatch_semaphore來控制。

dispatch_semaphore_create   創(chuàng)建一個semaphore
 dispatch_semaphore_signal   發(fā)送一個信號,信號量+1
 dispatch_semaphore_wait    等待信號惑畴,信號量-1

-(void) testSemaphore{
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < 100; i++)
    {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_group_async(group, queue, ^{
            NSLog(@"%i -> %@",i, [NSThread currentThread]);
            sleep(2);
            dispatch_semaphore_signal(semaphore);
        });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    NSLog(@"完成");
}

創(chuàng)建了一個初使值為10的semaphore蛋欣,每一次for循環(huán)都會創(chuàng)建一個新的線程,線程結(jié)束的時候會發(fā)送一個信號如贷,線程創(chuàng)建之前會信號等待陷虎,所以當(dāng)同時創(chuàng)建了10個線程之后,for循環(huán)就會阻塞杠袱,等待有線程結(jié)束之后會增加一個信號才繼續(xù)執(zhí)行尚猿,當(dāng)最后的一次dispatch_semaphore_signal完成后,阻塞結(jié)束楣富。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凿掂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纹蝴,更是在濱河造成了極大的恐慌庄萎,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件塘安,死亡現(xiàn)場離奇詭異糠涛,居然都是意外死亡,警方通過查閱死者的電腦和手機兼犯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門忍捡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來集漾,“玉大人,你說我怎么就攤上這事锉罐。” “怎么了绕娘?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵脓规,是天一觀的道長。 經(jīng)常有香客問我险领,道長侨舆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任绢陌,我火速辦了婚禮挨下,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脐湾。我一直安慰自己臭笆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布秤掌。 她就那樣靜靜地躺著愁铺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闻鉴。 梳的紋絲不亂的頭發(fā)上茵乱,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音孟岛,去河邊找鬼瓶竭。 笑死,一個胖子當(dāng)著我的面吹牛渠羞,可吹牛的內(nèi)容都是我干的斤贰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼次询,長吁一口氣:“原來是場噩夢啊……” “哼腋舌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起渗蟹,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤块饺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后雌芽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體授艰,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年世落,在試婚紗的時候發(fā)現(xiàn)自己被綠了淮腾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谷朝,靈堂內(nèi)的尸體忽然破棺而出洲押,到底是詐尸還是另有隱情,我是刑警寧澤圆凰,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布杈帐,位于F島的核電站,受9級特大地震影響专钉,放射性物質(zhì)發(fā)生泄漏挑童。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一跃须、第九天 我趴在偏房一處隱蔽的房頂上張望站叼。 院中可真熱鬧,春花似錦菇民、人聲如沸尽楔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翔试。三九已至,卻和暖如春复旬,著一層夾襖步出監(jiān)牢的瞬間垦缅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工驹碍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留壁涎,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓志秃,卻偏偏與公主長得像怔球,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浮还,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容