線程的總結

線程的總結

內容是:

談iOS多線程(NSThread、NSOperation、GCD)編程

?
Travis
Travis

GitHub release
GitHub release

![Github All Releases](https://img.shields.io/badge/download-6M Total-green.svg)

文章配圖
文章配圖

一周六早上,小明處于安全考慮,去銀行服務廳申請多一張銀行卡作為手機消費指定數額不多的專用卡。到了銀行,看到大廳坐滿了人,唱K的唱K,念經的念經,嘔奶的嘔奶,彼起此伏,聲聲入耳,直趕清華大學演奏團演奏的《小蘋果》,呀~!其實真實的情況是:每個人都做著椅子上低下頭盯著各自的手機,小明也不例外,找了個角落,瀏覽起3016年的新聞。半個小時過去了,40分鐘過去了,一個小時過去!小明等怒了,大喊“嘿嘿嘿,開多一條線程不可以嗎!!!”

“什么是多一條線程啊?”
文章大綱
文章大綱

一.基本概念

計算機操作系統都有的基本概念,以下概念簡單方式來描述。

  1. 進程: 一個具有一定獨立功能的程序關于某個數據集合的一次運行活動。可以理解成一個運行中的應用程序。
  2. 線程: 程序執行流的最小單元,線程是進程中的一個實體。
  3. 同步: 只能在當前線程按先后順序依次執行,不開啟新線程。
  4. 異步: 可以在當前線程開啟多個新線程執行,可不按順序執行。
  5. 隊列: 裝載線程任務的隊形結構。
  6. 并發: 線程執行可以同時一起進行執行。
  7. 串行: 線程執行只能依次逐一先后有序的執行。

注意:

  • 一個進程可有多個線程。
  • 一個進程可有多個隊列。
  • 隊列可分并發隊列和串行隊列。

二.iOS多線程對比

1. NSThread

每個NSThread對象對應一個線程,真正最原始的線程。
1)優點:NSThread 輕量級最低,相對簡單。
2)缺點:手動管理所有的線程活動,如生命周期、線程同步、睡眠等。

2. NSOperation

自帶線程管理的抽象類。
1)優點:自帶線程周期管理,操作上可更注重自己邏輯。
2)缺點:面向對象的抽象類,只能實現它或者使用它定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation。

3. GCD

Grand Central Dispatch (GCD)是Apple開發的一個多核編程的解決方法。
1)優點:最高效,避開并發陷阱。
2)缺點:基于C實現。

5. 選擇小結

1)簡單而安全的選擇NSOperation實現多線程即可。
2)處理大量并發數據,又追求性能效率的選擇GCD。
3)NSThread本人選擇基本上是在做些小測試上使用,當然也可以基于此造個輪子。

三.場景選擇

  1. 圖片異步加載。這種常見的場景是最常見也是必不可少的。異步加載圖片有分成兩種來說明一下。
    第一種,在UI主線程開啟新線程按順序加載圖片,加載完成刷新UI。
    第二種,依然是在主線程開啟新線程,順序不定地加載圖片,加載完成個字刷新UI。
  2. 創作工具上的異步。 這個跟上邊任務調度道理,只是為了豐富描述,有助于“舉一反三”效果。如下描述的是app創作小說。
    場景一,app本地創作10個章節內容未成同步服務器,同時發表這10個章節產生的一系列動作,其中上傳內容,獲取分配章節Id,如何后臺沒有做處理最好方式做異步按順序執行。
    場景二,app本地創作列表中有3本小說為發表,同時發表創作列表中的3本小說,自然考慮并行隊列執行發表。

四.使用方法

第三標題場景選擇內容實現先留下一個懸念。具體實現還是先熟知一下各自的API先。

1. NSThread

1.1)三種實現開啟線程方式:
①.動態實例化

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
thread.threadPriority = 1;// 設置線程的優先級(0.0 - 1.0,1.0最高級)
[thread start];  

②.靜態實例化

[NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl];   

③.隱式實例化

[self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];  

有了以上的知識點,可以試探了一下編寫場景選擇中的“圖片加載”的基本功能了。

