GCD底層實現理解以及建立單例的兩種方式

Deft_MKJing宓珂璟發表於2016-12-21

GCD底層看到的另一種解釋
這裡對GCD的底層看到一段和Python中協程相關的理解,記錄一下

  • 1.GCD的層次比執行緒高,其底層是利用多執行緒來實現的,蘋果將執行緒交給了系統去管理,這樣任務的管理和執行比起執行緒來更加高效。
  • 2.使用者要做的是定義自己Task任務,然後將其放到合適的分發佇列去執行。
  • 3.可以將GCD看作是一種更加輕量級的執行緒,類似於Python中的協程的概念。GCD的本質是在作業系統層面提供提供並行排程的,這樣任務的切換效率要遠高於核心的切換效率,它的大部分功能實在執行庫裡實現的。

GCD(Grand Central Dispatch)是蘋果為實現併發程式設計提供的新技術。從基本功能上講,GCD有點像NSOperationQueue,他們都允許程式將任務切分為多個單一任務然後提交至工作佇列來併發地或者序列地執行。但是GCD比之NSOpertionQueue更底層更高效。

   GCD的API很大程度上基於block,當然,GCD也可以脫離block來使用,比如使用傳統c機制提供函式指標和上下文指標。實踐證明,當配合block使用時,GCD非常簡單易用且能發揮其最大能力,這也是我們經常在程式中使用的方式。

  GCD提供很多超越傳統多執行緒程式設計的優勢:

   a.易用: GCD比之thread更加簡單易用。由於GCD基於工作單元(work unit)而非像thread那樣基於運算,所以GCD可以控制諸如等待任務結束、監視檔案描述符、週期執行程式碼以及工作掛起等任務。作為程式設計師只要一心關注自己的業務邏輯,可以從繁雜的執行緒管理的工作中解放出來。基於block的血統導致它能極為簡單,可以在不同程式碼作用域之間傳遞上下文(閉包特性)。

   b.高效率: GCD被實現得如此輕量和優雅,使得它在很多地方比之專門建立消耗資源的執行緒更實用且快速。這關係到易用性:導致GCD易用的原因有一部分在於你可以不用擔心太多的效率問題而僅僅使用它就行了。

   c.高效能: GCD自動根據系統負載(比例記憶體使用情況、CPU個數)來增減執行緒數量,它甚至可以在一個執行緒中實現多工的併發,這就減少了上下文切換帶來的開銷,增加了計算效率。

GDC的介紹有另外一篇部落格專門寫了下,需要看的可以點選以下傳送門GCD執行緒死鎖解鎖案例分析,這裡主要記錄下基本的API使用以及兩種單例的建立方式

1.GCD同步非同步併發序列排列組合的集中基本形式


/*
 *  非同步序列   --->       不阻塞當前執行緒,會開多一條執行緒,在這個執行緒中是序列執行的
 */
- (void)asynchSerial
{
    // 全域性併發佇列
    //    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 自定義併發佇列
    dispatch_queue_t queue1 = dispatch_queue_create("COM.MKJ.COM", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue1, ^{
        NSLog(@"------1,%@",[NSThread currentThread]);
    });

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

    dispatch_async(queue1, ^{
        NSLog(@"------3,%@",[NSThread currentThread]);
    });
    NSLog(@"asynchSerial------end");
//    2016-12-21 09:34:16.222 GCDBasicAPI[1137:38266] asynchSerial------end
//    2016-12-21 09:34:16.222 GCDBasicAPI[1137:38300] ------1,<NSThread: 0x60000007a640>{number = 3, name = (null)}
//    2016-12-21 09:34:16.223 GCDBasicAPI[1137:38300] ------2,<NSThread: 0x60000007a640>{number = 3, name = (null)}
//    2016-12-21 09:34:16.223 GCDBasicAPI[1137:38300] ------3,<NSThread: 0x60000007a640>{number = 3, name = (null)}

}


/*
 *  同步序列   --->       阻塞當前執行緒,不開多條執行緒,在當前執行緒執行,順序執行
 */
