iOS_GCD(Grand Central Dispatch)

apple官方解釋

Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in macOS, iOS, watchOS, and tvOS.

翻譯:Grand Central Dispatch(GCD)包括語言功能,運行時庫和系統(tǒng)增強功能表谊,可為macOS版扩,iOS骇吭,watchOS和tvOS中多核硬件上的并發(fā)代碼執(zhí)行提供系統(tǒng)怖糊,全面的改進。

The BSD subsystem, Core Foundation, and Cocoa APIs have all been extended to use these enhancements to help both the system and your application to run faster, more efficiently, and with improved responsiveness. Consider how difficult it is for a single application to use multiple cores effectively, let alone doing it on different computers with different numbers of computing cores or in an environment with multiple applications competing for those cores. GCD, operating at the system level, can better accommodate the needs of all running applications, matching them to the available system resources in a balanced fashion.

翻譯:BSD子系統(tǒng)悬钳,Core Foundation和Cocoa API都已擴展為使用這些增強功能塞蹭,以幫助系統(tǒng)和應(yīng)用程序更快,更高效地運行门坷,并提高響應(yīng)速度宣鄙。 考慮單個應(yīng)用程序有效使用多個內(nèi)核的難度躯畴,更不用說在具有不同數(shù)量的計算內(nèi)核的不同計算機上或在多個應(yīng)用程序競爭這些內(nèi)核的環(huán)境中執(zhí)行此操作蜡励。 在系統(tǒng)級別運行的GCD可以更好地滿足所有正在運行的應(yīng)用程序的需求闰蚕,并以平衡的方式將它們與可用的系統(tǒng)資源相匹配点弯。

GCD多線程關(guān)系

一姐直、進程:進程(Process)是計算機中的程序關(guān)于某數(shù)據(jù)集合上的一次運行活動锣吼,在iOS系統(tǒng)中铭段,開啟一個 應(yīng)用就打開了一個進程崭歧。

二惯裕、線程:線程(Thread)是進程中的一個實體温数,程序執(zhí)行的基本單元。在iOS系統(tǒng)中蜻势,一個進程包含 一個主線程撑刺,它的主要任務(wù)是處理UI事件,顯示和刷新UI握玛。

三够傍、隊列:這里(GCD)的隊列指執(zhí)行任務(wù)的等待隊列,即用來存放任務(wù)的隊列挠铲。隊列是一種特殊的線性表冕屯,采用 FIFO(先進先出)的原則,即新任務(wù)總是被插入到隊列的末尾拂苹,而讀取任務(wù)的時候總是從隊列的頭部開始讀取安聘。每讀取一個任務(wù),則從隊列中釋放一個任務(wù)。隊列的結(jié)構(gòu)可參考下圖:

image

兩個通用隊列

  • 串行隊列(Serial Dispatch Queue):
    所有任務(wù)會在一條線程中執(zhí)行(有可能是當(dāng)前線程也有可能是新開辟的線程)浴韭,并且一個任務(wù)執(zhí)行完畢后丘喻,才開始執(zhí)行下一個任務(wù)。(等待完成)

  • 并行(發(fā))隊列(Concurrent Dispatch Queue):
    可以開啟多條線程并行執(zhí)行任務(wù)(但不一定會開啟新的線程)念颈,并且當(dāng)一個任務(wù)放到指定線程開始執(zhí)行時泉粉,下一個任務(wù)就可以開始執(zhí)行了。(等待發(fā)生)舍肠。

兩個特殊隊列

  • 主隊列:
    系統(tǒng)為我們創(chuàng)建好的一個串行隊列搀继,牛逼之處在于它管理必須在主線程中執(zhí)行的任務(wù),屬于有勞保的翠语。

  • 全局隊列:
    系統(tǒng)為我們創(chuàng)建好的一個并行隊列叽躯,使用起來與我們自己創(chuàng)建的并行隊列無本質(zhì)差別。

四肌括、任務(wù):就是執(zhí)行操作的意思点骑,換句話說就是你在線程中執(zhí)行的那段代碼。在 GCD 中是放在 block 中的谍夭。

  • 同步執(zhí)行(sync):
    • 同步添加任務(wù)到指定的隊列中黑滴,在添加的任務(wù)執(zhí)行結(jié)束之前,會一直等待紧索,直到隊列里面的任務(wù)完成之后再繼續(xù)執(zhí)行袁辈。
    • 只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力珠漂。
  • 異步執(zhí)行(async):
    • 異步添加任務(wù)到指定的隊列中晚缩,它不會做任何等待,可以繼續(xù)執(zhí)行任務(wù)媳危。
    • 可以在新的線程中執(zhí)行任務(wù)荞彼,具備開啟新線程的能力。(注意??:異步執(zhí)行雖然具有開啟新線程的能力待笑,但是并不一定開啟新線程鸣皂。)

隊列和任務(wù)執(zhí)行方式的組合情況

區(qū)別 并行(發(fā))隊列 串行隊列 主隊列
同步(sync) 沒有(也不能)開啟新線程,串行執(zhí)行任務(wù) 沒有(也不能)開啟新線程暮蹂,串行執(zhí)行任務(wù) 主線程調(diào)用:死鎖卡住不執(zhí)行
其他線程調(diào)用:沒有開啟新線程寞缝,串行執(zhí)行任務(wù)
異步(sync) 有開啟新線程,并發(fā)執(zhí)行任務(wù) 有開啟新線程(1條)仰泻,串行執(zhí)行任務(wù) 沒有開啟新線程第租,串行執(zhí)行任務(wù)

多線程中會出現(xiàn)的問題

臨界資源:多個線程共享各種資源,然而有很多資源一次只能供一線程使用我纪。一次僅允許一個線程使用的資源稱為臨界資源。

臨界區(qū):訪問臨界資源的代碼區(qū)

