底層原理(八)------多線程

iOS中常見(jiàn)的多線程方案

控制器命令:
c:過(guò)掉
step:輸出匯編
stepi:進(jìn)入這個(gè)匯編函數(shù) 簡(jiǎn)稱si

  • NSThread、GCD、NSOperation的底層實(shí)現(xiàn)其實(shí)都是pthread

GCD的常用函數(shù)

GCD的隊(duì)列

容易混淆的術(shù)語(yǔ):同步、異步,并發(fā)、串行

各種隊(duì)列的執(zhí)行效果

// dispatch_sync和dispatch_async用來(lái)控制是否要開(kāi)啟新的線程

/**
 隊(duì)列的類型,決定了任務(wù)的執(zhí)行方式(并發(fā)、串行)
 1.并發(fā)隊(duì)列
 2.串行隊(duì)列
 3.主隊(duì)列(也是一個(gè)串行隊(duì)列)
 */

面試題

1、下列問(wèn)題是否會(huì)產(chǎn)生死鎖

- (void)interview01
{
    // 問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?會(huì)!
    NSLog(@"執(zhí)行任務(wù)1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"執(zhí)行任務(wù)2");
    });
    
    NSLog(@"執(zhí)行任務(wù)3");
    
    // dispatch_sync立馬在當(dāng)前線程同步執(zhí)行任務(wù)
}

- (void)interview02
{
    // 問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?不會(huì)!
    NSLog(@"執(zhí)行任務(wù)1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"執(zhí)行任務(wù)2");
    });
    
    NSLog(@"執(zhí)行任務(wù)3");
    
    // dispatch_async不要求立馬在當(dāng)前線程同步執(zhí)行任務(wù)
    // 打印順序:任務(wù)1-3-2
}

- (void)interview03
{
    // 問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?會(huì)!
    NSLog(@"執(zhí)行任務(wù)1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ // 0
        NSLog(@"執(zhí)行任務(wù)2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"執(zhí)行任務(wù)3");
        });
    
        NSLog(@"執(zhí)行任務(wù)4");
    });
    
    NSLog(@"執(zhí)行任務(wù)5");

    // block0等block1執(zhí)行完畢,block1等block0執(zhí)行完畢
    // 打印順序:任務(wù)1-5-2-死鎖
}

- (void)interview04
{
    // 問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?不會(huì)!
    NSLog(@"執(zhí)行任務(wù)1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
//    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{ // 0
        NSLog(@"執(zhí)行任務(wù)2");
        
        dispatch_sync(queue2, ^{ // 1
            NSLog(@"執(zhí)行任務(wù)3");
        });
        
        NSLog(@"執(zhí)行任務(wù)4");
    });
    
    NSLog(@"執(zhí)行任務(wù)5");
    // 打印順序:任務(wù)1-5-2-3-4
}

- (void)interview05
{
    // 問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?不會(huì)!
    NSLog(@"執(zhí)行任務(wù)1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{ // 0
        NSLog(@"執(zhí)行任務(wù)2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"執(zhí)行任務(wù)3");
        });
        
        NSLog(@"執(zhí)行任務(wù)4");
    });
    
    NSLog(@"執(zhí)行任務(wù)5");
}
  • 隊(duì)列的特點(diǎn):排隊(duì),F(xiàn)IFO,F(xiàn)irst In First Out,先進(jìn)先出
  • dispatch_sync:立馬在當(dāng)前線程執(zhí)行任務(wù),執(zhí)行完成才能繼續(xù)往下執(zhí)行
  • dispatch_async不要求立馬在當(dāng)前線程同步執(zhí)行任務(wù)

2、下面代碼的打印結(jié)果是什么?


// 這句代碼的本質(zhì)是往Runloop中添加定時(shí)器
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
// 這句代碼本質(zhì)就是Runtime調(diào)用objc_msgSend
[self performSelector:@selector(test) withObject:nil];

3、下面打印結(jié)果是什么?

- (void)test
{
    NSLog(@"2");
}

