Ⅰ android 大量多線程怎麼優化
在程序開發的實踐當中,為了讓程序表現得更加流暢,我們肯定會需要使用到多線程來提升程序的並發執行性能。但是編寫多線程並發的代碼一直以來都是一個相對棘手的問題,所以想要獲得更佳的程序性能,我們非常有必要掌握多線程並發編程的基礎技能。
眾所周知,Android 程序的大多數代碼操作都必須執行在主線程,例如系統事件(例如設備屏幕發生旋轉),輸入事件(例如用戶點擊滑動等),程序回調服務,UI 繪制以及鬧鍾事件等等。那麼我們在上述事件或者方法中插入的代碼也將執行在主線程。
一旦我們在主線程裡面添加了操作復雜的代碼,這些代碼就很可能阻礙主線程去響應點擊/滑動事件,阻礙主線程的 UI 繪制等等。我們知道,為了讓屏幕的刷新幀率達到 60fps,我們需要確保 16ms 內完成單次刷新的操作。一旦我們在主線程裡面執行的任務過於繁重就可能導致接收到刷新信號的時候因為資源被佔用而無法完成這次刷新操作,這樣就會產生掉幀的現象,刷新幀率自然也就跟著下降了(一旦刷新幀率降到 20fps 左右,用戶就可以明顯感知到卡頓不流暢了)。
為了避免上面提到的掉幀問題,我們需要使用多線程的技術方案,把那些操作復雜的任務移動到其他線程當中執行,這樣就不容易阻塞主線程的操作,也就減小了出現掉幀的可能性。
那麼問題來了,為主線程減輕負的多線程方案有哪些呢?這些方案分別適合在什麼場景下使用?Android 系統為我們提供了若干組工具類來幫助解決這個問題。
AsyncTask: 為 UI 線程與工作線程之間進行快速的切換提供一種簡單便捷的機制。適用於當下立即需要啟動,但是非同步執行的生命周期短暫的使用場景。
HandlerThread: 為某些回調方法或者等待某些任務的執行設置一個專屬的線程,並提供線程任務的調度機制。
ThreadPool: 把任務分解成不同的單元,分發到各個不同的線程上,進行同時並發處理。
IntentService: 適合於執行由 UI 觸發的後台 Service 任務,並可以把後台任務執行的情況通過一定的機制反饋給 UI。
了解這些系統提供的多線程工具類分別適合在什麼場景下,可以幫助我們選擇合適的解決方案,避免出現不可預期的麻煩。雖然使用多線程可以提高程序的並發量,但是我們需要特別注意因為引入多線程而可能伴隨而來的內存問題。舉個例子,在 Activity 內部定義的一個 AsyncTask,它屬於一個內部類,該類本身和外面的 Activity 是有引用關系的,如果 Activity 要銷毀的時候,AsyncTask 還仍然在運行,這會導致 Activity 沒有辦法完全釋放,從而引發內存泄漏。所以說,多線程是提升程序性能的有效手段之一,但是使用多線程卻需要十分謹慎小心,如果不了解背後的執行機制以及使用的注意事項,很可能引起嚴重的問題。
Ⅱ ios本地寫文件 應該在主線程么
大家都知道,在開發過程中應該盡可能減少用戶等待時間,讓程序盡可能快的完成運算。可是無論是哪種語言開發的程序最終往往轉換成匯編語言進而解釋成機器碼來執行。但是機器碼是按順序執行的,一個復雜的多步操作只能一步步按順序逐個執行。改變這種狀況可以從兩個角度出發:對於單核處理器,可以將多個步驟放到不同的線程,這樣一來用戶完成UI操作後其他後續任務在其他線程中,當CPU空閑時會繼續執行,而此時對於用戶而言可以繼續進行其他操作;對於多核處理器,如果用戶在UI線程中完成某個操作之後,其他後續操作在別的線程中繼續執行,用戶同樣可以繼續進行其他UI操作,與此同時前一個操作的後續任務可以分散到多個空閑CPU中繼續執行(當然具體調度順序要根據程序設計而定),及解決了線程阻塞又提高了運行效率。蘋果從iPad2 開始使用雙核A5處理器(iphone中從iPhone 4S開始使用),A7中還加入了協處理器,如何充分發揮這些處理器的性能確實值得思考。今天將重點分析iOS多線程開發:
多線程
簡介
iOS多線程
NSThread
解決線程阻塞問題
多線程並發
線程狀態
擴展-NSObject分類擴展
NSOperation
NSInvocationOperation
NSBlockOperation
線程執行順序
GCD
串列隊列
並發隊列
其他任務執行方法
線程同步
NSLock同步鎖
@synchronized代碼塊
擴展--使用GCD解決資源搶占問題
擴展--控制線程通信
總結
目 錄
多線程
簡介
當用戶播放音頻、下載資源、進行圖像處理時往往希望做這些事情的時候其他操作不會被中斷或者希望這些操作過程中更加順暢。在單線程中一個線程只能做一件事情,一件事情處理不完另一件事就不能開始,這樣勢必影響用戶體驗。早在單核處理器時期就有多線程,這個時候多線程更多的用於解決線程阻塞造成的用戶等待(通常是操作完UI後用戶不再干涉,其他線程在等待隊列中,CPU一旦空閑就繼續執行,不影響用戶其他UI操作),其處理能力並沒有明顯的變化。如今無論是移動操作系統還是PC、伺服器都是多核處理器,於是「並行運算」就更多的被提及。一件事情我們可以分成多個步驟,在沒有順序要求的情況下使用多線程既能解決線程阻塞又能充分利用多核處理器運行能力。
下圖反映了一個包含8個操作的任務在一個有兩核心的CPU中創建四個線程運行的情況。假設每個核心有兩個線程,那麼每個CPU中兩個線程會交替執行,兩個CPU之間的操作會並行運算。單就一個CPU而言兩個線程可以解決線程阻塞造成的不流暢問題,其本身運行效率並沒有提高,多CPU的並行運算才真正解決了運行效率問題,這也正是並發和並行的區別。當然,不管是多核還是單核開發人員不用過多的擔心,因為任務具體分配給幾個CPU運算是由系統調度的,開發人員不用過多關心系統有幾個CPU。開發人員需要關心的是線程之間的依賴關系,因為有些操作必須在某個操作完成完才能執行,如果不能保證這個順序勢必會造成程序問題。
iOS多線程
在iOS中每個進程啟動後都會建立一個主線程(UI線程),這個線程是其他線程的父線程。由於在iOS中除了主線程,其他子線程是獨立於Cocoa Touch的,所以只有主線程可以更新UI界面(新版iOS中,使用其他線程更新UI可能也能成功,但是不推薦)。iOS中多線程使用並不復雜,關鍵是如何控制好各個線程的執行順序、處理好資源競爭問題。常用的多線程開發有三種方式:
1.NSThread
2.NSOperation
3.GCD
三種方式是隨著iOS的發展逐漸引入的,所以相比而言後者比前者更加簡單易用,並且GCD也是目前蘋果官方比較推薦的方式(它充分利用了多核處理器的運算性能)。做過.Net開發的朋友不難發現其實這三種開發方式 剛好對應.Net中的多線程、線程池和非同步調用,因此在文章中也會對比講解。
NSThread
NSThread是輕量級的多線程開發,使用起來也並不復雜,但是使用NSThread需要自己管理線程生命周期。可以使用對象方法+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument直接將操作添加到線程中並啟動,也可以使用對象方法- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 創建一個線程對象,然後調用start方法啟動線程。
解決線程阻塞問題
在資源下載過程中,由於網路原因有時候很難保證下載時間,如果不使用多線程可能用戶完成一個下載操作需要長時間的等待,這個過程中無法進行其他操作。下面演示一個採用多線程下載圖片的過程,在這個示例中點擊按鈕會啟動一個線程去下載圖片,下載完成後使用UIImageView將圖片顯示到界面中。可以看到用戶點擊完下載按鈕後,不管圖片是否下載完成都可以繼續操作界面,不會造成阻塞。
//
// NSThread實現多線程
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//
#import "KCMainViewController.h"
@interface KCMainViewController (){
UIImageView *_imageView;
}
@end
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self layoutUI];
}
#pragma mark 界面布局
-(void)layoutUI{
_imageView =[[UIImageView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame];
_imageView.contentMode=;
[self.view addSubview:_imageView];
UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"載入圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
#pragma mark 將圖片顯示到界面
-(void)updateImage:(NSData *)imageData{
UIImage *image=[UIImage imageWithData:imageData];
_imageView.image=image;
}
#pragma mark 請求圖片數據
-(NSData *)requestData{
//對於多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
}
#pragma mark 載入圖片
-(void)loadImage{
//請求數據
NSData *data= [self requestData];
/*將數據顯示到UI控制項,注意只能在主線程中更新UI,
另外performSelectorOnMainThread方法是NSObject的分類方法,每個NSObject對象都有此方法,
它調用的selector方法是當前調用控制項的方法,例如使用UIImageView調用的時候selector就是UIImageView的方法
Object:代表調用方法的參數,不過只能傳遞一個參數(如果有多個參數請使用對象進行封裝)
waitUntilDone:是否線程任務完成執行
*/
[self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];
}
#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
//方法1:使用對象方法
//創建一個線程,第一個參數是請求的操作,第二個參數是操作方法的參數
// NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
// //啟動一個線程,注意啟動一個線程並非就一定立即執行,而是處於就緒狀態,當系統調度時才真正執行
// [thread start];
//方法2:使用類方法
[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
}
@end
運行效果:
程序比較簡單,但是需要注意執行步驟:當點擊了「載入圖片」按鈕後啟動一個新的線程,這個線程在演示中大概用了5s左右,在這5s內UI線程是不會阻塞的,用戶可以進行其他操作,大約5s之後圖片下載完成,此時調用UI線程將圖片顯示到界面中(這個過程瞬間完成)。另外前面也提到過,更新UI的時候使用UI線程,這里調用了NSObject的分類擴展方法,調用UI線程完成更新。
多個線程並發
上面這個演示並沒有演示多個子線程操作之間的關系,現在不妨在界面中多載入幾張圖片,每個圖片都來自遠程請求。
大家應該注意到不管是使用+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument、- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 方法還是使用- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait方法都只能傳一個參數,由於更新圖片需要傳遞UIImageView的索引和圖片數據,因此這里不妨定義一個類保存圖片索引和圖片數據以供後面使用。
KCImageData.h
//
// KCImageData.h
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface KCImageData : NSObject
#pragma mark 索引
@property (nonatomic,assign) int index;
#pragma mark 圖片數據
@property (nonatomic,strong) NSData *data;
@end
接下來將創建多個UIImageView並創建多個線程用於往UIImageView中填充圖片。
KCMainViewController.m
//
// NSThread實現多線程
// MultiThread
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//
#import "KCMainViewController.h"
#import "KCImageData.h"
#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
@interface KCMainViewController (){
NSMutableArray *_imageViews;
}
@end
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self layoutUI];
}
#pragma mark 界面布局
-(void)layoutUI{
//創建多個圖片控制項用於顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];
}
}
UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"載入圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
#pragma mark 將圖片顯示到界面
-(void)updateImage:(KCImageData *)imageData{
UIImage *image=[UIImage imageWithData:imageData.data];
UIImageView *imageView= _imageViews[imageData.index];
imageView.image=image;
}
#pragma mark 請求圖片數據
-(NSData *)requestData:(int )index{
//對於多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
}
#pragma mark 載入圖片
-(void)loadImage:(NSNumber *)index{
// NSLog(@"%i",i);
//currentThread方法可以取得當前操作線程
NSLog(@"current thread:%@",[NSThread currentThread]);
int i=[index integerValue];
// NSLog(@"%i",i);//未必按順序輸出
NSData *data= [self requestData:i];
KCImageData *imageData=[[KCImageData alloc]init];
imageData.index=i;
imageData.data=data;
[self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];
}
#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
//創建多個線程用於填充圖片
for (int i=0; i<ROW_COUNT*COLUMN_COUNT; ++i) {
// [NSThread detachNewThreadSelector:@selector(loadImage:) toTarget:self withObject:[NSNumber numberWithInt:i]];
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
thread.name=[NSString stringWithFormat:@"myThread%i",i];//設置線程名稱
[thread start];
}
}
@end