注意

  • 如果有若干線程要求進入空閑的臨界區(qū),一次僅允許一個線程進入浅悉。

  • 任何時候趟据,處于臨界區(qū)內(nèi)的線程不可多于一個。如已有線程進入自己的臨界區(qū)术健,則其它所有試圖進入臨界區(qū)的線程必須等待汹碱。

  • 進入臨界區(qū)的線程要在有限時間內(nèi)退出,以便其它線程能及時進入自己的臨界區(qū)荞估。

  • 如果線程不能進入自己的臨界區(qū)咳促,則應(yīng)讓出CPU,避免進程出現(xiàn)“忙等”現(xiàn)象勘伺。

  • 死鎖:兩個(多個)線程都要等待對方完成某個操作才能進行下一步跪腹,這時就會發(fā)生死鎖。

  • 互斥鎖:能夠防止多線程搶奪造成的數(shù)據(jù)安全問題飞醉,但是需要消耗大量的資源

原子屬性:

atomic: 原子屬性冲茸,為setter方法加鎖,將屬性以atomic的形式來聲明缅帘,該屬性變量就能支持互斥鎖了轴术。

nonatomic: 非原子屬性,不會為setter方法加鎖钦无,聲明為該屬性的變量逗栽,客戶端應(yīng)盡量避免多線程爭奪同一資源。

上下文切換(Context Switch):

當(dāng)一個進程中有多個線程來回切換時失暂,context switch用來記錄線程執(zhí)行狀態(tài)彼宠。從一個線程切換到另一個線程時需要保存當(dāng)前進程的狀態(tài)并恢復(fù)另一個進程的狀態(tài),當(dāng)前運行任務(wù)轉(zhuǎn)為就緒(或者掛起趣席、刪除)狀態(tài)兵志,另一個被選定的就緒任務(wù)成為當(dāng)前任務(wù)。上下文切換包括保存當(dāng)前任務(wù)的運行環(huán)境宣肚,恢復(fù)將要運行任務(wù)的運行環(huán)境想罕。

iOS中三種多線程編程技術(shù)的優(yōu)缺點

NSThread

  • 優(yōu)點:NSThread 比其他兩個輕量級

  • 缺點:需要自己管理線程的生命周期,線程同步霉涨。線程同步對數(shù)據(jù)的加鎖會有一定的系統(tǒng)開銷

NSOperation

  • 優(yōu)點:不需要關(guān)心線程管理按价,數(shù)據(jù)同步的事情,可以把精力放在自己需要執(zhí)行的操作上笙瑟。

GCD

  • 優(yōu)點:

    1. GCD 可以利用更多的CPU內(nèi)核進行運算(比如:雙核楼镐,四核)

    2. GCD 會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)往枷、銷毀線程)

總的來說:GCD是 Apple 開發(fā)的一個多核編程的解決方法框产,簡單易用凄杯,效率高,速度快秉宿。

GCD中的隊列類型

  • The main queue(主線程串行隊列): 與主線程功能相同戒突,提交至Main queue的任務(wù)會在主線程中執(zhí)行

  • Global queue(全局并發(fā)隊列): 全局并發(fā)隊列由整個進程共享,有高描睦、中(默認(rèn))膊存、低、后臺四個優(yōu)先級別忱叭。

  • Custom queue (自定義隊列): 可以為串行隔崎,也可以為并發(fā)。

在Apple文檔中的描述:

A dispatch queue is a lightweight object to which your application submits blocks for subsequent execution.

A dispatch queue invokes blocks submitted to it serially in FIFO order. A serial queue invokes only one block at a time, but independent queues may each invoke their blocks concurrently with respect to each other.

The global concurrent queues invoke blocks in FIFO order but do not wait for their completion, allowing multiple blocks to be invoked concurrently.

The system manages a pool of threads that process dispatch queues and invoke blocks submitted to them. Conceptually, a dispatch queue may have its own thread of execution, and interaction between queues is highly asynchronous.

GCD線程之間是怎么通訊的

在iOS開發(fā)過程中韵丑,我們一般在主線程里邊進行UI刷新爵卒,例如:點擊、滾動埂息、拖拽等事件技潘。我們通常把一些耗時的操作放在其他線程,比如說圖片下載千康、文件上傳等耗時操作享幽。而當(dāng)我們有時候在其他線程完成了耗時操作時,需要回到主線程拾弃,那么就用到了線程之間的通訊值桩。

/**
 * 線程間通信
 */
- (void)communication {
    // 獲取全局并發(fā)隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    // 獲取主隊列
    dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
    
    dispatch_async(queue, ^{
        // 異步追加任務(wù)
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        }
        
        // 回到主線程
        dispatch_async(mainQueue, ^{
            // 追加在主線程中執(zhí)行的任務(wù)
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        });
    });
}

輸出結(jié)果:
2018-02-23 20:47:03.462394+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}
2018-02-23 20:47:05.465912+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}
2018-02-23 20:47:07.466657+0800 YSC-GCD-demo[20154:5052953] 2---<NSThread: 0x60000007bf80>{number = 1, name = main}

  • 可以看到在其他線程中先執(zhí)行任務(wù),執(zhí)行完了之后回到主線程執(zhí)行主線程的相應(yīng)操作豪椿。

