關(guān)于iOS多線程淺析

多線程(英語(yǔ):multithreading),是指從軟件或者硬件上實(shí)現(xiàn)多個(gè)線程并發(fā)執(zhí)行的技術(shù)。具有多線程能力的計(jì)算機(jī)因有硬件支持而能夠在同一時(shí)間執(zhí)行多于一個(gè)執(zhí)行緒,進(jìn)而提升整體處理性能。 —— 維基百科

  • 多線程概念
    • 概念
    • 例子
    • 目的
    • 優(yōu)缺點(diǎn)
  • 多線程的同步異步
    • 同步
    • 異步
  • 多線程的線程進(jìn)程
    • 線程
    • 進(jìn)程
  • 多線程的方式
    • PThread
    • NSThread
    • GCD
      • GCD的概念
      • GCD的簡(jiǎn)單使用
      • GCD的任務(wù)和隊(duì)列
    • NSOPeration
      • NSOperation的簡(jiǎn)介
      • NSOperation的簡(jiǎn)單使用
      • NSOperation的高級(jí)功能
  • 面試題

多線程的概念

  • 概念

    • 多線程:一個(gè)進(jìn)程中可以開(kāi)啟多條線程,多條線程可以同時(shí)執(zhí)行不同的任務(wù)。
  • 例子

    • 舉個(gè)例子:酷我音樂(lè)的邊下載邊聽(tīng)歌,迅雷的邊下載邊播放。
  • 多線程目的

    • 將耗時(shí)操作放在后臺(tái)處理,保證UI界面的正常顯示和交互。
    • 網(wǎng)絡(luò)操作是非常耗時(shí)的,在做網(wǎng)絡(luò)開(kāi)發(fā),所有網(wǎng)絡(luò)訪問(wèn)都是耗時(shí)操作.需要在后臺(tái)線程中執(zhí)行。
    • 多線程開(kāi)發(fā)的原則:越簡(jiǎn)單越好。
  • 多線程優(yōu)缺點(diǎn)

    • 優(yōu)點(diǎn):能‘適當(dāng)’提高程序的執(zhí)行效率,能適當(dāng)提高CPU的內(nèi)存利用率,線程上的任務(wù)執(zhí)行完成后,線程會(huì)自動(dòng)銷(xiāo)毀節(jié)省內(nèi)存。
    • 缺點(diǎn):如果開(kāi)啟線程過(guò)多會(huì)占用大量CPU資源降低程序性能。

同步異步(同步和異步是兩種執(zhí)行任務(wù)的方式。)

  • 同步

    • 同步:代碼從上到下順序執(zhí)行就叫做同步執(zhí)行(多個(gè)任務(wù)依次執(zhí)行)。
  • 異步

    • 異步:多個(gè)任務(wù)同時(shí)執(zhí)行就是異步執(zhí)行。

多線程的線程進(jìn)程

  • 進(jìn)程

    • 進(jìn)程:在系統(tǒng)中正在運(yùn)行的一個(gè)程序叫做進(jìn)程,進(jìn)程可以類比成正在正常運(yùn)營(yíng)的公司。
  • 線程

    • 線程:線程可以類比成公司里的員工,程序啟動(dòng)默認(rèn)會(huì)開(kāi)啟一個(gè)線程。