- (void)synchSerial
{
    // 全域性併發佇列
    //    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 自定義併發佇列
    dispatch_queue_t queue1 = dispatch_queue_create("COM.MKJ.COM", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue1, ^{
        NSLog(@"------1,%@",[NSThread currentThread]);
    });

    dispatch_sync(queue1, ^{
        NSLog(@"------2,%@",[NSThread currentThread]);
    });

    dispatch_sync(queue1, ^{
        NSLog(@"------3,%@",[NSThread currentThread]);
    });
    NSLog(@"synchSerial------end");
//    2016-12-21 09:32:25.878 GCDBasicAPI[1117:36348] ------1,<NSThread: 0x600000075700>{number = 1, name = main}
//    2016-12-21 09:32:25.878 GCDBasicAPI[1117:36348] ------2,<NSThread: 0x600000075700>{number = 1, name = main}
//    2016-12-21 09:32:25.879 GCDBasicAPI[1117:36348] ------3,<NSThread: 0x600000075700>{number = 1, name = main}
//    2016-12-21 09:32:25.879 GCDBasicAPI[1117:36348] synchSerial------end

}




/*
 *  同步併發   --->       阻塞當前執行緒,不開多條執行緒,在當前執行緒執行,順序執行
 */
- (void)synchConcurrent
{
    // 全域性併發佇列
    //    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 自定義併發佇列
    dispatch_queue_t queue1 = dispatch_queue_create("COM.MKJ.COM", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue1, ^{
        NSLog(@"------1,%@",[NSThread currentThread]);
    });

    dispatch_sync(queue1, ^{
        NSLog(@"------2,%@",[NSThread currentThread]);
    });

    dispatch_sync(queue1, ^{
        NSLog(@"------3,%@",[NSThread currentThread]);
    });
    NSLog(@"synchConcurrent------end");
//    2016-12-21 09:29:47.575 GCDBasicAPI[1071:33607] ------1,<NSThread: 0x60000006b900>{number = 1, name = main}
//    2016-12-21 09:29:47.575 GCDBasicAPI[1071:33607] ------2,<NSThread: 0x60000006b900>{number = 1, name = main}
//    2016-12-21 09:29:47.576 GCDBasicAPI[1071:33607] ------3,<NSThread: 0x60000006b900>{number = 1, name = main}
//    2016-12-21 09:29:47.576 GCDBasicAPI[1071:33607] synchConcurrent------end

}


/*
 *  非同步併發   --->       不阻塞當前執行緒,可以開多條執行緒,併發執行,順序不一定
 */
- (void)asynchConcurrent
{
    // 全域性併發佇列
//    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 自定義併發佇列
    dispatch_queue_t queue1 = dispatch_queue_create("COM.MKJ.COM", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue1, ^{
        NSLog(@"------1,%@",[NSThread currentThread]);
    });

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

    dispatch_async(queue1, ^{
        NSLog(@"------3,%@",[NSThread currentThread]);
    });
    NSLog(@"asynchConcurrent------end");
//    2016-12-21 09:28:23.895 GCDBasicAPI[1050:32223] asynchConcurrent------end
//    2016-12-21 09:28:23.895 GCDBasicAPI[1050:32292] ------1,<NSThread: 0x60800026e180>{number = 3, name = (null)}
//    2016-12-21 09:28:23.895 GCDBasicAPI[1050:32293] ------3,<NSThread: 0x60800026fbc0>{number = 5, name = (null)}
//    2016-12-21 09:28:23.895 GCDBasicAPI[1050:32295] ------2,<NSThread: 0x60000026e0c0>{number = 4, name = (null)}

}

簡單概括如下:

專案 同步(sync) 非同步(async)
序列 當前執行緒,順序執行 另一個執行緒,順序執行
併發 當前執行緒,順序執行 另一個執行緒,同時執行

可以看出同步和非同步就是開執行緒的能力,同步執行必然一個個順序執行在當前執行緒,而非同步執行可以根據佇列不同來確定順序還是同步併發執行