1.2)使用這三種方式編寫代碼

//動態創建線程
-(void)dynamicCreateThread{

    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
    thread.threadPriority = 1;// 設置線程的優先級(0.0 - 1.0,1.0最高級)
    [thread start];
}

//靜態創建線程
-(void)staticCreateThread{

    [NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl];

}

//隱式創建線程
-(void)implicitCreateThread{

    [self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];
}

-(void)loadImageSource:(NSString *)url{
    NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
    UIImage *image = [UIImage imageWithData:imgData];
    if (imgData!=nil) {
        [self performSelectorOnMainThread:@selector(refreshImageView:) withObject:image waitUntilDone:YES];
    }else{
        NSLog(@"there no image data");
    }

}

-(void)refreshImageView:(UIImage *)image{
    [self.imageView setImage:image];
}

1.3)看先效果圖

NSThread多線程加載效果
NSThread多線程加載效果

1.4)NSThread的拓展認識
①獲取當前線程

NSThread *current = [NSThread currentThread];   

②獲取主線程

NSThread *main = [NSThread mainThread];   

③暫停當前線程

[NSThread sleepForTimeInterval:2];  

④線程之間通信

//在指定線程上執行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]; 
//在主線程上執行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 
//在當前線程執行操作
[self performSelector:@selector(run) withObject:nil]; 

顯然動態創建線程多了幾行代碼,其實就是那幾行代碼,如果重復編寫數遍那是一件多么不爽的事情。首次看來靜態方法創作線程和隱式創建線程顯得比較方便,簡潔。從知識結構來說,講到這里應該講述一下線程鎖,鑒于并不常用和文章過長就不在此詳細講述,有興趣可以自行查閱。

2. NSOperation

主要的實現方式:結合NSOperation和NSOperationQueue實現多線程編程。

  • 實例化NSOperation的子類,綁定執行的操作。
  • 創建NSOperationQueue隊列,將NSOperation實例添加進來。
  • 系統會自動將NSOperationQueue隊列中檢測取出和執行NSOperation的操作。

2.1)使用NSOperation的子類實現創作線程。
①.NSInvocationOperation創建線程。

NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
//[invocationOperation start];//直接會在當前線程主線程執行
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:invocationOperation];  

②.NSBlockOperation創建線程

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    [self loadImageSource:imgUrl];
}];

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:blockOperation];  

③.自定義NSOperation子類實現main方法

實現main方法

-(void)main {   
    // Do somthing
} 
  • 創建線程實例并添加到隊列中

    LoadImageOperation *imageOperation = [LoadImageOperation new];
    imageOperation.loadDelegate = self;
    imageOperation.imgUrl = imgUrl;

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:imageOperation];

2.2)使用這三種方式編寫代碼

創建各個實例并添加到隊列表當中

//使用子類NSInvocationOperation
-(void)useInvocationOperation{
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
    //[invocationOperation start];//直接會在當前線程主線程執行
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:invocationOperation];

}

//使用子類NSBlockOperation
-(void)useBlockOperation{

    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        [self loadImageSource:imgUrl];
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:blockOperation];

}
//使用繼承NSOperation
-(void)useSubclassOperation{

    LoadImageOperation *imageOperation = [LoadImageOperation new];
    imageOperation.loadDelegate = self;
    imageOperation.imgUrl = imgUrl;

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:imageOperation];
}

-(void)loadImageSource:(NSString *)url{

    NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
    UIImage *image = [UIImage imageWithData:imgData];
    if (imgData!=nil) {
        [self performSelectorOnMainThread:@selector(refreshImageView1:) withObject:image waitUntilDone:YES];
    }else{
        NSLog(@"there no image data");
    }

}

-(void)refreshImageView1:(UIImage *)image{
    [self.loadingLb setHidden:YES];
    [self.imageView setImage:image];
}

-(void) loadImageFinish:(UIImage *)image{
    [self.loadingLb setHidden:YES];
    [self.imageView setImage:image];
}

附自定義NSOperation子類main主要代碼實現