多線程的方式

  • PThread(幾乎不用)

    • Pthreads定義了一套C語(yǔ)言的類型、函數(shù)與常量,它以pthread.h頭文件和一個(gè)線程庫(kù)實(shí)現(xiàn)。
  • NSThread(很少使用)

    • NSThread是基于線程使用,輕量級(jí)的多線程編程方法(相對(duì)GCD和NSOperation),一個(gè)NSThread對(duì)象代表一個(gè)線程,需要手動(dòng)管理線程的生命周期,處理線程同步等問(wèn)題。
  • GCD(經(jīng)常使用)

    • GCD的概念
      • 什么是GCD:全稱是Grand Central Dispatch,純C語(yǔ)言的,提供了非常多強(qiáng)大的函數(shù)。
      • GCD的核心:將任務(wù)添加到隊(duì)列。
      • GCD使用的兩個(gè)步驟:創(chuàng)建任務(wù),確定要做的事情,GCD中的任務(wù)是使用BLOCK封裝的。將任務(wù)添加到隊(duì)列中,GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出,放到對(duì)應(yīng)的線程中執(zhí)行。任務(wù)的取出遵循隊(duì)列的FIFO原則 : 先進(jìn)先出,后進(jìn)后出
    • GCD的簡(jiǎn)單使用
      • 任務(wù)添加到隊(duì)列
        - (void)GCDDemo1 {
            // 1. 創(chuàng)建隊(duì)列
            dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
            // 2. 創(chuàng)建任務(wù) : 用block指定的 (無(wú)參無(wú)返回值的)
            void (^task)() = ^ {
                NSLog(@"%@",[NSThread currentThread]);
            };
            // 3. 把任務(wù)添加到隊(duì)列
            // dispatch_async : 表示任務(wù)是異步的
            // dispatch_sync : 表示任務(wù)是同步的
            dispatch_async(queue, task);
        }   
        
        
      • 簡(jiǎn)寫(xiě)
            - (void)GCDDemo2 {
                dispatch_async(dispatch_get_global_queue(0, 0), ^{
                NSLog(@"%@",[NSThread currentThread]);
                });
            }
        
        
      • 線程間的通信
            - (void)GCDDemo4 {
                dispatch_async(dispatch_get_global_queue(0, 0), ^{
                    NSLog(@"假裝在努力下載...%@",[NSThread currentThread]);
                    // 下載結(jié)束之后,回到主線程更新UI
                    dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@"假裝在更新UI...%@",[NSThread currentThread]);
                    });
                });
            }
        
        
      • 使用GCD的線程間的通信實(shí)現(xiàn)異步下載網(wǎng)絡(luò)圖片
- (void)downloadImage
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSLog(@"downloadImage %@",[NSThread currentThread]);
        
        // URL
        NSURL *URL = [NSURL URLWithString:@"http://atth.eduu.com/album/201203/12/1475134_1331559643qMzc.jpg"];
        // data
        NSData *data = [NSData dataWithContentsOfURL:URL];
        // image
        UIImage *image = [UIImage imageWithData:data];
        
        // 拿到圖片對(duì)象之后,回到主線程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            
            NSLog(@"updateUI %@",[NSThread currentThread]);
            
            self.myImageView.image = image;
            [self.myImageView sizeToFit];
            [self.myScrollView setContentSize:image.size];
        });
    });
}

  • GCD的任務(wù)和隊(duì)列
    • GCD的任務(wù):
      • 同步的方式執(zhí)行任務(wù) : 在當(dāng)前線程中依次執(zhí)行任務(wù)。 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
      • 異步的方式執(zhí)行任務(wù) : 新開(kāi)線程在新線程中執(zhí)行任務(wù)。
        dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    • GCD隊(duì)列:
      • 串行隊(duì)列:讓任務(wù)一個(gè)接著一個(gè)有序的執(zhí)行:不管隊(duì)列里面放的是什么任務(wù),一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù),同時(shí)只能調(diào)度一個(gè)任務(wù)執(zhí)行。
      • 并發(fā)隊(duì)列:可以讓多個(gè)任務(wù)并發(fā)/同時(shí)執(zhí)行,自動(dòng)開(kāi)啟多個(gè)線程同時(shí)執(zhí)行多個(gè)任務(wù),同時(shí)可以調(diào)度多個(gè)任務(wù)執(zhí)行。并發(fā)隊(duì)列的并發(fā)功能只有內(nèi)部的任務(wù)是異步任務(wù)時(shí),才有效。
  • 代碼小結(jié)

串行隊(duì)列+同步任務(wù)