2.最基本的執行緒間通訊方式

// 不阻塞當前執行緒,後臺拉取圖片資源
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSURL *url = [NSURL URLWithString:@"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcRCfHVwGXGvrpCBplQieSKsLgfBULL8ZZXSzosPFdoZsvjDlqnOrKK_w58"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        NSLog(@"獲取資源%@",[NSThread currentThread]);
        // 下載完之後回到主執行緒
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            NSLog(@"載入資源%@",[NSThread currentThread]);
        });
    });

    //    2016-12-21 09:43:51.675 GCDBasicAPI[1237:45972] 獲取資源<NSThread: 0x6000002742c0>{number = 3, name = (null)}
    //    2016-12-21 09:43:51.676 GCDBasicAPI[1237:45929] 載入資源<NSThread: 0x60000007b680>{number = 1, name = main}

3.其他Barrier,apply,after,Group等常用API

  • dispatch_barrier_async
/*
 *  barrier函式可以阻塞任務,執行到他這裡,要等之前的任務執行完才能執行之後的任務
 */
- (void)barrier
{
    dispatch_queue_t queue = dispatch_queue_create("com.mkj.hehe", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"--------1,%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 10; i ++)
        {
           NSLog(@"--------2%ld,%@",i,[NSThread currentThread]);
        }

    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"--------3,%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"--------4,%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"--------5,%@",[NSThread currentThread]);
    });
//    2016-12-21 09:58:09.231 GCDBasicAPI[1392:57437] --------1,<NSThread: 0x60000007f400>{number = 5, name = (null)}
//    2016-12-21 09:58:09.231 GCDBasicAPI[1392:57807] --------20,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.232 GCDBasicAPI[1392:57807] --------21,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.233 GCDBasicAPI[1392:57807] --------22,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.233 GCDBasicAPI[1392:57807] --------23,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.233 GCDBasicAPI[1392:57807] --------24,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.233 GCDBasicAPI[1392:57807] --------25,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.233 GCDBasicAPI[1392:57807] --------26,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.234 GCDBasicAPI[1392:57807] --------27,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.234 GCDBasicAPI[1392:57807] --------28,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.234 GCDBasicAPI[1392:57807] --------29,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.234 GCDBasicAPI[1392:57807] --------3,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.235 GCDBasicAPI[1392:57807] --------4,<NSThread: 0x60000007e780>{number = 7, name = (null)}
//    2016-12-21 09:58:09.235 GCDBasicAPI[1392:57437] --------5,<NSThread: 0x60000007f400>{number = 5, name = (null)}
}
  • dispatch_apply
/*
 *  apply 無序快速遍歷,可以用於檔案移動等需求
 */
- (void)apply
{
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"%ld,%@",index,[NSThread currentThread]);
    });
//    2016-12-21 10:04:01.487 GCDBasicAPI[1485:63041] 0,<NSThread: 0x600000069940>{number = 1, name = main}
//    2016-12-21 10:04:01.487 GCDBasicAPI[1485:63172] 2,<NSThread: 0x60800007a940>{number = 7, name = (null)}
//    2016-12-21 10:04:01.487 GCDBasicAPI[1485:63845] 1,<NSThread: 0x60800007a8c0>{number = 6, name = (null)}
//    2016-12-21 10:04:01.487 GCDBasicAPI[1485:63873] 3,<NSThread: 0x60800007ab00>{number = 8, name = (null)}
//    2016-12-21 10:04:01.487 GCDBasicAPI[1485:63041] 4,<NSThread: 0x600000069940>{number = 1, name = main}
//    2016-12-21 10:04:01.488 GCDBasicAPI[1485:63172] 5,<NSThread: 0x60800007a940>{number = 7, name = (null)}
//    2016-12-21 10:04:01.488 GCDBasicAPI[1485:63845] 6,<NSThread: 0x60800007a8c0>{number = 6, name = (null)}
//    2016-12-21 10:04:01.488 GCDBasicAPI[1485:63873] 7,<NSThread: 0x60800007ab00>{number = 8, name = (null)}
//    2016-12-21 10:04:01.488 GCDBasicAPI[1485:63041] 8,<NSThread: 0x600000069940>{number = 1, name = main}
//    2016-12-21 10:04:01.488 GCDBasicAPI[1485:63172] 9,<NSThread: 0x60800007a940>{number = 7, name = (null)}
}
  • dispatch_after