相關(guān)知識點

  1. 對于單核CPU來說奔坟,不存在真正意義上的并行,所以搭盾,多線程執(zhí)行任務(wù)咳秉,其實也只是一個人在干活,CPU的調(diào)度決定了非等待任務(wù)的執(zhí)行速率鸯隅,同時對于非等待任務(wù)澜建,多線程并沒有真正意義提高效率。

  2. 線程可以簡單的認(rèn)為就是一段代碼+運行時數(shù)據(jù)蝌以。

  3. 同步執(zhí)行會在當(dāng)前線程執(zhí)行任務(wù)炕舵,不具備開辟線程的能力或者說沒有必要開辟新的線程。并且跟畅,同步執(zhí)行必須等到Block函數(shù)執(zhí)行完畢咽筋,dispatch函數(shù)才會返回,從而阻塞同一串行隊列中外部方法的執(zhí)行徊件。

  4. 異步執(zhí)行dispatch函數(shù)會直接返回奸攻,Block函數(shù)我們可以認(rèn)為它會在下一幀加入隊列蒜危,并根據(jù)所在隊列目前的任務(wù)情況無限下一幀執(zhí)行,從而不會阻塞當(dāng)前外部任務(wù)的執(zhí)行舞箍。同時舰褪,只有異步執(zhí)行才有開辟新線程的必要,但是異步執(zhí)行不一定會開辟新線程疏橄。

  5. 只要是隊列,肯定是FIFO(先進先出)略就,但是誰先執(zhí)行完要看第1條捎迫。

  6. 只要是串行隊列,肯定要等上一個任務(wù)執(zhí)行完成表牢,才能開始下一個任務(wù)窄绒。但是并行隊列當(dāng)上一個任務(wù)開始執(zhí)行后,下一個任務(wù)就可以開始執(zhí)行崔兴。

  7. 想要開辟新線程必須讓任務(wù)在異步執(zhí)行彰导,想要開辟多個線程,只有讓任務(wù)在并行隊列中異步執(zhí)行才可以敲茄。執(zhí)行方式和隊列類型多層組合在一定程度上能夠?qū)崿F(xiàn)對于代碼執(zhí)行順序的調(diào)度位谋。

  8. 同步+串行:未開辟新線程,串行執(zhí)行任務(wù)堰燎;同步+并行:未開辟新線程掏父,串行執(zhí)行任務(wù);異步+串行:新開辟一條線程秆剪,串行執(zhí)行任務(wù)赊淑;異步+并行:開辟多條新線程,并行執(zhí)行任務(wù)仅讽;在主線程中同步使用主隊列執(zhí)行任務(wù)陶缺,會造成死鎖。

  9. 對于多核CPU來說洁灵,線程數(shù)量也不能無限開辟饱岸,線程的開辟同樣會消耗資源,過多線程同時處理任務(wù)并不是你想像中的人多力量大处渣。

GCD其他函數(shù)用法

1. dispatch_after

該函數(shù)用于任務(wù)延時執(zhí)行伶贰,其中參數(shù)dispatch_time_t代表延時時長,dispatch_queue_t代表使用哪個隊列罐栈。如果隊列未主隊列黍衙,那么任務(wù)在主線程執(zhí)行,如果隊列為全局隊列或者自己創(chuàng)建的隊列荠诬,那么任務(wù)在子線程執(zhí)行琅翻,代碼如下:

-(void)GCDDelay{
    //主隊列延時
    dispatch_time_t when_main = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(when_main, dispatch_get_main_queue(), ^{
        NSLog(@"main_%@",[NSThread currentThread]);
    });
    //全局隊列延時
    dispatch_time_t when_global = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC));
    dispatch_after(when_global, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"global_%@",[NSThread currentThread]);
    });
    //自定義隊列延時
    dispatch_time_t when_custom = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
    dispatch_after(when_custom, self.serialQueue, ^{
        NSLog(@"custom_%@",[NSThread currentThread]);
    });
}

打印結(jié)果log:

ThreadDemo[1508:499647] main_{number = 1, name = main}
ThreadDemo[1508:499697] global_{number = 3, name = (null)}
ThreadDemo[1508:499697] custom_{number = 3, name = (null)}
2. dispatch_once

保證函數(shù)在整個生命周期內(nèi)只會執(zhí)行一次位仁,看代碼。

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
}

打印結(jié)果:

ThreadDemo[1524:509261] {number = 1, name = main}

無論你怎么瘋狂的點擊方椎,在第一次打印之后聂抢,輸出臺便巋然不動。

3. dispatch_group_async & dispatch_group_notify

試想棠众,現(xiàn)在牛逼的你要現(xiàn)在兩張小圖琳疏,并且你要等兩張圖都下載完成之后把他們拼起來,你要怎么做闸拿?我根本就不會把兩張圖拼成一張圖啊空盼,牛逼的我怎么可能有這種想法呢?

其實方法有很多新荤,比如你可以一張一張下載揽趾,再比如使用局部變量和Blcok實現(xiàn)計數(shù),但是既然今天我們講到這苛骨,那我們就得入鄉(xiāng)隨俗篱瞎,用GCD來實現(xiàn),有一個神器的東西叫做隊列組痒芝,當(dāng)加入到隊列組中的所有任務(wù)執(zhí)行完成之后俐筋,會調(diào)用dispatch_group_notify函數(shù)通知任務(wù)全部完成,代碼如下:

