iOS 多線程最全總結(jié)

一薪寓、iOS中常見的多線程方案

1褪那、pthread
  • 一套通用的多線程API
  • 適用于Unix\Linux\Windows等系統(tǒng)
  • 跨平臺(tái)鲤遥、可移植
  • 使用難度大
  • 使用C語(yǔ)言
  • 程序員管理線程生命周期
  • 實(shí)際項(xiàng)目中幾乎不用
2斋日、NSThread
  • 使用更加面向?qū)ο?/li>
  • 簡(jiǎn)單易用饰恕、可直接操作線程對(duì)象
  • 使用OC語(yǔ)言
  • 程序員管理線程生命周期
  • 實(shí)際項(xiàng)目中幾乎不用
3挠羔、GCD
  • 旨在替代NSThread技術(shù)
  • 充分利用設(shè)備的多核
  • 使用C語(yǔ)言
  • 自動(dòng)管理線程生命周期
  • 實(shí)際項(xiàng)目中經(jīng)常使用
4、NSOperation
  • 基于GCD(底層是GCD)
  • 比GCD多了一些簡(jiǎn)單實(shí)用的功能
  • 使用更加面向?qū)ο?/li>
  • 使用OC語(yǔ)言
  • 自動(dòng)管理線程生命周期
  • 實(shí)際項(xiàng)目中經(jīng)常使用

在項(xiàng)目中更多的時(shí)候我們使用的是GCD埋嵌。


image.png

二破加、GCD

1、GCD有兩個(gè)用來執(zhí)行任務(wù)的函數(shù)
  • 同步(sync):只能在當(dāng)前線程中執(zhí)行任務(wù)雹嗦,不具備開啟新線程的能力范舀,任務(wù)立刻馬上執(zhí)行,會(huì)阻塞當(dāng)前線程并等待 Block中的任務(wù)執(zhí)行完畢dispatch函數(shù)才會(huì)返回了罪,然后當(dāng)前線程才會(huì)繼續(xù)往下運(yùn)行锭环。
  • 異步(async):可以在新的線程中執(zhí)行任務(wù),具備開啟線程的能力泊藕,但不一定會(huì)開啟新的線程田藐,dispatch函數(shù)會(huì)立即返回, 然后Block在后臺(tái)異步執(zhí)行,即當(dāng)前線程會(huì)直接往下執(zhí)行,不會(huì)阻塞當(dāng)前線程汽久。

相關(guān)代碼如下:

#pragma mark - 同步執(zhí)行
- (void)syncQueue {
    NSLog(@"同步主線程開始");
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.weixin.globalQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"同步線程");
    });
    NSLog(@"同步主線程結(jié)束");
}

#pragma mark - 異步執(zhí)行
- (void)asyncQueue {
    NSLog(@"異步主線程開始");
    dispatch_queue_t queue = dispatch_queue_create("com.qq.globalQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"異步線程");
    });
    NSLog(@"異步主線程結(jié)束");
}

結(jié)果如下:
WX20180119-162214.png
2鹤竭、隊(duì)列

用于存放任務(wù),分為串行隊(duì)列和并行隊(duì)列景醇。

  • 串行隊(duì)列:所有任務(wù)會(huì)在一條線程中執(zhí)行(有可能是當(dāng)前線程也有可能是新開辟的線程)臀稚,并且一個(gè)任務(wù)執(zhí)行完畢后,才開始執(zhí)行下一個(gè)任務(wù)三痰。

  • 并行隊(duì)列:可以開啟多條線程并行執(zhí)行任務(wù)(但不一定會(huì)開啟新的線程)吧寺,并且當(dāng)一個(gè)任務(wù)放到指定線程開始執(zhí)行時(shí),下一個(gè)任務(wù)就可以開始執(zhí)行了

  • 創(chuàng)建隊(duì)列:

  1. 串行隊(duì)列
//創(chuàng)建串行隊(duì)列
dispatch_queue_t firstQueue = dispatch_queue_create("com.weibo", DISPATCH_QUEUE_SERIAL);
  1. 并行隊(duì)列
//創(chuàng)建并行隊(duì)列
dispatch_queue_t secondQueue = dispatch_queue_create("com.facebook", DISPATCH_QUEUE_CONCURRENT);
  1. 創(chuàng)建全局默認(rèn)并發(fā)隊(duì)列
//創(chuàng)建全局默認(rèn)并發(fā)隊(duì)列
/**
   第一個(gè)參數(shù):優(yōu)先級(jí) 也可直接填后面的數(shù)字
   #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 // 后臺(tái)
   第二個(gè)參數(shù): 預(yù)留參數(shù)  0
*/
dispatch_queue_t queue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  1. 獲取主隊(duì)列
//獲取主隊(duì)列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • \color{red} {dispatch_async和dispatch_sync決定是否具備開啟新線程的能力散劫。}
  • \color{red} {隊(duì)列的類型決定了任務(wù)的執(zhí)行方式稚机。}

5、任務(wù)隊(duì)列組合


image.png
死鎖的問題

