什么是進(jìn)程
進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個應(yīng)用程序
每個進(jìn)程之間是獨(dú)立的,每個進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi)
比如同時打開迅雷筐喳、Xcode贺待,系統(tǒng)就會分別啟動2個進(jìn)程
什么是線程
1個進(jìn)程要想執(zhí)行任務(wù),必須得有線程(每1個進(jìn)程至少要有1條線程)
一個進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行
比如使用酷狗播放音樂临庇、使用迅雷下載電影反璃,都需要在線程中執(zhí)行
1個線程中任務(wù)的執(zhí)行是串行的
如果要在1個線程中執(zhí)行多個任務(wù),那么只能一個一個地按順序執(zhí)行這些任務(wù)
也就是說假夺,在同一時間內(nèi)淮蜈,1個線程只能執(zhí)行1個任務(wù)
什么是多線程
- 1個進(jìn)程中可以開啟多條線程,每條線程可以并行(同時)執(zhí)行不同的任務(wù)
- 多線程技術(shù)可以提高程序的執(zhí)行效率
- 比如同時開啟3條線程分別下載3個文件(分別是文件A侄泽、文件B礁芦、文件C
多線程的原理
- 同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)
- 多線程并發(fā)(同時)執(zhí)行柿扣,其實(shí)是CPU快速地在多條線程之間調(diào)度(切換)
- 如果CPU調(diào)度線程的時間足夠快肖方,就造成了多線程并發(fā)執(zhí)行的假象
如果線程非常非常多,會發(fā)生什么情況未状?
CPU會在N多線程之間調(diào)度俯画,CPU會累死,消耗大量的CPU資源
每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)
多線程的優(yōu)點(diǎn)
- 能適當(dāng)提高程序的執(zhí)行效率
- 能適當(dāng)提高資源利用率(CPU司草、內(nèi)存利用率)
多線程的缺點(diǎn)
- 創(chuàng)建線程是有開銷的艰垂,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、椔窈纾空間(子線程512KB猜憎、主線程1MB,也可以使用-setStackSize:設(shè)置搔课,但必須是4K的倍數(shù)胰柑,而且最小是16K),創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間
- 如果開啟大量的線程爬泥,會降低程序的性能
- 線程越多柬讨,CPU在調(diào)度線程上的開銷就越大
- 程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享
什么是主線程
一個iOS程序運(yùn)行后袍啡,默認(rèn)會開啟1條線程踩官,稱為“主線程”或“UI線程”
-
主線程的主要作用
- 顯示\刷新UI界面
- 處理UI事件(比如點(diǎn)擊事件、滾動事件境输、拖拽事件等)
-
主線程的使用注意
- 別將比較耗時的操作放到主線程中
- 耗時操作會卡住主線程蔗牡,嚴(yán)重影響UI的流暢度,給用戶一種“卡”的壞體驗(yàn)
在主線程放一個需要耗時10秒的操作;用戶在第五秒的時候點(diǎn)擊屏幕按鈕,在第十秒的時候才會做出響應(yīng);造成卡頓現(xiàn)象;
- 在ios領(lǐng)域里面真正的多線程技術(shù)只有這兩個pthread和NSthread;
GCD不能直接操作多線程;屬于并發(fā)技術(shù);NSOperation也是一樣,是操作隊(duì)列的,與線程無關(guān),線程部分GCD已經(jīng)幫你封裝好了!
- GCD會自動管理線程的生命周期(創(chuàng)建線程畴嘶、調(diào)度任務(wù)蛋逾、銷毀線程)
- 我們只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼
- 蘋果不建議開發(fā)者使用多線程技術(shù);
鼓勵使用并發(fā)技術(shù);
NSThread
- 創(chuàng)建線程的幾種方法
// 創(chuàng)建一個NSThread
NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(demo:) object:@"Thread"];
//線程就緒-之后等著cpu調(diào)度;
[thread start];
//demo函數(shù)在子線程執(zhí)行
-(void)demo:(id)obj{
for (int i = 0; i < 2; i++) {
//number !=1
NSLog(@"%@",[NSThread currentThread]);
//[NSThread currentThread] 打印當(dāng)前線程
//返回一個Thread對象,里面有number和name屬性
//number == 1 說明是主線程 number != 1 就是其他線程
}
}
//detach ==> 分離
//分離出一條子線程
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"Detach"];
//InBackground 就是在后臺(子線程)運(yùn)行!!
//是NSObject的分類 意味著所有的繼承NSObject的都可以使用這個方法
//非常方便.不用NSThread對象
[self performSelectorInBackground:@selector(demo:) withObject:@"background"];
線程的狀態(tài)
- 新建一條線程,線程在可調(diào)度線程池里 (由cpu進(jìn)行調(diào)度的)
- 當(dāng) [thread start] 的時候,在可調(diào)度線程池里的線程都處于就緒狀態(tài)
- cpu調(diào)度處于就緒狀態(tài)的線程
- 運(yùn)行狀態(tài)
- 線程執(zhí)行完畢之后線程在可調(diào)度線程池取出,干掉;
線程阻塞
- 當(dāng)運(yùn)行滿足某個條件,會讓線程"睡一會
提示:sleep 方法是類方法,會直接休眠當(dāng)前線程!!
//當(dāng)前線程睡兩秒
[NSThread sleepForTimeInterval:2.0];
//一旦強(qiáng)行終止線程,后續(xù)的所有代碼都不會被執(zhí)行
//注意:在終止線程之前,應(yīng)該要釋放之前分配的對象!!
[NSThread exit];
//創(chuàng)建線程
NSThread * t = [[NSThread alloc]initWithTarget:self selector:@selector(theadStatus) object:nil];
//線程就緒(CPU翻牌)
[t start];
-(void)theadStatus{
for (int i = 0; i < 20;i++) {
//阻塞,當(dāng)運(yùn)行滿足某個條件,會讓線程"睡一會"
//提示:sleep 方法是類方法,會直接休眠當(dāng)前線程!!
if (i == 8) {
NSLog(@"睡一會");
//睡的是子線程
//注意!!! exit會殺掉主線程!但是APP不會掛掉!!
[NSThread sleepForTimeInterval:2.0];
}
NSLog(@"%@ %d",[NSThread currentThread],i);
//當(dāng)線程滿足某一個條件時,可以強(qiáng)行終止的
//exit 類方法,哥么終止當(dāng)前線程!!!!
if (i == 15) {
[NSThread exit]; //線程就就處于死亡狀態(tài)了
}
}
NSLog(@"能來嗎???"); //來不了了
}
threadPriority //線程優(yōu)先級
- 優(yōu)先級只是保證cpu調(diào)度的可能性會高!
用優(yōu)先級來控制線程得執(zhí)行順序是不理性的!
- 建議:再開發(fā)的時候不要修改優(yōu)先級;
在多線程的開發(fā)中,不要相信一次的運(yùn)行結(jié)果!
多線程的目的
- 將耗時操作放在后臺,不阻塞UI線程
多線程的安全隱患
-
1塊資源可能會被多個線程共享窗悯,也就是多個線程可能會訪問同一塊資源
- 比如多個線程訪問同一個對象区匣、同一個變量、同一個文件
當(dāng)多個線程訪問同一塊資源時蒋院,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題
安全隱患的解決 - 互斥鎖
- 一個線程訪問一個資源時,先給他上鎖,訪問完成之后再解鎖讓其他線程訪問;
- 互斥鎖 -- 保證鎖內(nèi)的代碼,同一時間,只有一條線程執(zhí)行!
- 互斥鎖 的范圍 應(yīng)該盡量小,范圍大了 效率就差!!
//互斥鎖
@synchronized (self) {
//括號里是 同一時間只能有一條線程執(zhí)行的代碼
};
好比你去上廁所,你把門鎖上了,別人只能在外面等著,等你操作完廁所,別人才能進(jìn)來;如果你不鎖門,兩個人在廁所后果不堪設(shè)想!??
原子屬性
- atomic-用來保護(hù)線程安全(多線程執(zhí)行寫入操作的時候,保證同一時間只有一個線程執(zhí)行寫入 只對寫入操作上鎖,讀不上鎖)
- 單寫多讀的原子屬性(只能單個線程寫,但可以多個線程讀)
- 實(shí)際上原子屬性內(nèi)部有一把鎖叫 - 自旋鎖
- 自旋鎖&互斥鎖異同
-
共同點(diǎn)
- 都能保證線程安全
-
不同點(diǎn)
- 互斥鎖:被鎖在外面的線程,處于休眠狀態(tài);等待鎖打開,然后被喚醒;
- 自旋鎖:被鎖在外面的線程,用死循環(huán)的方式,等待鎖打開;
-
- 自旋鎖&互斥鎖異同
- 不管什么鎖,都很消耗性能,效率不高;
想要模擬原子屬性的時候就在set方法里加了一把鎖;
@synchronized
如果不寫nonatomic | atomic
默認(rèn)是atomic
線程間的通信方法
- 就這五個方法
- 把SEL丟到某一個線程去執(zhí)行
/**
waitUntilDone : 當(dāng)前線程是否等待SEL執(zhí)行完再繼續(xù)
*/
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
GCD(重點(diǎn))
GCD并不是多線程技術(shù);屬于并發(fā)解決技術(shù);
- GCD是蘋果為了適配多核的并行運(yùn)算提出的解決方案
- GCD會自動利用更多的CPU內(nèi)核(比如雙核亏钩、四核)
- GCD會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)欺旧、銷毀線程)
- 程序員只需要告訴GCD想要執(zhí)行什么任務(wù)姑丑,不需要編寫任何線程管理代碼
GCD的使用就2個步驟
定制任務(wù)
將任務(wù)添加到隊(duì)列中
GCD中有2個核心概念
- 任務(wù):執(zhí)行什么操作
- 執(zhí)行任務(wù)方式有兩種; 同步/異步
- 同步:不會到線程池里面去獲取子線程!
- 異步:只要有任務(wù),就會到線程池取子線程!(主隊(duì)列除外!)
- 執(zhí)行任務(wù)方式有兩種; 同步/異步
- 隊(duì)列:用來存放任務(wù)
- 串行:一個接一個的調(diào)度任務(wù)
- 并行:可以同時調(diào)度多個任務(wù)
GCD會自動將隊(duì)列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行
任務(wù)的取出遵循隊(duì)列的FIFO原則:先進(jìn)先出辞友,后進(jìn)后出
/**
同步執(zhí)行方法,這句話不執(zhí)行完,就不會執(zhí)行下一個任務(wù),同步執(zhí)行不會開啟線程
*/
//1.創(chuàng)建隊(duì)列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//2.任務(wù)添加到隊(duì)列中
//2.1 定義任務(wù) -- block
void(^task)() = ^{
NSLog(@"%@",[NSThread currentThread]);
};
//2.2 添加任務(wù)到隊(duì)列,并且會執(zhí)行
dispatch_sync(q, task);
/**
異步執(zhí)行任務(wù) 哥么如果任務(wù)沒有執(zhí)行完畢,可以不用等待,異步執(zhí)行下一個任務(wù)
具備開啟線程的能力! 異步通常又是多線程的代名詞!!
*/
-(void)gcdDemo2{
//1.創(chuàng)建隊(duì)列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//2 定義任務(wù) -- block
void(^task)() = ^{
NSLog(@"%@",[NSThread currentThread]);
};
//3. 添加任務(wù)到隊(duì)列
dispatch_async(q, task);
}
//線程間通信-切換子線程到主線程更新UI
//指定任務(wù)執(zhí)行方法 -- 異步
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//耗時操作
NSLog(@"%@",[NSThread currentThread]);
//更新UI 主隊(duì)列,就是專門負(fù)責(zé)在主線程上調(diào)度任務(wù)的隊(duì)列!
//dispatch_get_main_queue 主隊(duì)列 住能操作主線程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI%@",[NSThread currentThread]);
});
});
//MARK:串行隊(duì)列,同步任務(wù)
/**
* 不會開啟線程,會順序執(zhí)行
*/
-(void)gcdDemo1{
//1.隊(duì)列 - 串行
/**
1."ios" 隊(duì)列名稱:
2. NULL 隊(duì)列的屬性: DISPATCH_QUEUE_SERIAL 表示串行!
*/
dispatch_queue_t q = dispatch_queue_create("ios", NULL);
//2.同步執(zhí)行任務(wù)
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
}
//MARK: 串行隊(duì)列,異步任務(wù)
-(void)gcdDemo2{
/**
會開幾條線程?會順序執(zhí)行嗎?
開一條線程,順序執(zhí)行
*/
//1.隊(duì)列 - 串行
dispatch_queue_t q = dispatch_queue_create("tanzhouios", NULL);
//2.異步執(zhí)行任務(wù)
for (int i = 0; i < 10; i++) {
NSLog(@"%d------------",i);
dispatch_async(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
//哥么在主線程!
NSLog(@"come here");
}
//MARK : 并發(fā)隊(duì)列,異步執(zhí)行
-(void)gcdDemo3{
//1.隊(duì)列 - 并發(fā) DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t q = dispatch_queue_create("tanzhouios", DISPATCH_QUEUE_CONCURRENT);
//2.異步執(zhí)行任務(wù)
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
//哥么在主線程!
NSLog(@"come here");
}
//MARK : 并發(fā)隊(duì)列,同步執(zhí)行 和 串行隊(duì)列,同步執(zhí)行 效果一樣!
-(void)gcdDemo4{
// 會開線程嗎? 順序執(zhí)行? come here?
// 不會 順序 最后
//1.隊(duì)列 - 并發(fā) DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t q = dispatch_queue_create("tanzhouios", DISPATCH_QUEUE_CONCURRENT);
//2.同步執(zhí)行任務(wù)
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
//哥么在主線程!
NSLog(@"come here");
}
小結(jié):
只有異步才會開啟子線程!同步不開啟!
開幾條線程,取決于隊(duì)列,串行開一條,并發(fā)可以開多條(異步)
個人理解鎖這個問題
線程鎖:是多條線程同時訪問一個資源時需要一個互斥鎖;
GDC-死鎖:主隊(duì)列是一個串行隊(duì)列;在主隊(duì)列里添加一個同步任務(wù)會造成死鎖;互相等待的局面;
//主隊(duì)列是專門負(fù)責(zé)在主線程上調(diào)度任務(wù)的隊(duì)列 --> 不會開線程
//1.隊(duì)列 --> 已啟動主線程,就可以獲取主隊(duì)列
dispatch_queue_t q = dispatch_get_main_queue();
//2.同步任務(wù) ==> 死鎖
//走到這里添加一個同步任務(wù),主隊(duì)列的任務(wù)是不是要等他執(zhí)行完了在執(zhí)行;
dispatch_sync(q, ^{
NSLog(@"能來嗎? ");
//主隊(duì)列里的任務(wù)沒執(zhí)行完呢你想執(zhí)行這同步任務(wù),你是不是要等主隊(duì)列執(zhí)行完了才能執(zhí)行;
});
NSLog(@"come here");
注意:
主隊(duì)列不是全局隊(duì)列
全局隊(duì)列:dispatch_get_global_queue
全局隊(duì)列本質(zhì)上是并發(fā)隊(duì)列
主隊(duì)列:dispatch_get_main_queue()
主隊(duì)列是一個串行隊(duì)列
主隊(duì)列是串行的 主隊(duì)列里的任務(wù)默認(rèn)只有一個(不手動添加的話) 這個任務(wù)是同步的 主隊(duì)列里直接添加一個異步任務(wù) 不開開啟子線程
主隊(duì)列只負(fù)責(zé)主線程
//主隊(duì)列是專門負(fù)責(zé)在主線程上調(diào)度任務(wù)的隊(duì)列 --> 不會開線程
//1.隊(duì)列 --> 一啟動主線程,就可以獲取主隊(duì)列
dispatch_queue_t q = dispatch_get_main_queue();
//2.異步任務(wù)
dispatch_async(q, ^{
NSLog(@"%@",[NSThread currentThread]);
});
NSLog(@"come here");
//全局隊(duì)列
/* 參數(shù)
1. 涉及到系統(tǒng)適配
iOS 8 服務(wù)質(zhì)量
QOS_CLASS_USER_INTERACTIVE 用戶交互(希望線程快速被執(zhí)行,不要用好使的操作)
QOS_CLASS_USER_INITIATED 用戶需要的(同樣不要使用耗時操作)
QOS_CLASS_DEFAULT 默認(rèn)的(給系統(tǒng)來重置隊(duì)列的)
QOS_CLASS_UTILITY 使用工具(用來做耗時操作)
QOS_CLASS_BACKGROUND 后臺
QOS_CLASS_UNSPECIFIED 沒有指定優(yōu)先級
iOS 7 調(diào)度的優(yōu)先級
- DISPATCH_QUEUE_PRIORITY_HIGH 2 高優(yōu)先級
- DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默認(rèn)優(yōu)先級
- DISPATCH_QUEUE_PRIORITY_LOW (-2) 低優(yōu)先級
- DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后臺優(yōu)先級
提示:尤其不要選擇BACKGROUND 優(yōu)先級,服務(wù)質(zhì)量,線程執(zhí)行會慢到令人發(fā)指!!!
2. 為未來使用的一個保留,現(xiàn)在始終給0.
老項(xiàng)目中,一般還是沒有淘汰iOS 7 ,沒法使用服務(wù)質(zhì)量
*/
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//以第一個0用來設(shè)置優(yōu)先級
//第二個0還沒有定義
/*
全局隊(duì)列 & 并發(fā)隊(duì)列 區(qū)別
1> 名稱,并發(fā)隊(duì)列取名字,適合于企業(yè)開發(fā)跟蹤錯誤
2> release,在MRC 并發(fā)隊(duì)列 需要使用的
dispatch_release(q);//ARC 情況下不需要release !
全局隊(duì)列 & 串行隊(duì)列
全局隊(duì)列: 并發(fā),能夠調(diào)度多個線程,執(zhí)行效率高
- 費(fèi)電
串行隊(duì)列:一個一個執(zhí)行,執(zhí)行效率低
- 省點(diǎn)
判斷依據(jù):用戶上網(wǎng)方式
- WIFI : 可以多開線程
- 流量 : 盡量少開線程
*/
//1.隊(duì)列
dispatch_queue_t q = dispatch_queue_create("tanzhou", DISPATCH_QUEUE_CONCURRENT);
//2.全局隊(duì)列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
延遲執(zhí)行
/**
從現(xiàn)在開始,進(jìn)過多少納秒之后,讓 queue隊(duì)列,調(diào)度 block 任務(wù),異步執(zhí)行!
參數(shù):
1.when
2.queue
3.block
*/
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.00003 * NSEC_PER_SEC));
dispatch_after(when, dispatch_queue_create("tanzhou", NULL), ^{
NSLog(@"%@",[NSThread currentThread]);
});
GCD:執(zhí)行一次 常用于單利
//蘋果提供的 一次執(zhí)行機(jī)制,不僅能夠保證一次執(zhí)行!而且是線程安全的!!
static dispatch_once_t onceToken;
NSLog(@"%ld",onceToken);
//蘋果推薦使用 gcd 一次執(zhí)行,效率高
//不要使用互斥鎖,效率低!
dispatch_once(&onceToken, ^{
//只會執(zhí)行一次!!
NSLog(@"執(zhí)行了%@",[NSThread currentThread]);
});
GCD調(diào)度組
//1.隊(duì)列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//2.調(diào)度組
dispatch_group_t g = dispatch_group_create();
//3.添加任務(wù),讓隊(duì)列調(diào)度,任務(wù)執(zhí)行情況,最后通知群組
dispatch_group_async(g, q, ^{
NSLog(@"download A%@",[NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download B%@",[NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download C%@",[NSThread currentThread]);
});
//4.所有任務(wù)執(zhí)行完畢后,通知
//用一個調(diào)度組,可以監(jiān)聽全局隊(duì)列的任務(wù),主隊(duì)列去執(zhí)行最后的任務(wù)
//dispatch_group_notify 本身也是異步的!
dispatch_group_notify(g, dispatch_get_main_queue(), ^{
//更新UI,通知用戶
NSLog(@"OK %@",[NSThread currentThread]);
});