/*
 *  延時執行
 */
 - (void)delay
{
    // 方法1
    NSLog(@"開始了");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"我才開始");
    });
    // 方法2
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
}

- (void)run
{
    NSLog(@"run");
}
  • dispatch_group_async
/*
 *  執行緒組  實現多個請求完成之後同時重新整理UI的問題
 *  把多個任務加入到佇列裡面,最後都完成之後會呼叫Notify的方法進行通知,最終重新整理UI
 */

- (void)group
{
    // 併發佇列
    dispatch_queue_t queue = dispatch_queue_create("com.mkj.hh", DISPATCH_QUEUE_CONCURRENT);
    // 執行緒組
    dispatch_group_t group = dispatch_group_create();
    // 任務1加入組
    dispatch_group_async(group, queue, ^{

        NSURL *url = [NSURL URLWithString:@"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQxex7CvJ0pArQ8NHwXMaZ8fSt3ALAZBlljTQVlDsh6AIegeMjWWMoSVtej"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image1 = [UIImage imageWithData:data];
    });

    // 任務2加入組
    dispatch_group_async(group, queue, ^{
        // 這裡可以一樣是耗時的網路請求,暫時處理成本地的
        self.image2 = [UIImage imageNamed:@"Play"];
    });
    // 任務完成之後統一通知
    dispatch_group_notify(group, queue, ^{

        // 這裡的queue如果是mainQueue的話就可以直接回到主執行緒操作需要的UI
        // 現在還是在併發佇列裡面  進行圖片合成  還是放在子執行緒
        UIGraphicsBeginImageContext(CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width));
        [self.image1 drawInRect:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width)];
        [self.image2 drawInRect:CGRectMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.width/2, 30, 40)];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        // 回到主執行緒重新整理UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        });

    });
}

這裡寫圖片描述

4.兩種方式實現單例

  • 自己加鎖實現
static id obj;

+ (instancetype)shareInstance
{
    @synchronized (self) {
        if (!obj) {
            obj = [[self alloc] init];
        }
    }
    return obj;
}


+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    @synchronized (self) {
        if (!obj) {
            obj = [super allocWithZone:zone];
        }
    }
    return obj;
}

- (id)copyWithZone:(NSZone *)zone
{
    return obj;
}
  • GCD實現(直接抽成巨集)
#define MKJSingletonH + (instancetype)shareManager;
#define MKJSinletonM \
static id obj; \
 \
+ (instancetype)shareManager \
{ \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        obj = [[self alloc] init]; \
    }); \
    return obj; \
} \
 \
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        obj = [super allocWithZone:zone]; \
    }); \
    return obj; \
} \
 \
- (id)copyWithZone:(NSZone *)zone \
{ \
    return obj; \
}

抽成巨集之後可以直接進行呼叫就OK

@interface ManagerHelper : NSObject
MKJSingletonH
@end

@implementation ManagerHelper
MKJSinletonM
@end

注:這裡為什麼需要用Static進行修飾,static保證只有本檔案才能訪問,這樣就能形成單例,但是如果不用static修飾,外部也一樣能訪問,這個時候外部如果置為nil之後,這個物件就不存在了,再也不會建立了,dispatch_once只會執行一次,這樣肯定不對的



非常簡單的介紹,這裡有個Demo,需要的朋友可以下載看看基礎用法先,文章頭部有介紹GCD死鎖解鎖的案例文章,需要的也可以看看
本文Demo傳送門
NSOperation用法和GCD之間的關係

相關文章