/*
 1.不開(kāi)線程
 2.有序執(zhí)行
*/
- (void)GCDDemo1
{
    /* 
     創(chuàng)建串行隊(duì)列
     參數(shù)1 : 隊(duì)列的標(biāo)識(shí)符
     參數(shù)2 :  隊(duì)列的屬性,決定了隊(duì)列是串行的還是并行的
     DISPATCH_QUEUE_SERIAL : 串行隊(duì)列
    */
    dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
    
    // 循環(huán)的創(chuàng)建了10個(gè)同步任務(wù),添加到隊(duì)列
    for (NSInteger i = 0; i<10; i++) {
        
        // 把同步任務(wù)添加到串行隊(duì)列
        dispatch_sync(queue, ^{
            NSLog(@"%zd %@",i,[NSThread currentThread]);
        });
    }
    
    NSLog(@"哈哈哈");
}

串行隊(duì)列+異步任務(wù)

- (void)GCDDemo2
{
    // 串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
    
    for (NSInteger i = 0; i<10; i++) {
        // 把異步任務(wù)添加到串行隊(duì)列
        dispatch_async(queue, ^{
            NSLog(@"%zd %@",i,[NSThread currentThread]);
        });
    }
    
    NSLog(@"嘿嘿嘿");
}

并行隊(duì)列+同步任務(wù)

/*
 不開(kāi)線程
 有序執(zhí)行
 */
- (void)GCDDemo1
{
    // 創(chuàng)建并行隊(duì)列
    // DISPATCH_QUEUE_CONCURRENT : 并行隊(duì)列
    // 并行隊(duì)列只能決定"是否"可以同時(shí)調(diào)度多個(gè)任務(wù);不能決定開(kāi)不開(kāi)線程
    dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
    
    for (NSInteger i = 0; i<10; i++) {
        // 把同步任務(wù)添加到并行隊(duì)列
        dispatch_sync(queue, ^{
            NSLog(@"%zd %@",i,[NSThread currentThread]);
        });
    }
    
    NSLog(@"哈哈哈");
}

并行隊(duì)列+異步任務(wù)

/*
 開(kāi)線程
 無(wú)序執(zhí)行
 */
- (void)GCDDemo2
{
    // 并行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
    
    for (NSInteger i = 0; i<10; i++) {
        
        // 把異步任務(wù)添加到并發(fā)隊(duì)列
        dispatch_async(queue, ^{
            NSLog(@"%zd %@",i,[NSThread currentThread]);
        });
    }
    
    NSLog(@"嘿嘿嘿");
}
Paste_Image.png
  • NSOperation(經(jīng)常使用)

    • NSOperation的簡(jiǎn)介
      • 是OC語(yǔ)言中基于GCD的面向?qū)ο蟮姆庋b,使用起來(lái)比GCD更加簡(jiǎn)單。提供了一些GCD不好實(shí)現(xiàn)的功能,蘋(píng)果推薦使用。NSOperation還不用關(guān)心線程和線程的聲明周期。
      • NSOperation是個(gè)抽象類無(wú)法直接使用。因?yàn)榉椒ㄖ挥新暶鳑](méi)有實(shí)現(xiàn)。
      • 子類:NSInvocationOperation和NSBlockOperation,自定義NSOperation操作默是異步的。
      • 隊(duì)列 : NSOperationQueue隊(duì)列默認(rèn)是并發(fā)的。
      • 核心:GCD的核心 : 將任務(wù)添加到隊(duì)列中。OP的核心 : 將操作添加到隊(duì)列中。
    • NSOperation的簡(jiǎn)單使用
      • 先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對(duì)象中,創(chuàng)建NSOperation對(duì)象。
      • 將NSOperation對(duì)象添加到NSOperationQueue中。
      • NSOperationQueue會(huì)自動(dòng)將NSOperation取出來(lái)
      • 將取出的NSOperation封裝的操作自動(dòng)放到一條對(duì)應(yīng)的新線程中執(zhí)行。
    • NSOperation的高級(jí)功能
      • 最大并發(fā)數(shù)

設(shè)置最大并發(fā)數(shù)

// 隊(duì)列的最大并發(fā)數(shù)的屬性
// 作用 : 控制隊(duì)列同時(shí)調(diào)度任務(wù)執(zhí)行的個(gè)數(shù);
// 間接控制了線程的數(shù)量;
// 注意 : 隊(duì)列的最大并發(fā)數(shù),不是線程數(shù);