使用同步sync函數(shù)向當(dāng)前的串行queue中添加任務(wù)获搏,會(huì)卡住當(dāng)前串行隊(duì)列赖条,產(chǎn)生死鎖。

6常熙、隊(duì)列組
dispatch_group_async & dispatch_group_notify
代碼以及說明如下:

#pragma mark - 隊(duì)列組
- (void)GCDGroup {
    //創(chuàng)建隊(duì)列組
    dispatch_group_t group = dispatch_group_create();
    //1.開子線程下載圖片
    //創(chuàng)建隊(duì)列(并發(fā))
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //下載圖片1
    dispatch_group_async(group, queue, ^{
        NSURL *url = [NSURL URLWithString:@"http://www.huabian.com/uploadfile/2015/0914/20150914014032274.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image1 = [UIImage imageWithData:data];
        NSLog(@"image1 ==== %@",self.image1);
    });
    
    //下載圖片2
    dispatch_group_async(group, queue, ^{
        NSURL *url = [NSURL URLWithString:@"http://img1.3lian.com/img2011/w12/1202/19/d/88.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image2 = [UIImage imageWithData:data];
        NSLog(@"image2 ==== %@",self.image2);
    });
    
    //group中所有任務(wù)執(zhí)行完畢纬乍,通知該方法執(zhí)行
    dispatch_group_notify(group, queue, ^{
        //開啟圖形上下文
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        //畫1
        [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
        //畫2
        [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
        //根據(jù)圖形上下文拿到圖片
        UIImage *image =  UIGraphicsGetImageFromCurrentImageContext();
        //關(guān)閉上下文
        UIGraphicsEndImageContext();
        //回到主線程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            NSLog(@"%@--刷新UI",[NSThread currentThread]);
        });
    });
}

注意:隊(duì)列組一般用來比如進(jìn)入一個(gè)頁(yè)面,同時(shí)異步請(qǐng)求多個(gè)接口裸卫,然后再所有接口請(qǐng)求完畢后仿贬,再返回主線程刷新UI。

三墓贿、面試題

1茧泪、


image.png

2、atomic和nonatomic的區(qū)別
給屬性加上atomic修飾聋袋,可以保證屬性的getter和setter方法都是原子性操作(最小單位一個(gè)整體)队伟,也就是說保證setter和getter的內(nèi)部是線程同步的,在內(nèi)部會(huì)在調(diào)用set和get方法前后進(jìn)行加鎖和解鎖的操作舱馅。
但實(shí)際中很少有用到atomic,因?yàn)閷?shí)在太耗費(fèi)性能了刀荒,頻繁的調(diào)用屬性代嗤,頻繁的加鎖解鎖。

四缠借、iOS的各種鎖

多個(gè)線程同時(shí)操作同一塊的資源干毅,會(huì)引起數(shù)據(jù)的錯(cuò)誤混亂
經(jīng)典問題:存儲(chǔ)錢、賣票

解決方案:采用線程同步技術(shù)
常見的線程同步技術(shù):加鎖

1泼返、OSSpinLock自旋鎖
  • OSSpinLock叫做“自旋鎖”硝逢,等待鎖的線程會(huì)處于忙等狀態(tài),一直占用著CPU的資源,他不睡覺渠鸽。
  • 目前這個(gè)鎖已經(jīng)不安全叫乌,可能出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)的問題。
  • 如果等待鎖的線程優(yōu)先級(jí)高徽缚,它會(huì)一直占用著CPU資源憨奸,優(yōu)先級(jí)低的線程就無法釋放。
  • 所以在10.0以后這個(gè)OSSpinLock就被廢棄不再使用了凿试。
2排宰、os_unfair_lock
  • 用于取代不安全的OSSpinLink,從iOS10開始支持
  • os_unfair_lock是一種互斥鎖那婉,它不會(huì)向自旋鎖那樣忙等板甘,而是等待線程會(huì)休眠
os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
os_unfair_lock_lock(&unfairLock);
os_unfair_lock_unlock(&unfairLock);
3、pthread_mutex
  • 互斥鎖详炬,等待鎖的線程會(huì)處于休眠狀態(tài)
  • 跨平臺(tái)的 Unix Linux Windows iOS等都可以使用
  • 導(dǎo)入頭文件<pthread.h>
  • pthread_mutex(recursive) 遞歸鎖
    加鎖后只能有一個(gè)線程訪問該對(duì)象盐类,后面的線程需要排隊(duì),并且 lock 和 unlock 是對(duì)應(yīng)出現(xiàn)的痕寓,同一線程多次 lock 是不允許的傲醉,而遞歸鎖允許同一個(gè)線程在未釋放其擁有的鎖時(shí)反復(fù)對(duì)該鎖進(jìn)行加鎖操作。不用的線程用遞歸鎖呻率,其他線程也要等待unlock才行硬毕。
static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且給它賦予默認(rèn)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //設(shè)置鎖類型,這邊是設(shè)置為遞歸鎖
pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr); //銷毀一個(gè)屬性對(duì)象礼仗,在重新進(jìn)行初始化之前該結(jié)構(gòu)不能重新使用吐咳,初始化完要銷毀