// 打印結(jié)果為1,因?yàn)閇thread start]啟動(dòng)會(huì)運(yùn)行當(dāng)前block,輸出結(jié)果為1,但是輸出以后,線程也就自動(dòng)退出了,也就是沒(méi)有子線程了,所以thread=nil,如果想繼續(xù)執(zhí)行的話 在block里面啟動(dòng)RunLoop。
//        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");
//        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [thread start];
    
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

GNUstep

  • GNUstep是GNU計(jì)劃的項(xiàng)目之一,它將Cocoa的OC庫(kù)重新開(kāi)源實(shí)現(xiàn)了一遍
  • 源碼地址:http://www.gnustep.org/resources/downloads.php
  • 雖然GNUstep不是蘋(píng)果官方源碼,但還是具有一定的參考價(jià)值

隊(duì)列組的使用

思考:如何用gcd實(shí)現(xiàn)以下功能
異步并發(fā)執(zhí)行任務(wù)1、任務(wù)2
等任務(wù)1、任務(wù)2都執(zhí)行完畢后,再回到主線程執(zhí)行任務(wù)3

// 創(chuàng)建隊(duì)列組
    dispatch_group_t group = dispatch_group_create();
    // 創(chuàng)建并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 添加異步任務(wù)
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)2-%@", [NSThread currentThread]);
        }
    });
    
    // 等前面的任務(wù)執(zhí)行完畢后,會(huì)自動(dòng)執(zhí)行這個(gè)任務(wù)
//    dispatch_group_notify(group, queue, ^{
//        dispatch_async(dispatch_get_main_queue(), ^{
//            for (int i = 0; i < 5; i++) {
//                NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
//            }
//        });
//    });
    
    //也可以直接把主隊(duì)列放進(jìn)去
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        for (int i = 0; i < 5; i++) {
//            NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
//        }
//    });
    
    // 也可以執(zhí)行完任務(wù)1、2 再去執(zhí)行任務(wù)3、4
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)4-%@", [NSThread currentThread]);
        }
    });

多線程安全隱患

多線程安全隱患示例01-存錢(qián)取錢(qián)

存錢(qián)取錢(qián)代碼演示

@property (assign, nonatomic) int money;


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self moneyTest];
}

/**
 存錢(qián)、取錢(qián)演示
 */
- (void)moneyTest
{
    self.money = 100;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self saveMoney];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self drawMoney];
        }
    });
}

/**
 存錢(qián)
 */
- (void)saveMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 50;
    self.money = oldMoney;
    
    NSLog(@"存50,還剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

/**
 取錢(qián)
 */
- (void)drawMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    
    NSLog(@"取20,還剩%d元 - %@", oldMoney, [NSThread currentThread]);
}
多線程安全隱患示例02-賣票

賣票代碼演示

/**
 賣1張票
 */
@property (assign, nonatomic) int ticketsCount;

- (void)saleTicket
{
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    
    NSLog(@"還剩%d張票 - %@", oldTicketsCount, [NSThread currentThread]);
}

/**
 賣票演示
 */
- (void)ticketTest
{
    self.ticketsCount = 15;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
             [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self ticketTest];
}
多線程安全隱患分析
多線程安全隱患解決方案

iOS 中的線程同步方案

  • OSSpinLock
  • os_unfair_lock
  • pthread_mutex
  • dispatch_semaphore
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditionLock
  • @synchronized
OSSpinLock

os_unfair_lock

pthread_mutex 普通鎖

pthread_mutex--遞歸鎖

遞歸鎖:允許同一個(gè)線程對(duì)一把鎖進(jìn)行重復(fù)加鎖


pthread_mutex--條件鎖

NSLock、NSRecursiveLock

NSCodition

NSConditionLock

dispatch_queue

dispatch_semaphore

@synchronized

所有示例代碼Demo

iOS線程同步方案性能比較

GCD、OperationQueue 對(duì)比

GCD的核心概念:將 任務(wù)(block) 添加到隊(duì)列,并且指定執(zhí)行任務(wù)的函數(shù)。
NSOperation 的核心概念:把 操作(異步) 添加到 隊(duì)列。