@implementation ViewController {
    
    /// 全局隊(duì)列
    NSOperationQueue *_queue;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _queue = [[NSOperationQueue alloc] init];
    
    // 設(shè)置隊(duì)列的最大并發(fā)數(shù) : 至少開(kāi)兩個(gè)
    _queue.maxConcurrentOperationCount = 2;
}

演示

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self GCDDemo];
}

- (void)GCDDemo
{
    for (NSInteger i = 0; i<50; i++) {
        
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%zd %@",i,[NSThread currentThread]);
        }];
        
        [_queue addOperation:op];
    }
}

執(zhí)行結(jié)果:任務(wù)是兩個(gè)兩個(gè)的執(zhí)行。

繼續(xù)、暫停、取消全部
  • 在最大并發(fā)數(shù)代碼的基礎(chǔ)上增加暫停、繼續(xù)、取消。
#pragma 取消全部
/*
 1.正在執(zhí)行的操作無(wú)法被取消;
 2.如果非要取消正在執(zhí)行的操作,需要自定義NSOperation
 3.這個(gè)取消全部的操作有一定的時(shí)間延遲
 */
- (IBAction)cancelAll:(id)sender
{
    // 移除隊(duì)列里面"所有"的操作
    [_queue cancelAllOperations];
    
    NSLog(@"取消全部 %tu",_queue.operationCount);
}

#pragma 繼續(xù)
- (IBAction)jixu:(id)sender
{
    // 不掛起隊(duì)列,使隊(duì)列繼續(xù)調(diào)度任務(wù)執(zhí)行
    _queue.suspended = NO;
    
    NSLog(@"繼續(xù) %tu",_queue.operationCount);
}

#pragma 暫停
/*
 1.正在執(zhí)行的操作無(wú)法被暫停
 2.operationCount : 隊(duì)列里面的操作個(gè)數(shù);統(tǒng)計(jì)的是隊(duì)列里面還沒(méi)有執(zhí)行完的操作;
 3.隊(duì)列里面的任務(wù)一旦執(zhí)行完,會(huì)從隊(duì)列里面移除;
 */
- (IBAction)zanting:(id)sender
{
    // 掛起隊(duì)列,使隊(duì)列暫停調(diào)度任務(wù)執(zhí)行
    _queue.suspended = YES;
    
    NSLog(@"暫停 %tu",_queue.operationCount);
}
  • 暫停隊(duì)列結(jié)論

將隊(duì)列掛起之后,隊(duì)列中的操作就不會(huì)被調(diào)度,但是正在執(zhí)行的操作不受影響。operationCount:操作計(jì)數(shù),沒(méi)有執(zhí)行和沒(méi)有執(zhí)行完的操作,都會(huì)計(jì)算在操作計(jì)數(shù)之內(nèi)。注意:如果先暫停隊(duì)列,再添加操作到隊(duì)列,隊(duì)列不會(huì)調(diào)度操作執(zhí)行。所以在暫停隊(duì)列之前要判斷隊(duì)列中有沒(méi)有任務(wù),如果沒(méi)有操作就不暫停隊(duì)列。

  • 取消隊(duì)列結(jié)論

一旦調(diào)用的 cancelAllOperations方法,隊(duì)列中的操作,都會(huì)被移除,正在執(zhí)行的操作除外。 正在執(zhí)行的操作取消不了,如果要取消,需要自定義NSOperation。 隊(duì)列取消全部操作時(shí),會(huì)有一定的時(shí)間延遲。

  • 操作間依賴關(guān)系。

場(chǎng)景:登陸-->付費(fèi)-->下載-->通知用戶

準(zhǔn)備需要執(zhí)行的操作

    // 登錄
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"登錄 %@",[NSThread currentThread]);
    }];
    
    // 付費(fèi)
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"付費(fèi) %@",[NSThread currentThread]);
    }];
    
    // 下載
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載 %@",[NSThread currentThread]);
    }];
    
    // 通知用戶
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"通知用戶 %@",[NSThread currentThread]);
    }];

