一嫉晶、基本概念
01 - 進程
- 進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序大审。
-
每個進程之間是獨立的贿肩,每個進程均運行在其專用受保護的內(nèi)存空間內(nèi)。
比如:同時打開迅雷聂使、Xcode,系統(tǒng)就會分別啟動2個進程
進程(迅雷) -<內(nèi)存>- 進程(Xcode)
通過『活動監(jiān)視器』可以查看Mac系統(tǒng)中所開啟的進程
02 - 線程
- 1個進程要想執(zhí)行任務(wù),必須得有線程(每一個進程至少要有1條線程),線程是進程的基本執(zhí)行單元壁拉。
-
一個進程(程序)的所有任務(wù)都在線程中執(zhí)行
比如使用酷狗音樂谬俄、使用迅雷下載電影,都需要在線程中執(zhí)行
進程(線程(任務(wù) -
1個線程中任務(wù)的執(zhí)行是串行的
-如果要在1個線程中執(zhí)行多個任務(wù)弃理,那么只能一個一個地按順序執(zhí)行這些任務(wù)
-也就是說溃论,在同一時間內(nèi),1個線程只能執(zhí)行1個任務(wù)
比如在1個線程中下載3個文件(分別是文件A痘昌、文件B钥勋、文件C)
串行執(zhí)行
03 - 線程和進程的比較
1.線程是CPU調(diào)用(執(zhí)行任務(wù))的最小單位
2.進程是CPU分配資源和調(diào)用的單位
3.一個程序可以對應(yīng)多個進程楞抡,一個進程中可以有多個線程煤裙,但至少要有一個線程。
4.同一個進程內(nèi)的線程共享進程的資源萧恕。
線程和進程的比較
04 - 多線程
- 1個進程中可以開啟多條線程姑子,每條線程可以并行(同時)執(zhí)行不同的任務(wù)
- 進程->車間乎婿,線程->車間工人
- 多線程技術(shù)可以提高程序的執(zhí)行效率
-
比如同時開啟3條線程分別下載3個文件(分別是文件A、文件B街佑、文件C)
并行
05 - 多線程并發(fā)執(zhí)行的原理
- 同一時間谢翎,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)
- 多線程并發(fā)(同時)執(zhí)行沐旨,其實是CPU快速地在多條線程之間調(diào)度(切換)
-
如果CPU調(diào)度線程的時間足夠快森逮,就造成了多線程并發(fā)執(zhí)行的假象
思考:如果線程非常非常多,會發(fā)生什么情況磁携?
--CPU會在N多線程之間調(diào)度褒侧,CPU會累死,消耗大量的CPU資源
--每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)
多線程并發(fā)執(zhí)行的原理
06 - 多線程的優(yōu)谊迄、缺點
優(yōu)點:
- 能適當(dāng)提高程序的執(zhí)行效率
- 能適當(dāng)提高資源利用率(CPU闷供、內(nèi)存利用率)
缺點:
- 創(chuàng)建線程是有開銷的,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1kB)、椡撑担空間(子線程512kb歪脏、主線程1MB-(測試512kb),也可以使用-setStackSize:設(shè)置,但必須是4k的倍數(shù)粮呢,而且最小是16k),創(chuàng)建線程大約需要90豪秒的創(chuàng)建時間婿失。
- 如果開啟大量的線程,會降低程序的性能
- 線程越多啄寡,CPU在調(diào)度線程上的開銷就越大
- 程序設(shè)計更加復(fù)雜: 比如線程之間的通信豪硅、多線程的數(shù)據(jù)共享
07 - 多線程在iOS開發(fā)中的應(yīng)用
- 主線程:
-- 一個iOS程序運行后,默認會開啟1條線程挺物,稱為『主線程』或『UI線程』
- 主線程的主要作用
-- 顯示懒浮、刷新UI界面
-- 處理UI事件(比如點擊事件、滾動時間识藤、拖拽事件等) - 主線程的使用注意
-- 別將比較耗時的操作放到主線程中
-- 耗時操作會卡住主線程嵌溢,嚴重影響UI的流暢度眯牧,給用戶一種『卡』的壞體驗
耗時操作-主線程
耗時操作-子線程
08 - iOS中多線程的實現(xiàn)方案
多線程的實現(xiàn)方案 - 1.pthread
說明:pthread的基本使用(需要包含頭文件)
// 使用pthread創(chuàng)建線程對象
pthread_t thread;
NSString name = @"zhengyang";
// 使用pthread創(chuàng)建線程
// 第一個參數(shù): 線程對象地址
// 第二個參數(shù): 線程屬性
// 第三個參數(shù): 指向函數(shù)的指針
// 第四個參數(shù): 傳遞給該函數(shù)的參數(shù)
pthread_create(&thread,NULL,run,(__bridge void)(name)); - 2.NSThread
(1) NSThread的基本使用
// 第一種創(chuàng)建線程的方式: alloc init.
特點: 需要手動開啟線程,可以拿到線程對象進行詳細設(shè)置
// 創(chuàng)建線程
/*
第一個參數(shù): 目標對象
第二個參數(shù): 選擇器赖草,線程啟動要調(diào)用哪個方法
第三個參數(shù): 前面方法要接受的參數(shù)(最多只能接受一個參數(shù)学少,沒有則傳nil)
*/
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"zhengyang"];
// 啟動線程
[thread start];
// 第二種創(chuàng)建線程的方式: 分離出一條線程
//特點: 自動啟動線程。無法對線程進行更詳細的設(shè)置
/*
第一個參數(shù):線程啟動調(diào)用的方法
第二個參數(shù):目標對象
第三個參數(shù):傳遞給調(diào)用方法的參數(shù)
*/
[NSThread detachNewThreadSelector(run:) toTarget:self withObject:@"我是分離出來的子線程"];
// 第三種創(chuàng)建線程的方式:后臺線程
// 特點:自動啟動線程秧骑,無法進行更詳細設(shè)置
[self performSelectorInBackground:@selector(run:) withObject:@"我是后臺線程"];
(2) 設(shè)置線程的屬性
// 設(shè)置線程的屬性
// 設(shè)置線程的名稱
thread.name = @"線程A";
// 設(shè)置線程的優(yōu)先級版确,注意線程優(yōu)先級的取值范圍為0.0~1.0之間。
1.0表示線程的優(yōu)先級最高乎折,如果不設(shè)置該值绒疗,那么理想狀態(tài)下默認為0.5
thread.threadPriority = 1.0;
(3) 線程的狀態(tài)(了解)
// 線程的各種狀態(tài):新建-就緒-運行-阻塞-死亡
// 常用的控制線程狀態(tài)的方法
[NSTread exit]; //退出當(dāng)前線程
[NSTread sleepForTimeInterval:2.0]; // 阻塞線程
[NSTread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; // 阻塞線程
// 注意: 線程死了不能復(fù)生
(4) 線程安全
01 前期:多個線程訪問同一塊資源會發(fā)生數(shù)據(jù)安全問題
02 解決方案: 加互斥鎖
03 相關(guān)代碼:@synchronized(self){}
04 專業(yè)術(shù)語-線程同步
05 原子和非原子屬性 (是否對setter方法加鎖)
(5) 線程間通信
-(void)touchesBegan:(nonnull NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
// [self download2];
// 開啟一條子線程來下載圖片
[NSThread detachNewThreadSelector:@seletor(downloadImage) toTarget:self withObject:nil];
}
-(void)downloadImage
{
// 1.確定要下載網(wǎng)絡(luò)圖片的url地址,一個url唯一對應(yīng)著網(wǎng)絡(luò)上的一個資源
NSURL *URL= [NSURL URLWithString:@"http://p6.qhimg.com/t01d2954e2799c461ab.jpg"];
// 2.根據(jù)url地址下載圖片數(shù)據(jù)到本地(二進制數(shù)據(jù))
NSData *data = [NSData dataWithContenesOfURL:rul];
// 3.把下載到本地的二進制數(shù)據(jù)轉(zhuǎn)換成圖片
UIImage *image = [UIImage imageWithData:data];
// 4.回到主線程刷新UI
// 4.1第一種方式
[self performSelectorOnMainThread:@selector(showImage:) withObject waitUntilDone:YES];
// 4.2第二種方式
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject waitUntilDone:YES];
// 4.3第三種方式
[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUnitilDone:YES];
}
(6) 如何計算代碼段的執(zhí)行時間
// 第一種方式
NSDate *start = [NSDate date];
// 根據(jù)url地址下載圖片數(shù)據(jù)到本地(二進制數(shù)據(jù))
NSData *end = [NSData dataWithContentsOfURL:url];
NSData *end = [NSDate date];
NSLog(@"第二步操作花費的時間為%f",[end timeIntervalSinceDate:start]);
// 第二種方法
CFTimeInterval start = CFAbsoluteTimeGetCurrent();
NSData *data = [NSData dataWithContentsOfURL:url];
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"第二步操作花費的時間為%f,end-start");
- 3 GCD
(1) GCD 基本知識
01 兩個核心概念-隊列和任務(wù)
02 同步函數(shù)和異步函數(shù)
(2)GCD基本使用【重點】
01 異步函數(shù)+并發(fā)隊列: 開啟多條線程骂澄,并發(fā)執(zhí)行任務(wù)
02 異步函數(shù)+串行隊列:開啟一條線程吓蘑,串行執(zhí)行任務(wù)
03 同步函數(shù)+并發(fā)隊列:不開線程,串行執(zhí)行任務(wù)
04 同步函數(shù)+串行隊列:不開線程坟冲,串行執(zhí)行任務(wù)
05 異步函數(shù)+主隊列: 不開線程磨镶,在主線程中串行執(zhí)行任務(wù)
06 同步函數(shù)+主隊列:不開線程,串行執(zhí)行任務(wù)(注意死鎖發(fā)生)
07 注意同步函數(shù)和異步函數(shù)在執(zhí)行順序上面的差異
(3)GCD 線程間通信
// 0.獲取一個全局的隊列
// dispatch_queue_t queue= dispath_get_global_queue(0,0);
// 1.先開啟一個線程健提,把下載圖片的操作放在子線程中處理
dispath_async(queue,^{
// 2.下載圖片
NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
NSLog(@"下載操作所在的線程--%@",[NSThread currentThread]);
// 3.回到主線程刷新UI
dispatch_async(dispatch_get_main_queue(),^{
self.imageView.image = image;
// 打印查看當(dāng)前線程
NSLog(@"刷新UI---%@",[NSThread currentThread]);
});
}
(4) GCD其它常用函數(shù)
01 柵欄函數(shù)(控制任務(wù)的執(zhí)行順序)
dispatch_barrier_async(queue,^{
NSLog(@"--dispatch_barrier_async-");
});
02 延遲執(zhí)行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2.0 * NSEC_PER_SEC)),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)^{
NSLog(@"---%@",[NSThread currentThread]);
});
03 一次性代碼(注意不能放到懶加載)
- (void)once{
// 整個程序運行過程中只會執(zhí)行一次
onceToken用來記錄該部分的代碼是否被執(zhí)行過
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
NSLog(@"---");
})琳猫;
}
04 快速迭代(開多個線程并發(fā)完成迭代操作)
dispatch_apply(subpaths.count,queue,^(size_t index){
});
05 隊列組(同柵欄函數(shù))
// 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
// 隊列組中的任務(wù)執(zhí)行完畢之后私痹,執(zhí)行該函數(shù)
dispatch_group_notify(dispatch_group_t,dispatch_queue_t queue,dispatch_block_t block);
06 進入群組和離開群組
dispatch_group_enter(group); // 執(zhí)行該函數(shù)后脐嫂,后面異步執(zhí)行的block會被group監(jiān)聽
dispatch_group_leave(group); // 異步block中,所有的任務(wù)都執(zhí)行完畢紊遵,最后離開群組
// 注意:dispatch_group_enter dispatch_group_leave必須成對使用