//1.線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    static void (^RecursiveBlock)(int);
    RecursiveBlock = ^(int value) {
        pthread_mutex_lock(&pLock);
        if (value > 0) {
            NSLog(@"value: %d", value);
            RecursiveBlock(value - 1);
        }
        pthread_mutex_unlock(&pLock);
    };
    RecursiveBlock(5);
});
4、NSLock

NSLock是對(duì)mutex普通鎖的封裝元践。

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

tryLock 和 lock 方法都會(huì)請(qǐng)求加鎖韭脊,唯一不同的是trylock在沒有獲得鎖的時(shí)候可以繼續(xù)做一些任務(wù)和處理。lockBeforeDate方法也比較簡(jiǎn)單单旁,就是在limit時(shí)間點(diǎn)之前獲得鎖沪羔,沒有拿到返回NO。

5象浑、NSRecursiveLock
  • 遞歸鎖有一個(gè)特點(diǎn)蔫饰,遞歸鎖可以被同一線程多次請(qǐng)求,而不會(huì)引起死鎖愉豺。這主要是用在循環(huán)或遞歸操作中篓吁。
  • 是對(duì)pthread_mutex遞歸的封裝
6、NSCondition
  • 條件鎖
    定義:
    是對(duì)pthread_mutex中鎖以及條件的封裝
@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;

wait:進(jìn)入等待狀態(tài)
waitUntilDate::讓一個(gè)線程等待一定的時(shí)間
signal:?jiǎn)拘岩粋€(gè)等待的線程
broadcast:?jiǎn)拘阉械却木€程

7蚪拦、dispatch_semaphore信號(hào)量

GCD信號(hào)量的應(yīng)用場(chǎng)景杖剪,一般是控制最大并發(fā)量冻押,控制資源的同步訪問,如數(shù)據(jù)訪問盛嘿,網(wǎng)絡(luò)同步加載等洛巢。
用法:

  • 創(chuàng)建信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);

信號(hào)量的數(shù)量初始化,最好就是使用要執(zhí)行任務(wù)的多少來定孩擂,如題是兩次狼渊,則設(shè)置為2。

  • 信號(hào)量-1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

每次執(zhí)行一個(gè)任務(wù)类垦,調(diào)用方法dispatch_semaphore_wait狈邑,讓信號(hào)量減1
如果信號(hào)量的值>0,擇讓信號(hào)量的值減1蚤认,然后繼續(xù)執(zhí)行下面的代碼米苹;
如果信號(hào)量的值<=0,就會(huì)等待休眠砰琢,直到信號(hào)量的值變成>0蘸嘶,擇繼續(xù)減1,然后執(zhí)行下面的代碼

  • 信號(hào)量+1
dispatch_semaphore_signal(semaphore);

每次結(jié)束一個(gè)任務(wù)陪汽,調(diào)用方法dispatch_semaphore_signal训唱,讓信號(hào)量+1

注意:
也有一些用法是設(shè)置信號(hào)量為0的,進(jìn)入任務(wù)的時(shí)候signal挚冤,離開任務(wù)的時(shí)候wait况增。

8、@synchronized

實(shí)際項(xiàng)目中:AFNetworking中 isNetworkActivityOccurring屬性的getter方法

- (BOOL)isNetworkActivityOccurring {
    @synchronized(self) {
        return self.activityCount > 0;
    }
}

各種鎖的性能對(duì)比


image.png

自旋鎖與互斥鎖的比較


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末训挡,一起剝皮案震驚了整個(gè)濱河市澳骤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌澜薄,老刑警劉巖谆甜,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砍濒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡废睦,警方通過查閱死者的電腦和手機(jī)朵夏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門丈咐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缸剪,“玉大人蛾洛,你說我怎么就攤上這事》古樱” “怎么了戒悠?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵熬荆,是天一觀的道長(zhǎng)舟山。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么累盗? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任寒矿,我火速辦了婚禮,結(jié)果婚禮上若债,老公的妹妹穿的比我還像新娘符相。我一直安慰自己,他們只是感情好蠢琳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布啊终。 她就那樣靜靜地躺著,像睡著了一般傲须。 火紅的嫁衣襯著肌膚如雪蓝牲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天泰讽,我揣著相機(jī)與錄音例衍,去河邊找鬼。 笑死已卸,一個(gè)胖子當(dāng)著我的面吹牛佛玄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播累澡,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼梦抢,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了永乌?” 一聲冷哼從身側(cè)響起惑申,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎翅雏,沒想到半個(gè)月后圈驼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡望几,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年绩脆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橄抹。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡靴迫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出楼誓,到底是詐尸還是另有隱情玉锌,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布疟羹,位于F島的核電站主守,受9級(jí)特大地震影響禀倔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜参淫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一救湖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涎才,春花似錦鞋既、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至棕兼,卻和暖如春检吆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背程储。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工蹭沛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人章鲤。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓摊灭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親败徊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帚呼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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