一、進程
1.1 什么是進程
- 進程是指在系統中正在運行的一個應用程序
- 每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內
- 比如同時打開Xcode和QQ,就會開啟兩個進程
- 通過活動“活動監視器”可以查看MAC系統中所有的進程
二、線程
2.1 什么是線程
- 一個進程要想執行任務,必須得有線程(每一個進程至少要有一個線程)
- 線程是進程的基本單元,一個程序所有的任務都做線程中執行
- 使用迅雷下載電影、使用網易云聽歌,都需要在線程中執行
2.2 線程的串行
- 一個線程的任務是串行的(同一時間內,一個線程只能執行一個任務)
三、多線程
3.1 what
- 一個進程中可以開啟多條線程,每條線程可以同時(并行)執行不同的任務
- 多線程可以提高程序的執行效率
3.2 多線程的原理
- 同一時間,cpu只能處理一條線程
- 多線程并發執行,其實是cpu快速的在多條線程之間調度(切換),如果cpu調度線程的時間足夠快,就造成了多線程并發執行的假象。
3.3 多線程的優點
- 能適當提高程序的執行效率
- 能適當提高資源的利用率(cpu 、內存)
3.4 多線程的缺點
- 如果開啟大量的線程,會占用大量的內存空間,降低程序的性能
- 線程越多,cpu在調度線程上的開銷就越大
- 使程序的設計更加復雜:比如多線程之間的通信,多線程之間的數據共享
四、多線程在iOS中的應用
4.1 什么是主線程
- 一個ios程序運行后,默認會開啟一條線程,稱為“主線程”或“UI線程”
4.2 主線程的主要作用
- 顯示\刷新UI界面
- 處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
4.3 主線程的使用注意
- 別將比較耗時的操作放到主線程中,耗時操作會卡住主線程,嚴重影響UI的流暢度,給用戶一種“卡”的體驗。
4.4 實現方案
五、NSThread
5.1 what
一個NSThread對象就代表一個線程
- 創建、啟動線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"哈哈"]; thread.name = @"線程A";//自己來區別的 // 開啟線程 [thread start];
- 獲得當前線程
NSThread *current = [NSThread currentThread]; NSLog(@"btnClick---%@", current);
- 其他創建線程的方式
//創建完線程直接(自動)啟動 [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"我是參數"]; // 在后臺線程中執行 === 在子線程中執行 [self performSelectorInBackground:@selector(run:) withObject:@"abc參數"]; 以上2種方式的優點:快捷方便 缺點:無法對線程進行更詳細的設置
六、線程的狀態
線程一共有5種狀態,如圖:
6.1新建狀態和就緒狀態
現在以NSThread為例,黃色為例。
[tehread start]之前是新建狀態,start之后進入了就緒狀態(cpu在調度其他進程的時候也是處于調度狀態),這個時候就進入了可調度線程池,放進可調度的線程池的線程是可以來回進行調度的。
6.2運行狀態
線程經過cpu調度之后就進入了運行狀態。
6.3阻塞狀態
調用了sleep方法/等待同步鎖的時候,就進入了阻塞狀態。
6.4死亡狀態
線程任務執行完畢,或者異常/強制退出等,就進入了死亡狀態。
七、多線程的安全隱患
7.1 why
-
資源共享
一個資源可能被多個資源共享(多個線程可能訪問同一個資源),比如多個線程訪問同一個變量、同一個對象、同一個文件等,當多個線程訪問同一個資源時,很容易引發數據安全和數據錯亂問題
如下圖分析:
安全隱患分析.png
7.2解決方法
-
互斥鎖
安全隱患解決.png
@synchronized(鎖對象){
}
注意:鎖定一份代碼只用一把鎖,用多把鎖是無效的
*互斥鎖的優點、缺點
優點:能有效防止因多線程搶奪資源造成的數據安全問題
缺點:需要消耗大量的cpu資源
線程同步
多條線程按順序的執行任務
互斥鎖就是使用了線程同步技術-
原子性和非原子性
oc在定義屬性時有2種選擇- atomic: 原子屬性,為setter方法加鎖(默認就是atomic),線程安全,需要消耗大量的資源
- nonatmic: 非原子屬性,不會為setter方法加鎖,非線程安全,適合內存小的移動設備
ios開發建議:所有的屬性都聲明為nonatmic,盡量避免多線程搶奪同一塊資源,盡量將加鎖、搶奪資源的業務邏輯交給服務器端處理,減小移動端的壓力。
八、線程之間的通信
8.1 what
在一個進程中,線程往往不是孤立存在的,多個線程之間需要經常進行通信
8.2 體現
- 一個線程傳遞數據給另外一個線程
- 在一個線程中執行完一個特定的任務后,轉到另一個線程繼續執行任務。
import "ViewController.h"
@interface HMViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation HMViewController
-(void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event
{
// 在子線程中調用download方法下載圖片
[self performSelectorInBackground:@selector(download) withObject:nil];
}
/
下載圖片 : 子線程
*/
-(void)download
{
// 1.根據URL下載圖片
NSURL *url = [NSURL URLWithString:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];
NSLog(@"-------begin");
NSData data = [NSData dataWithContentsOfURL:url]; // 這行會比較耗時
NSLog(@"-------end");
UIImage image = [UIImage imageWithData:data];
// 2.回到主線程顯示圖片
// [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];
// setImage: 1s
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
// [self performSelectorOnMainThread:@selector(settingImage:) withObject:image waitUntilDone:NO];
}
/
設置(顯示)圖片: 主線程
*/
//- (void)settingImage:(UIImage *)image
//{
// self.imageView.image = image
//}
@end
九、GCD
9.1 what
- 全稱是Grand Central Dispatch,可譯為“牛逼的中樞調度器”。
- 純c語言,提供了非常多、強大的函數
9.2 優勢
- GCD是蘋果公司為多核的并行運算提出的解決法案
- GCD會自動利用更多的CPU內核,會自動管理線程的生命周期(創建線程、調度線程、銷毀線程)
- 程序員只需要告訴GCD想要執行什么任務,不需要編寫任何線程管理代碼
9.3 隊列和任務
- 任務:執行什么操作
- 隊列:用來存放任務
- 并發隊列:可以讓多個任務同時執行,只有在異步函數下才有效
- 串行隊列:讓任務一個接著一個的執行
- 同步:在當前線程中執行,不具備開啟線程的能力
- 異步:在另一條線程中執行,具備開啟線程的能力
GCD會自動將隊列中的任務取出,放到對應的線程中執行,任務的取出遵循隊列的FIFO原則:先進先出,后進后出
9.4 并發對列
GCD默認已經提供了全局的并發對列,供整個應用使用,不需要手動創建
// 1.獲得全局的并發隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
9.5串行隊列
GCD中獲得串行有2中途徑
// 1.創建串行隊列 dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", NULL);
//2.使用主隊列(跟主線程相關聯的隊列) dispatch_queue_t queue = dispatch_get_main_queue();
各隊列的執行效果
上代碼
#import "ViewController.h"
@interface HMViewController ()
@end
@implementation HMViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorInBackground:@selector(test) withObject:nil];
// [self syncMainQueue];
}
- (void)test
{
NSLog(@"test --- %@", [NSThread currentThread]);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"任務 --- %@", [NSThread currentThread]);
});
}
/**
* 使用dispatch_async異步函數, 在主線程中往主隊列中添加任務
*/
- (void)asyncMainQueue
{
// 1.獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.添加任務到隊列中 執行
dispatch_async(queue, ^{
NSLog(@"----下載圖片1-----%@", [NSThread currentThread]);
});
}
/**
* 使用dispatch_sync同步函數, 在主線程中往主隊列中添加任務 : 任務無法往下執行
*/
- (void)syncMainQueue
{
// 1.獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.添加任務到隊列中 執行
dispatch_sync(queue, ^{
NSLog(@"----下載圖片1-----%@", [NSThread currentThread]);
});
// dispatch_sync(queue, ^{
// NSLog(@"----下載圖片2-----%@", [NSThread currentThread]);
// });
// dispatch_sync(queue, ^{
// NSLog(@"----下載圖片3-----%@", [NSThread currentThread]);
// });
// 不會開啟新的線程, 所有任務在主線程中執行
}
// 凡是函數名種帶有create\copy\new\retain等字眼, 都需要在不需要使用這個數據的時候進行release
// GCD的數據類型在ARC環境下不需要再做release
// CF(Core Foundation)的數據類型在ARC環境下還是需要再做release
/**
* 用dispatch_sync同步函數往串行列中添加任務
*/
- (void)syncSerialQueue
{
// 1.創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", NULL);
// 2.添加任務到隊列中 執行
dispatch_sync(queue, ^{
NSLog(@"----下載圖片1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"----下載圖片2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"----下載圖片3-----%@", [NSThread currentThread]);
});
// 3.釋放資源
// dispatch_release(queue); // MRC(非ARC)
// 總結: 不會開啟新的線程
}
/**
* 用dispatch_sync同步函數往并發隊列中添加任務
*/
- (void)syncGlobalQueue
{
// 1.獲得全局的并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.添加任務到隊列中 執行
dispatch_sync(queue, ^{
NSLog(@"----下載圖片1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"----下載圖片2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"----下載圖片3-----%@", [NSThread currentThread]);
});
// 總結: 不會開啟新的線程, 并發隊列失去了并發的功能
}
/**
* 用dispatch_async異步函數往串行隊列中添加任務
*/
- (void)asyncSerialQueue
{
// 1.創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", NULL);
// 2.添加任務到隊列中 執行
dispatch_async(queue, ^{
NSLog(@"----下載圖片1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----下載圖片2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----下載圖片3-----%@", [NSThread currentThread]);
});
// 總結: 只開1個線程執行任務
}
/**
* 用dispatch_async異步函數往并發隊列中添加任務
*/
- (void)asyncGlobalQueue
{
// 1.獲得全局的并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.添加任務到隊列中 執行
dispatch_async(queue, ^{
NSLog(@"----下載圖片1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----下載圖片2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----下載圖片3-----%@", [NSThread currentThread]);
});
// 總結: 同時開啟了3個線程
}
@end
9.6 線程間的通信
從子線程回到主線程
上代碼
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"--download--%@", [NSThread currentThread]);
// 下載圖片
NSURL *url = [NSURL URLWithString:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url]; // 這行會比較耗時
UIImage *image = [UIImage imageWithData:data];
// 回到主線程顯示圖片
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"--imageView--%@", [NSThread currentThread]);
self.imageView.image = image;
});
});
}
9.7 延時執行
2種方法
- (void)delay
{
// NSLog(@"----touchesBegan----%@", [NSThread currentThread]);
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];//第一種
// });
// 1.全局并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.計算任務執行的時間
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
// 3.會在when這個時間點, 執行queue中的任務。第二種
dispatch_after(when, queue, ^{
NSLog(@"----run----%@", [NSThread currentThread]);
});
}
//- (void)run
//{
// NSLog(@"----run----%@", [NSThread currentThread]);
//}
9.8一次性代碼
保證某段代碼在程序運行的過程中只被執行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-------touchesBegan");
});
}
9.9 隊列組
首先有一個需求,分別異步執行2個耗時的操作,等2個異步操作都執行完畢后,再回到主線程執行操作。
dispatch_group_t group = dispatch_group_create();
上代碼
/**
1.下載圖片1和圖片2
2.將圖片1和圖片2合并成一張圖片后顯示到imageView上
思考:
* 下載圖片 : 子線程
* 等2張圖片都下載完畢后, 才回到主線程
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 創建一個組
dispatch_group_t group = dispatch_group_create();
// 開啟一個任務下載圖片1
__block UIImage *image1 = nil;
dispatch_group_async(group, global_queue, ^{
image1 = [self imageWithURL:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];
});
// 開啟一個任務下載圖片2
__block UIImage *image2 = nil;
dispatch_group_async(group, global_queue, ^{
image2 = [self imageWithURL:@"http://news.baidu.com/z/resource/r/image/2014-06-22/b2a9cfc88b7a56cfa59b8d09208fa1fb.jpg"];
});
// 同時執行下載圖片1\下載圖片2操作
// 等group中的所有任務都執行完畢, 再回到主線程執行其他操作
dispatch_group_notify(group, main_queue, ^{
self.imageView1.image = image1;
self.imageView2.image = image2;
// 合并
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
[image1 drawInRect:CGRectMake(0, 0, 100, 100)];
[image2 drawInRect:CGRectMake(100, 0, 100, 100)];
self.bigImageView.image = UIGraphicsGetImageFromCurrentImageContext();
// 關閉上下文
UIGraphicsEndImageContext();
});
}
- (UIImage *)imageWithURL:(NSString *)urlStr
{
NSURL *url = [NSURL URLWithString:urlStr];
NSData *data = [NSData dataWithContentsOfURL:url]; // 這行會比較耗時
return [UIImage imageWithData:data];
}
十、NSOperation
10.1 what
作用:配合使用NSOperation和NSOperationQueue也能實現多線程編程
實現步驟:
先將需要執行的操作封裝到一個NSOperation對象中
然后將NSOperation對象添加到NSOperationQueue中
系統會自動將NSOperation中封裝的操作放到一條新線程中執行
10.2 how
NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類
使用NSOperationa子類有3種
- NSInvocationOperation
- NSBlockOperation
- 自定義子類繼承NSOperation,實現內部相應的方法
10.2.1 NSInvocationOperation
- 創建SInvocationOperation對象
- 調用start方法開始執行操作
注意:
默認情況下,調用了start方法后并不會開一條新線程去執行操作,而是在當前線程同步執行操作,只有將NSOperation放到NSOperationQueue中,才會異步執行操作。
-(void)invocationOperation
{
// 1.創建操作對象, 封裝要執行的任務
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
// 2.執行操作(默認情況下, 如果操作沒有放到隊列queue中, 都是同步執行)
[operation start];
}
-(void)download
{
for (int i = 0; i<10; i++) {
NSLog(@"------download---%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.1];
}
}
10.2.2 NSBlockOperation
如下代碼會開啟3個線程,線程數取決于任務的個數
-(void)blockOperation
{
// 1.封裝操作
// NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// NSLog(@"NSBlockOperation------下載圖片1---%@", [NSThread currentThread]);
// }];
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
NSLog(@"NSBlockOperation------下載圖片1---%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"NSBlockOperation------下載圖片2---%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"NSBlockOperation------下載圖片3---%@", [NSThread currentThread]);
}];
// 2.執行操作
[operation start];
}
添加到NSOperationQueue的操作
- (void)operationQueue
{
// 1.封裝操作
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
// operation1.queuePriority = NSOperationQueuePriorityHigh
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i<10; i++) {
NSLog(@"NSBlockOperation------下載圖片---%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.1];
}
}];
// [operation3 addExecutionBlock:^{
// for (int i = 0; i<10; i++) {
// NSLog(@"NSBlockOperation------下載圖片2---%@", [NSThread currentThread]);
// [NSThread sleepForTimeInterval:0.1];
// }
// }];
// 2.創建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//設置并發數
queue.maxConcurrentOperationCount = 2; // 2 ~ 3為宜
// 設置依賴
[operation2 addDependency:operation3];
[operation3 addDependency:operation1];
// 3.添加操作到隊列中(自動執行操作, 自動開啟線程)
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
// [queue setSuspended:YES];
}
10.3 對列的暫停、取消和恢復
[queue setSuspended:YES];//yes代表暫停,no代表恢復
[queue cancel]// 取消
10.4 操作依賴(代碼在上邊)
10.5 NSOperation下載圖片的思路
github上有一個比較好的下載圖片框架,sdwebimage,可以去研究一下。