-(void)GCDGroup{

//

[self jointImageView];

//

dispatch_group_t group = dispatch_group_create();

__block UIImage *image_1 = nil;

__block UIImage *image_2 = nil;

//在group中添加一個任務(wù)

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

image_1 = [self imageWithPath:@"[https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502706256731&di=371f5fd17184944d7e2b594142cd7061&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201605%2F14%2F20160514165210_LRCji.jpeg](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502706256731&di=371f5fd17184944d7e2b594142cd7061&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201605%2F14%2F20160514165210_LRCji.jpeg)"``];`

});

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

image_2 = [self imageWithPath:@"[https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=776127947](https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=776127947),2002573948&fm=26&gp=0.jpg"];

});

//group中所有任務(wù)執(zhí)行完畢吼野,通知該方法執(zhí)行

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

self.imageView_1.image = image_1;

self.imageView_2.image = image_2;

//

UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0f);

[image_2 drawInRect:CGRectMake(0, 0, 100, 100)];

[image_1 drawInRect:CGRectMake(100, 0, 100, 100)];

UIImage *image_3 = UIGraphicsGetImageFromCurrentImageContext();

self.imageView_3.image = image_3;

UIGraphicsEndImageContext();

});

}

-(void)jointImageView{

self.imageView_1 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 50, 100, 100)];

[self.view addSubview:_imageView_1];

self.imageView_2 = [[UIImageView alloc] initWithFrame:CGRectMake(140, 50, 100, 100)];

[self.view addSubview:_imageView_2];

self.imageView_3 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 200, 200, 100)];

[self.view addSubview:_imageView_3];

self.imageView_1.layer.borderColor = self.imageView_2.layer.borderColor = self.imageView_3.layer.borderColor = [UIColor grayColor].CGColor;

self.imageView_1.layer.borderWidth = self.imageView_2.layer.borderWidth = self.imageView_3.layer.borderWidth = 1;

}

4. dispatch_barrier_async

柵欄函數(shù)校哎,這么看來它能擋住或者分隔什么東西,別瞎猜了瞳步,反正你又猜不對闷哆,看這,使用此方法創(chuàng)建的任務(wù)单起,會查找當(dāng)前隊列中有沒有其他任務(wù)要執(zhí)行抱怔,如果有,則等待已有任務(wù)執(zhí)行完畢后再執(zhí)行嘀倒,同時屈留,在此任務(wù)之后進入隊列的任務(wù),需要等待此任務(wù)執(zhí)行完成后测蘑,才能執(zhí)行灌危。看代碼碳胳,老鐵勇蝙。

-(void)GCDbarrier{
     
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)2");
    });
     
//    dispatch_barrier_async(self.concurrentQueue, ^{
//        NSLog(@"任務(wù)barrier");
//    });
     
//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)4");
    });
}

運行結(jié)果:

ThreadDemo[1816:673351] 任務(wù)3
ThreadDemo[1816:673353] 任務(wù)1
ThreadDemo[1816:673350] 任務(wù)2
ThreadDemo[1816:673370] 任務(wù)4

是不是如你所料,牛逼大了挨约,下面我們打開第一句注釋:

-(void)GCDbarrier{
     
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)2");
    });
     
    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)barrier");
    });
     
//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)4");
    });
}

打印結(jié)果:

ThreadDemo[1833:678739] 任務(wù)2
ThreadDemo[1833:678740] 任務(wù)1
ThreadDemo[1833:678740] 任務(wù)barrier
ThreadDemo[1833:678740] 任務(wù)3
ThreadDemo[1833:678739] 任務(wù)4

這個結(jié)果和我們上面的解釋完美契合味混,我們可以簡單的控制函數(shù)執(zhí)行的順序了产雹,你離大牛又近了一步,如果現(xiàn)在的你不會懷疑還有dispatch_barrier_sync這個函數(shù)的話翁锡,說明... ...嘿嘿嘿蔓挖,我們看一下這個函數(shù)和上面我們用到的函數(shù)的區(qū)別,你一定想到了馆衔,再打開第二個和第三個注釋瘟判,如下:

-(void)GCDbarrier{        
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)2");
    });
     
    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)barrier");
    });
     
    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)4");
    });
}

運行結(jié)果:

ThreadDemo[1853:692434] 任務(wù)1
ThreadDemo[1853:692421] 任務(wù)2
ThreadDemo[1853:692387] big
ThreadDemo[1853:692421] 任務(wù)barrier
ThreadDemo[1853:692387] apple
ThreadDemo[1853:692421] 任務(wù)3
ThreadDemo[1853:692434] 任務(wù)4

不要著急,我們換一下函數(shù):

-(void)GCDbarrier{
     
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)2");
    });
     
    dispatch_barrier_sync(self.concurrentQueue, ^{
        NSLog(@"任務(wù)barrier");
    });
     
    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)4");
    });
}

打印結(jié)果:

ThreadDemo[1874:711841] 任務(wù)1
ThreadDemo[1874:711828] 任務(wù)2
ThreadDemo[1874:711793] 任務(wù)barrier
ThreadDemo[1874:711793] big
ThreadDemo[1874:711793] apple
ThreadDemo[1874:711828] 任務(wù)3
ThreadDemo[1874:711841] 任務(wù)4

老鐵角溃,發(fā)現(xiàn)了嗎荒适?這兩個函數(shù)對于隊列的柵欄作用是一樣的,但是對于該函數(shù)相對于其他內(nèi)部函數(shù)遵循了最開始說到的同步和異步的規(guī)則开镣。你是不是有點懵逼,如果你蒙蔽了咽扇,那么請在每一個輸出后面打印出當(dāng)前的線程邪财,如果你還是懵逼,那么請你重新看质欲,有勞树埠,不謝!

5. dispatch_apply

該函數(shù)用于重復(fù)執(zhí)行某個任務(wù)嘶伟,如果任務(wù)隊列是并行隊列怎憋,重復(fù)執(zhí)行的任務(wù)會并發(fā)執(zhí)行,如果任務(wù)隊列為串行隊列九昧,則任務(wù)會順序執(zhí)行绊袋,需要注意的是,該函數(shù)為同步函數(shù)铸鹰,要防止線程阻塞和死鎖哦癌别,老鐵。

串行隊列:

-(void)GCDApply{
    //重復(fù)執(zhí)行
    dispatch_apply(5, self.serialQueue, ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

運行結(jié)果:

ThreadDemo[1446:158101] 第0次_{number = 1, name = main}
ThreadDemo[1446:158101] 第1次_{number = 1, name = main}
ThreadDemo[1446:158101] 第2次_{number = 1, name = main}
ThreadDemo[1446:158101] 第3次_{number = 1, name = main}
ThreadDemo[1446:158101] 第4次_{number = 1, name = main}

并行隊列:

-(void)GCDApply{
    //重復(fù)執(zhí)行
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

運行結(jié)果:

ThreadDemo[1461:160567] 第2次_{number = 4, name = (null)}
ThreadDemo[1461:160534] 第0次_{number = 1, name = main}
ThreadDemo[1461:160566] 第3次_{number = 5, name = (null)}
ThreadDemo[1461:160569] 第1次_{number = 3, name = (null)}
ThreadDemo[1461:160567] 第4次_{number = 4, name = (null)}

死鎖:

-(void)GCDApply{
    //重復(fù)執(zhí)行
    dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

運行結(jié)果:

image

6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait

看這幾個函數(shù)的時候你需要拋開隊列蹋笼,丟掉同步異步展姐,不要把它們想到一起,混為一談剖毯,信號量只是控制任務(wù)執(zhí)行的一個條件而已圾笨,相對于上面通過隊列以及執(zhí)行方式來控制線程的開辟和任務(wù)的執(zhí)行,它更貼近對于任務(wù)直接的控制逊谋。類似于單個隊列的最大并發(fā)數(shù)的控制機制擂达,提高并行效率的同時,也防止太多線程的開辟對CPU早層負(fù)面的效率負(fù)擔(dān)涣狗。

dispatch_semaphore_create創(chuàng)建信號量谍婉,初始值不能小于0舒憾;

dispatch_semaphore_wait等待降低信號量,也就是信號量-1穗熬;

dispatch_semaphore_signal提高信號量镀迂,也就是信號量+1;

dispatch_semaphore_wait和dispatch_semaphore_signal通常配對使用唤蔗。

看一下代碼吧探遵,老鐵。

-(void)GCDSemaphore{
    //
    //dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        //dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            //dispatch_semaphore_signal(semaphore);
        });
    });
}

你能猜到運行結(jié)果嗎妓柜?沒錯箱季,就是你想的這樣,開辟了5個線程執(zhí)行任務(wù)棍掐。

ThreadDemo[1970:506692] 第0次_{number = 3, name = (null)}
ThreadDemo[1970:506711] 第1次_{number = 4, name = (null)}
ThreadDemo[1970:506713] 第2次_{number = 5, name = (null)}
ThreadDemo[1970:506691] 第3次_{number = 6, name = (null)}
ThreadDemo[1970:506694] 第4次_{number = 7, name = (null)}

下一步你一定猜到了藏雏,把注釋的代碼打開:

-(void)GCDSemaphore{
    //
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
    });
}

運行結(jié)果:

ThreadDemo[2020:513651] 第0次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第1次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第2次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第3次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第4次_{number = 3, name = (null)}

很明顯,我開始說的是對的作煌,哈哈哈哈掘殴,信號量是控制任務(wù)執(zhí)行的重要條件,當(dāng)信號量為0時粟誓,所有任務(wù)等待奏寨,信號量越大,允許可并行執(zhí)行的任務(wù)數(shù)量越多鹰服。

GCD就先說到這病瞳,很多API沒有涉及到,有興趣的同學(xué)們可以自己去看看悲酷,重要的是方法和習(xí)慣套菜,而不是你看過多少。

image

NSOperation && NSOperationQueue

如果上面的郭草地如果你學(xué)會了舔涎,那么這兩個東西你也不一定能學(xué)得會笼踩!

NSOperation以及NSOperationQueue是蘋果對于GCD的封裝,其中呢亡嫌,NSOperation其實就是我們上面所說的任務(wù)嚎于,但是這個類不能直接使用,我們要用他的兩個子類挟冠,NSBlockOperation和NSInvocationOperation于购,而NSOperationQueue呢,其實就是類似于GCD中的隊列知染,用于管理你加入到其中的任務(wù)肋僧。

NSOperation

它提供了關(guān)于任務(wù)的執(zhí)行,取消,以及隨時獲取任務(wù)的狀態(tài)嫌吠,添加任務(wù)依賴以及優(yōu)先級等方法和屬性止潘,相對于GCD提供的方法來說,更直觀辫诅,更方便凭戴,并且提供了更多的控制接口。(很多時候炕矮,蘋果設(shè)計的架構(gòu)是很棒的么夫,不要只是在乎他實現(xiàn)了什么,可能你學(xué)到的東西會更多肤视,一不小心又吹牛逼了档痪,哦呵呵),有幾個方法和屬性我們了解一下:

@interface NSOperation : NSObject {
@private
    id _private;
    int32_t _private1;
#if __LP64__
    int32_t _private1b;
#endif
}
- (void)start;//啟動任務(wù) 默認(rèn)加入到當(dāng)前隊列
- (void)main;//自定義NSOperation邢滑,寫一個子類腐螟,重寫這個方法,在這個方法里面添加需要執(zhí)行的操作困后。
@property (readonly, getter=isCancelled) BOOL cancelled;//是否已經(jīng)取消遭垛,只讀
- (void)cancel;//取消任務(wù)
@property (readonly, getter=isExecuting) BOOL executing;//正在執(zhí)行,只讀
@property (readonly, getter=isFinished) BOOL finished;//執(zhí)行結(jié)束操灿,只讀
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);//是否并發(fā),只讀
@property (readonly, getter=isReady) BOOL ready;//準(zhǔn)備執(zhí)行
- (void)addDependency:(NSOperation *)op;//添加依賴
- (void)removeDependency:(NSOperation *)op;//移除依賴
@property (readonly, copy) NSArray *dependencies;//所有依賴關(guān)系泵督,只讀
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};//系統(tǒng)提供的優(yōu)先級關(guān)系枚舉
@property NSOperationQueuePriority queuePriority;//執(zhí)行優(yōu)先級
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);//任務(wù)執(zhí)行完成之后的回調(diào)
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);//阻塞當(dāng)前線程趾盐,等到某個operation執(zhí)行完畢。
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);//已廢棄小腊,用qualityOfService替代救鲤。
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服務(wù)質(zhì)量,一個高質(zhì)量的服務(wù)就意味著更多的資源得以提供來更快的完成操作秩冈。
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);//任務(wù)名稱
@end

然而NSOperation本身是個抽象類本缠,不能直接使用,我們有三種方式賦予它新的生命入问,就是下面這三個東西丹锹,您坐穩(wěn)看好。

NSOperation自定義子類

這是我要說的第一個任務(wù)類型芬失,我們可以自定義繼承于NSOperation的子類楣黍,并重寫父類提供的方法,實現(xiàn)一波具有特殊意義的任務(wù)棱烂。比如我們?nèi)ハ螺d一個圖片:

.h
#import @protocol YSImageDownLoadOperationDelegate -(void)YSImageDownLoadFinished:(UIImage*)image;
@end
@interface YSImageDownLoadOperation : NSOperation
-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id)delegate;
@end
.m
#import "YSImageDownLoadOperation.h"
@implementation YSImageDownLoadOperation{
    NSURL *_imageUrl;
    id _delegate;
}
-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id)delegate{
    if (self == [super init]) {
        _imageUrl = imageUrl;
        _delegate = delegate;
    }
    return self;
}
-(void)main{
    @autoreleasepool {
        UIImage *image = [self imageWithUrl:_imageUrl];
        if (_delegate && [_delegate respondsToSelector:@selector(YSImageDownLoadFinished:)]) {
            [_delegate YSImageDownLoadFinished:image];
        }
    }
}
-(UIImage*)imageWithUrl:(NSURL*)url{
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:imageData];
    return image;
}
@end
然后調(diào)用:
-(void)YSDownLoadImageOperationRun{
    YSImageDownLoadOperation *ysOper = [[YSImageDownLoadOperation alloc] initOperationWithUrl:[NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"] delegate:self];
    [ysOper start];
}
-(void)YSImageDownLoadFinished:(UIImage *)image{
    NSLog(@"%@",image);
}

運行打印結(jié)果:

ThreadDemo[4141:1100329] , {700, 1050}

哦呵呵租漂,其實自定義的任務(wù)更具有指向性,它可以滿足你特定的需求,但是一般用的比較少哩治,不知道是因為我太菜還是真的有很多更加方便的方法和思路實現(xiàn)這樣的邏輯秃踩。

image

NSBlockOperation

第二個,就是系統(tǒng)提供的NSOperation的子類NSBlockOperation业筏,我們看一下他提供的API:

@interface NSBlockOperation : NSOperation {
@private
    id _private2;
    void *_reserved2;
}
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray *executionBlocks;
@end

很簡單憔杨,就這幾個,我們就用它實現(xiàn)一個任務(wù):

-(void)NSBlockOperationRun{
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperationRun_%@_%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
    }];
    [blockOper start];
}

運行結(jié)果:

ThreadDemo[4313:1121900] NSBlockOperationRun_{name = 'NSOperationQueue Main Queue'}_{number = 1, name = main}

我們發(fā)現(xiàn)這個任務(wù)是在當(dāng)前線程順序執(zhí)行的驾孔,我們發(fā)現(xiàn)還有一個方法addExecutionBlock:試一下:

-(void)NSBlockOperationRun{
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperationRun_1_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{
        NSLog(@"NSBlockOperationRun_2_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{
        NSLog(@"NSBlockOperationRun_3_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{
        NSLog(@"NSBlockOperationRun_4_%@",[NSThread currentThread]);
    }];
    [blockOper start];
}

打印結(jié)果:

ThreadDemo[4516:1169835] NSBlockOperationRun_1_{number = 1, name = main}
ThreadDemo[4516:1169875] NSBlockOperationRun_3_{number = 4, name = (null)}
ThreadDemo[4516:1169877] NSBlockOperationRun_4_{number = 5, name = (null)}
ThreadDemo[4516:1169893] NSBlockOperationRun_2_{number = 3, name = (null)}

從打印結(jié)果來看芍秆,這個4個任務(wù)是異步并發(fā)執(zhí)行的,開辟了多條線程翠勉。

NSInvocationOperation

第三個妖啥,就是它了,同樣也是系統(tǒng)提供給我們的一個任務(wù)類对碌,基于一個target對象以及一個selector來創(chuàng)建任務(wù)荆虱,具體代碼:

-(void)NSInvocationOperationRun{
    NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil];
    [invocationOper start];
}
-(void)invocationOperSel{
    NSLog(@"NSInvocationOperationRun_%@",[NSThread currentThread]);
}

運行結(jié)果:

ThreadDemo[4538:1173118] NSInvocationOperationRun_{number = 1, name = main}

運行結(jié)果與NSBlockOperation單個block函數(shù)的執(zhí)行方式相同,同步順序執(zhí)行朽们。的確系統(tǒng)的封裝給予我們關(guān)于任務(wù)更直觀的東西怀读,但是對于多個任務(wù)的控制機制并不完善,所以我們有請下一位骑脱,也許你會眼前一亮菜枷。

4408163-2b5e4c01cc727efe.jpeg

NSOperationQueue

上面說道我們創(chuàng)建的NSOperation任務(wù)對象可以通過start方法來執(zhí)行,同樣我們可以把這個任務(wù)對象添加到一個NSOperationQueue對象中去執(zhí)行叁丧,好想有好東西啤誊,先看一下系統(tǒng)的API:

@interface NSOperationQueue : NSObject {
@private
    id _private;
    void *_reserved;
}
- (void)addOperation:(NSOperation *)op;//添加任務(wù)
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);//添加一組任務(wù)
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);//添加一個block形式的任務(wù)
@property (readonly, copy) NSArray *operations;//隊列中所有的任務(wù)數(shù)組
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);//隊列中的任務(wù)數(shù)
@property NSInteger maxConcurrentOperationCount;//最大并發(fā)數(shù)
@property (getter=isSuspended) BOOL suspended;//暫停
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);//名稱
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服務(wù)質(zhì)量,一個高質(zhì)量的服務(wù)就意味著更多的資源得以提供來更快的完成操作拥娄。
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
- (void)cancelAllOperations;//取消隊列中的所有任務(wù)
- (void)waitUntilAllOperationsAreFinished;//阻塞當(dāng)前線程蚊锹,等到隊列中的任務(wù)全部執(zhí)行完畢。
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);//獲取當(dāng)前隊列
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);//獲取主隊列
#endif
@end

來一段代碼開心開心:

-(void)NSOperationQueueRun{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil];
    [queue addOperation:invocationOper];
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperationRun_%@",[NSThread currentThread]);
    }];
    [queue addOperation:blockOper];
    [queue addOperationWithBlock:^{
        NSLog(@"QUEUEBlockOperationRun_%@",[NSThread currentThread]);
    }];
}

打印結(jié)果:

ThreadDemo[4761:1205689] NSBlockOperationRun_{number = 4, name = (null)}
ThreadDemo[4761:1205691] NSInvocationOperationRun_{number = 3, name = (null)}
ThreadDemo[4761:1205706] QUEUEBlockOperationRun_{number = 5, name = (null)}

我們發(fā)現(xiàn)稚瘾,加入隊列之后不用調(diào)用任務(wù)的start方法牡昆,隊列會幫你管理任務(wù)的執(zhí)行情況。上訴執(zhí)行結(jié)果說明這些任務(wù)在隊列中為并發(fā)執(zhí)行的摊欠。

下面我們改變一下任務(wù)的優(yōu)先級:

invocationOper.queuePriority = NSOperationQueuePriorityVeryLow;

運行結(jié)果:

ThreadDemo[4894:1218440] QUEUEBlockOperationRun_{number = 3, name = (null)}
ThreadDemo[4894:1218442] NSBlockOperationRun_{number = 4, name = (null)}
ThreadDemo[4894:1218457] NSInvocationOperationRun_{number = 5, name = (null)}

我們發(fā)現(xiàn)優(yōu)先級低的任務(wù)會后執(zhí)行丢烘,但是,這并不是絕對的些椒,還有很多東西可以左右CPU分配铅协,以及操作系統(tǒng)對于任務(wù)和線程的控制,只能說摊沉,優(yōu)先級會在一定程度上讓優(yōu)先級高的任務(wù)開始執(zhí)行狐史。同時,優(yōu)先級只對同一隊列中的任務(wù)有效哦。下面我們就看一個會忽視優(yōu)先級的情況骏全。

添加依賴關(guān)系

-(void)NSOperationQueueRun{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *blockOper_1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 1000; i++) {
            NSLog(@"blockOper_1_%@_%@",@(i),[NSThread currentThread]);
        }
    }];
     
    NSBlockOperation *blockOper_2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 1000; i++) {
            NSLog(@"blockOper_2_%@_%@",@(i),[NSThread currentThread]);
        }
    }];
     
    [blockOper_1 addDependency:blockOper_2];
    [queue addOperation:blockOper_1];
    [queue addOperation:blockOper_2];
}

打印結(jié)果:

ThreadDemo[5066:1233824] blockOper_2_0_{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_1_{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_2_{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_3_{number = 3, name = (null)}
... ...
ThreadDemo[5066:1233824] blockOper_2_999_{number = 3, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_0_{number = 4, name = (null)}
... ...
ThreadDemo[5066:1233822] blockOper_1_997_{number = 4, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_998_{number = 4, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_999_{number = 4, name = (null)}

通過打印結(jié)果我們可以看到苍柏,添加依賴之后,依賴任務(wù)必須等待被依賴任務(wù)執(zhí)行完畢之后才會開始執(zhí)行姜贡。??试吁,就算依賴任務(wù)的優(yōu)先級再高,也是被依賴任務(wù)先執(zhí)行楼咳,同時熄捍,和優(yōu)先級不同,依賴關(guān)系不受隊列的局限母怜,愛哪哪余耽,只要是我依賴于你,那你必須先執(zhí)行完苹熏,我才執(zhí)行碟贾。

隊列的最大并發(fā)數(shù)

就是說,這個隊列最多可以有多少任務(wù)同時執(zhí)行袱耽,或者說最多開辟多少條線程干发,如果設(shè)置為1,那就一次只能執(zhí)行一個任務(wù)蔬崩,但是,不要以為這和GCD的串行隊列一樣,就算最大并發(fā)數(shù)為1跨琳,隊列任務(wù)的執(zhí)行順序依然取決于很多因素。

關(guān)于NSOperationQueue還有取消啊桂敛,暫停啊等操作方式溅潜,大家可以試一下,應(yīng)該注意的是粗仓,和學(xué)習(xí)GCD的方式不同,不要總是站在面向過程的角度看帶這些面向?qū)ο蟮念悾驗樗拿嫦鄬ο蠡姆庋b過程中借浊,肯定有很多你看不到的面相過程的操作塘淑,所以你也沒有必要用使用GCD的思想來套用它距糖,否則你可能會迷糊的一塌糊涂岭皂。

線程鎖

上面算是把多線程操作的方法講完了,下面說一下線程鎖機制铅碍。多線程操作是多個線程并行的曙蒸,所以同一塊資源可能在同一時間被多個線程訪問捌治,舉爛的例子就是買火車票,在就剩一個座時纽窟,如果100個線程同時進入肖油,那么可能上火車時就有人得干仗了。為了維護世界和平师倔,人民安定构韵,所以我們講一下這個線程鎖。我們先實現(xiàn)一段代碼:

- (void)viewDidLoad {
        [super viewDidLoad];
        self.sourceArray_m = [NSMutableArray new];
        [_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
        [self threadLock];
    }
    -(void)threadLock{
        for (int i = 0; i < 8; i++) {
            dispatch_async(self.concurrentQueue, ^{
                NSLog(@"%@",[self sourceOut]) ;
            });
        }
    }
    -(NSString*)sourceOut{
        NSString *source = @"沒有了,取光了";
        if (_sourceArray_m.count > 0) {
            source = [_sourceArray_m lastObject];
            [_sourceArray_m removeLastObject];
        }
        return source;
}

運行打印結(jié)果:

ThreadDemo[5540:1291666] 6
ThreadDemo[5540:1291669] 6
ThreadDemo[5540:1291682] 5
ThreadDemo[5540:1291667] 4
ThreadDemo[5540:1291683] 3
ThreadDemo[5540:1291666] 2
ThreadDemo[5540:1291669] 1
ThreadDemo[5540:1291682] 沒有了,取光了

我們發(fā)現(xiàn)6被取出來兩次(因為代碼簡單趋艘,執(zhí)行效率較快疲恢,所以這種情況不實必現(xiàn),耐心多試幾次)瓷胧,這樣的話就尷尬了显拳,一張票賣了2次,這么惡劣的行為是不可能容忍的搓萧,所以我們需要正義的衛(wèi)士——線程鎖杂数,我們就講最直接的兩種(之前說的GCD的很多方法同樣可以等價于線程鎖解決這些問題):

NSLock

代碼這樣寫:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.lock = [[NSLock alloc] init];
    self.sourceArray_m = [NSMutableArray new];
    [_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
    [self threadLock];
}
-(void)threadLock{
    for (int i = 0; i < 8; i++) {
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"%@",[self sourceOut]) ;
        });
    }
}
-(NSString*)sourceOut{
    NSString *source = @"沒有了,取光了";
    [_lock lock];
    if (_sourceArray_m.count > 0) {
        source = [_sourceArray_m lastObject];
        [_sourceArray_m removeLastObject];
    }
    [_lock unlock];
    return source;
}

運行結(jié)果:

ThreadDemo[5593:1298144] 5
ThreadDemo[5593:1298127] 6
ThreadDemo[5593:1298126] 4
ThreadDemo[5593:1298129] 3
ThreadDemo[5593:1298146] 2
ThreadDemo[5593:1298144] 1
ThreadDemo[5593:1298127] 沒有了,取光了
ThreadDemo[5593:1298147] 沒有了,取光了

這樣就保證了被Lock的資源只能同時讓一個線程進行訪問,從而也就保證了線程安全瘸洛。

@synchronized

這個也很簡單揍移,有時候也會用到這個,要傳入一個同步對象(一般就是self)罕邀,然后將你需要加鎖的資源放入代碼塊中诉探,如果該資源有線程正在訪問時肾胯,會讓其他線程等待怕敬,直接上代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.sourceArray_m = [NSMutableArray new];
    [_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
    [self threadLock];
}
-(void)threadLock{
    for (int i = 0; i < 8; i++) {
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"%@",[self sourceOut]) ;
        });
    }
}
-(NSString*)sourceOut{
    NSString *source = @"沒有了,取光了";
    @synchronized (self) {
        if (_sourceArray_m.count > 0) {
            source = [_sourceArray_m lastObject];
            [_sourceArray_m removeLastObject];
        }
    }
    return source;
}

運行結(jié)果:

ThreadDemo[5625:1301834] 5

ThreadDemo[5625:1301835] 6

ThreadDemo[5625:1301837] 4

ThreadDemo[5625:1301852] 3

ThreadDemo[5625:1301834] 1

ThreadDemo[5625:1301854] 2

ThreadDemo[5625:1301835] 沒有了,取光了

ThreadDemo[5625:1301855] 沒有了,取光了

結(jié)語

看來該結(jié)束了!K涮睢曹动!就到這吧恶守,小弟已經(jīng)盡力了兔港,帶大家入個門衫樊,這條路小弟只能陪你走到這了科侈。

本文大部分內(nèi)容轉(zhuǎn)自http://www.cocoachina.com/ios/20170829/20404.html 一位大神臀栈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子威蕉,更是在濱河造成了極大的恐慌韧涨,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異第晰,居然都是意外死亡茁瘦,警方通過查閱死者的電腦和手機甜熔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焊虏,“玉大人炕淮,你說我怎么就攤上這事∪笄福” “怎么了踩衩?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長褐鸥。 經(jīng)常有香客問我浑侥,道長晰绎,這世上最難降的妖魔是什么伶选? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任考蕾,我火速辦了婚禮,結(jié)果婚禮上掸鹅,老公的妹妹穿的比我還像新娘葵姥。我一直安慰自己榔幸,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布拨齐。 她就那樣靜靜地躺著,像睡著了一般歼狼。 火紅的嫁衣襯著肌膚如雪羽峰。 梳的紋絲不亂的頭發(fā)上限寞,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機與錄音玫霎,去河邊找鬼。 笑死眷蚓,一個胖子當(dāng)著我的面吹牛叉钥,可吹牛的內(nèi)容都是我干的投队。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼扒披,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蟆淀?” 一聲冷哼從身側(cè)響起熔任,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤惦费,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扯夭,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡橡淑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年构拳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梁棠。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡隐圾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出掰茶,到底是詐尸還是另有隱情暇藏,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布濒蒋,位于F島的核電站瓮顽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏贮配。R本人自食惡果不足惜叼旋,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦蒙畴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽削罩。三九已至趾疚,卻和暖如春宝与,著一層夾襖步出監(jiān)牢的瞬間搞疗,已是汗流浹背拒贱。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工戚揭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沟绪,地道東北人钝凶。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓肿嘲,卻偏偏與公主長得像匣屡,于是被迫代替她去往敵國和親也拜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

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