- (void)main {

    if (self.isCancelled) return;
    
    NSURL *url = [NSURL URLWithString:self.imgUrl];
    NSData *imageData = [NSData dataWithContentsOfURL:url];
            
    if (self.loadDelegate!=nil&&[self.loadDelegate respondsToSelector:@selector(loadImageFinish:)]) {
        
        [(NSObject *)self.loadDelegate performSelectorOnMainThread:@selector(loadImageFinish:) withObject:image waitUntilDone:NO];
    }
}

2.3)看先效果圖

NSOperation多線程加載效果
NSOperation多線程加載效果

3. GCD多線程

GCD是Apple開發,據說高性能的多線程解決方案。既然這樣,就細說一下這個解決方案。
進過Nsthread和NSOperation的講述和上邊的基礎概念,可以開始組合用起來吧。并發隊列串行隊列都用起來。
3.1)分發隊列種類(dispatch queue)
①.UI主線程隊列 main queue

dispatch_get_main_queue()  

②.并行隊列global dispatch queue

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)  

這里的兩個參數得說明一下:第一個參數用于指定優先級,分別使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW兩個常量來獲取高和低優先級的兩個queue;第二個參數目前未使用到,默認0即可

③.串行隊列serial queues

dispatch_queue_create("minggo.app.com", NULL);  

3.2)6中多線程實現
①后臺執行線程創建

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self loadImageSource:imgUrl1];
});

②UI線程執行(只是為了測試,長時間加載內容不放在主線程)

dispatch_async(dispatch_get_main_queue(), ^{
    [self loadImageSource:imgUrl1];
});

③一次性執行(常用來寫單例)

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    [self loadImageSource:imgUrl1];
});

④并發地執行循環迭代

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count = 10;
dispatch_apply(count, queue, ^(size_t i) {
    NSLog(@"循環執行第%li次",i);
    [self loadImageSource:imgUrl1];
});  

⑤延遲執行

double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self loadImageSource:imgUrl1];
});  

⑥自定義dispatch_queue_t

dispatch_queue_t urls_queue = dispatch_queue_create("minggo.app.com", NULL);
dispatch_async(urls_queue, ^{
    [self loadImageSource:imgUrl1];
});

3.3)對比多任務執行
異步加載圖片是大部分app都要問題,那么加載圖片是按循序加載完之后才刷新UI呢?還是不安順序加載UI呢?顯然大部分的希望各自加載各自的圖片,各自刷新。以下就是模擬這兩種場景。

①先后執行,加載兩張圖片為例

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    UIImage *image1 = [self loadImage:imgUrl1];
    UIImage *image2 = [self loadImage:imgUrl2];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageview1.image = image1;
        self.imageView2.image = image2;
    });
});  

②并行隊列執行,也是以加載兩張圖片為例

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


dispatch_async(queue, ^{
    
    dispatch_group_t group = dispatch_group_create();
    
    __block UIImage *image1 = nil;
    __block UIImage *image2 = nil;
    
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        image1 = [self loadImage:imgUrl1];
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        image2 = [self loadImage:imgUrl2];
    });
    
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        self.imageview1.image = image1;
        self.imageView2.image = image2;
        
    });
});

①中等到兩張圖片加載完成后一起刷新,②就是典型的異步并行的例子,不需要理會各自圖片加載的先后問題,完成加載圖片刷新UI即可。從加載圖片中來說,第①種不太合適使用,但是對于在上邊場景選擇的時候的創作工具來說有很大的好處,首先得異步進行,然后異步中有得按順序執行幾個任務,比如上傳章節內容。因此,我們可以靈活考慮使用這兩多線程任務執行方式,實現各種場景。
3.4)編碼實現
以上3.3的內容99%代碼一樣,就不提供一個稍微整體的代碼了。看看下邊的效果圖吧。
3.5)效果圖如下

GCD多線程加載效果
GCD多線程加載效果

五.源碼地址

https://github.com/minggo620/iOSMutipleThread.git
如果小明這么跟銀行柜臺的MM講多線程,會不會。。。“給我滾出去~~”。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容