添加依賴(核心代碼)

    /*
     添加依賴關(guān)系
     1.不能在操作添加到隊(duì)列之后,在建立依賴關(guān)系;因?yàn)橐呀?jīng)晚了
     2.可以跨隊(duì)列建立依賴關(guān)系
     3.不能建立循環(huán)依賴
     */
    [op2 addDependency:op1]; // 付費(fèi)依賴登錄
    [op3 addDependency:op2]; // 下載依賴付費(fèi)
    [op4 addDependency:op3]; // 通知用戶依賴下載
    
    // [op1 addDependency:op4]; // 登錄依賴通知用戶 : 循環(huán)依賴;會(huì)卡死
    
    // 批量把操作添加到隊(duì)列
    // waitUntilFinished : 是否等待前面的異步任務(wù)執(zhí)行完,在執(zhí)行后面的代碼
    [_queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];
    
    // 一個(gè)操作不能同時(shí)添加到兩個(gè)隊(duì)列
    [[NSOperationQueue mainQueue] addOperation:op4];
  • 結(jié)論

    不能循環(huán)建立操作間依賴關(guān)系,否則隊(duì)列不調(diào)度操作執(zhí)行。

    操作間可以跨隊(duì)列建立依賴關(guān)系。

    要將操作間的依賴建立好了之后,再添加到隊(duì)列中,先建立操作依賴關(guān)系,再把操作添加到隊(duì)列。

面試題

  • 面試題僅供參考

    用 NSOPeration 和 NSOpertionQueue 處理 A,B,C 三個(gè)線程,要求執(zhí)行完 A,B 后才能執(zhí)行 C, 怎么做?

    添加操作依賴,C依賴于A同時(shí)依賴于B。創(chuàng)建操作隊(duì)列,將操作添加到操作隊(duì)列中。

    線程間怎么通信?

    什么是線程通信:不同線程之間傳遞數(shù)據(jù),一般線程傳遞到主線程。 iOS中開(kāi)啟多線程的方式:三種。比如:在子線程下載圖片,然后回到主線程顯示圖片。

    簡(jiǎn)述多線程的作用以及什么地方會(huì)用到多線程?OC實(shí)現(xiàn)多線程的方法有哪些?談?wù)劧嗑€程安全問(wèn)題的幾種解決方案?何為線程同步,如何實(shí)現(xiàn)的?分線程回調(diào)主線程方法是什么,有什么作用?

    1. 耗時(shí)操作、界面卡死的時(shí)候使用多線程

    2. 作用:可以同時(shí)執(zhí)行多個(gè)任務(wù),適當(dāng)提高程序的執(zhí)行效率。為了提高CPU的使用率,采用多線程的方式去同時(shí)完成幾件事互不干擾。

    3. iOS中多線程的方法:NSThread、NSOperation、GCD、pthread

    4. 使用場(chǎng)景:同時(shí)上傳和下載多個(gè)文件:加載網(wǎng)絡(luò)數(shù)據(jù)同時(shí)展示Loading的UI、大量數(shù)據(jù)I/O操作。

    5. 資源共享造成的安全問(wèn)題:多線程環(huán)境下,當(dāng)多個(gè)線程同時(shí)操作共享資源的setter和getter方法時(shí),會(huì)造成數(shù)據(jù)的讀寫(xiě)錯(cuò)亂就是線程安全問(wèn)題。

    6. 線程同步技術(shù):使多個(gè)線程一次有序的執(zhí)行,實(shí)現(xiàn)方案是加鎖,把共享資源的讀寫(xiě)操作鎖起來(lái)常用的是互斥鎖。

    7. 線程間的通信:一個(gè)線程執(zhí)行完任務(wù)之后,把執(zhí)行的結(jié)果傳遞到另外一個(gè)線程叫線程間通信。線程間通信可用來(lái)在兩個(gè)線程間傳遞數(shù)據(jù)。


感謝讀到最后的朋友,最后請(qǐng)點(diǎn)贊支持一下,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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