區(qū)別
GCD:
將任務(wù)(block)添加到隊(duì)列(串行/并發(fā)/主隊(duì)列),并且指定任務(wù)執(zhí)行的函數(shù)(同步/異步)
GCD是底層的C語(yǔ)言構(gòu)成的API
iOS 4.0 推出的,針對(duì)多核處理器的并發(fā)技術(shù)
在隊(duì)列中執(zhí)行的是由 block 構(gòu)成的任務(wù),這是一個(gè)輕量級(jí)的數(shù)據(jù)結(jié)構(gòu)
要停止已經(jīng)加入 queue 的 block 需要寫(xiě)復(fù)雜的代碼
需要通過(guò) Barrier(dispatch_barrier_async)或者同步任務(wù)設(shè)置任務(wù)之間的依賴關(guān)系
只能設(shè)置隊(duì)列的優(yōu)先級(jí)
高級(jí)功能: dispatch_once_t(一次性執(zhí)行, 多線程安全); dispatch_after(延遲); dispatch_group(調(diào)度組); dispatch_semaphore(信號(hào)量); dispatch_apply(優(yōu)化順序不敏感大體量for循環(huán));

OperationQueue:
OC 框架,更加面向?qū)ο?,是?duì) GCD 的封裝。
iOS 2.0 推出的,蘋(píng)果推出 GCD 之后,對(duì) NSOperation 的底層進(jìn)行了全部重寫(xiě)。
可以設(shè)置隊(duì)列中每一個(gè)操作的 QOS() 隊(duì)列的整體 QOS
操作相關(guān) Operation作為一個(gè)對(duì)象,為我們提供了更多的選擇: 任務(wù)依賴(addDependency),可以跨隊(duì)列設(shè)置操作的依賴關(guān)系; 在隊(duì)列中的優(yōu)先級(jí)(queuePriority) 服務(wù)質(zhì)量(qualityOfService, iOS8+); 完成回調(diào)(void (^completionBlock)(void)
隊(duì)列相關(guān) 服務(wù)質(zhì)量(qualityOfService, iOS8+); 最大并發(fā)操作數(shù)(maxConcurrentOperationCount),GCD 不易實(shí)現(xiàn); 暫停/繼續(xù)(suspended); 取消所有操作(cancelAllOperations); KVO 監(jiān)聽(tīng)隊(duì)列任務(wù)執(zhí)行進(jìn)度(progress, iOS13+);

自旋鎖跟互斥鎖比較

atomic

/*
 nonatomic和atomic
 atom:原子,不可再分割的單位
 atomic:原子性
 
 給屬性加上atomic修飾,可以保證屬性的setter和getter都是原子性操作,也就是保證setter和gette內(nèi)部是線程同步的
 
 atomic跟nonatomic 在取值跟設(shè)值的時(shí)候不同,atomic取值設(shè)值會(huì)加鎖(自旋鎖)
atomic經(jīng)常在Mac使用,太耗內(nèi)存
 */

iOS中的讀寫(xiě)安全方案

pthread_rwlock:讀寫(xiě)鎖

dispatch_barrier_async:異步柵欄調(diào)用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 多線程面試題 你理解的多線程?iOS的多線程方案有哪幾種?你更傾向于哪一種?你在項(xiàng)目中用過(guò) GCD 嗎?GCD 的...
    _曾夢(mèng)想仗劍走天涯閱讀 1,090評(píng)論 0 4
  • iOS中的常見(jiàn)多線程方案 GCD的常用函數(shù) GCD中有2個(gè)用來(lái)執(zhí)行任務(wù)的函數(shù) 用同步的方式執(zhí)行任務(wù)dispatch...
    Stago閱讀 151評(píng)論 0 0
  • 一、基礎(chǔ)概念 有4個(gè)術(shù)語(yǔ)比較容易混淆:同步、異步、并發(fā)、串行 1.進(jìn)程和線程 進(jìn)程:進(jìn)程是計(jì)算機(jī)中已運(yùn)行程序的實(shí)體...
    666真666閱讀 1,355評(píng)論 0 7
  • 一、GCD iOS中常見(jiàn)的多線程方案有:pthread、NSThread、GCD、NSOperation,我們用的...
    冰風(fēng)v落葉閱讀 648評(píng)論 0 1
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了,難過(guò)就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,412評(píng)論 2 7

友情鏈接更多精彩內(nèi)容