背景
前段時間鍛煉身體胃出血的事情徊哑,每天想著這個事情袜刷,害怕有什么大問題!這周一才預約下周一去做胃鏡檢查莺丑,又要擔心好幾天爸贰!我在思考如果真有大病梢莽,醫(yī)院這種制度得耽誤多少病人萧豆,因為這種制度而耽誤多少人的最佳治療時間?
我們還是要多讀書的昏名,因為最近動物考古學家一行在飯店吃飯涮雷,點的羊腿中赫然發(fā)現(xiàn)豬骨,憤而與店家對質(你們知道我們是干什么的嗎轻局?我們連一根毛都知道出自什么動物身上)洪鸭,最后店家給免單了(我怎么就沒這本事呢_)!
為什么要讀書仑扑?
你失戀時…你會說:“人生若只如初見览爵,何事秋風悲畫扇。等閑變卻故人心镇饮,卻道故人心易變蜓竹。”而不是千萬遍呼喊:“藍瘦,香菇梅肤!”
當你看到夕陽余暉…你腦海里浮現(xiàn)的是:“落霞與孤鶩齊飛司蔬,秋水共長天一色∫毯”而不是:“臥槽,這多鳥肺缕,這鳥真肥啊左医,真好看,真他媽太好看了同木!”
所以我們要多讀書浮梢,多學習,這樣才能出去裝X彤路!
前言
說到多線程編程大家肯定不陌生秕硝,也是面試和工作中經常碰到的,所以網上各種博客層出不窮洲尊,光在簡書上搜索iOS 多線程就有1w+的文章远豺,所以我也來湊湊熱鬧混個臉熟(不要臉啊)坞嘀!
談到多線程就到說到線程和進程躯护,就要說到NSThread、GCD丽涩、NSOperation棺滞!就要提到RunLoop,你既然提到了RunLoop了矢渊,他兄弟Runtime肯定要提到吧(他倆不是一回事)继准,這些我都會在后面一一道來啊矮男!我能從詩詞歌賦談到人生哲學移必,從人生哲學談到詩詞歌賦。下面就跟我一起來裝X昂灵!
基本知識
1. 進程(process)
- 進程是指在系統(tǒng)中正在運行的一個應用程序避凝,就是一段程序的執(zhí)行過程。
- 每個進程之間是相互獨立的, 每個進程均運行在其專用且受保護的內存空間內眨补。
- 進程是一個具有一定獨立功能的程序關于某次數(shù)據(jù)集合的一次運行活動管削,它是操作系統(tǒng)分配資源的基本單元。
- 進程狀態(tài):進程有三個狀態(tài)撑螺,就緒含思,運行和阻塞。就緒狀態(tài)其實就是獲取了除cpu外的所有資源,只要處理器分配資源馬上就可以運行含潘。運行態(tài)就是獲取了處理器分配的資源饲做,程序開始執(zhí)行,阻塞態(tài)遏弱,當程序條件不夠時盆均,需要等待條件滿足時候才能執(zhí)行,如等待I/O操作的時候漱逸,此刻的狀態(tài)就叫阻塞態(tài)泪姨。
2. 線程(thread)
- 一個進程要想執(zhí)行任務,必須要有線程,至少有一條線程
- 一個進程的所有任務都是在線程中執(zhí)行
- 每個應用程序想要跑起來,最少也要有一條線程存在,其實應用程序啟動的時候我們的系統(tǒng)就會默認幫我們的應用程序開啟一條線程,這條線程也叫做'主線程',或者'UI線程'
3. 進程和線程的關系
- 線程是進程的執(zhí)行單元,進程的所有任務都在線程中執(zhí)行饰抒!
- 線程是 CPU 調用的最小單位
- 進程是 CPU 分配資源和調度的單位
- 一個程序可以對應過個進程,一個進程中可有多個線程,但至少要有一條線程
- 同一個進程內的線程共享進程資源
舉個例子:進程就好比公司中的一個個部門,線程則代表著部門中的同事,而主線程當然是我們的老板了,一個公司不能沒有老板,一個程序不能沒有線程其實都是一個道理.
相同點:進程和線程都是有操作系統(tǒng)所提供的程序運行的基本單元肮砾,系統(tǒng)利用該基本單元實現(xiàn)系統(tǒng)對應用程序的并發(fā)性。
不同點:
進程和線程的主要差別在于他們是不同的操作系統(tǒng)資源管理方式袋坑。
進程有獨立的地址空間仗处,一個進程crash后,在保護模式下不會對其他進程產生影響枣宫。
而線程只是一個進程中的不同執(zhí)行路徑婆誓。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間镶柱。一個線程crash就等于整個進程crash
多進程的程序比多線程的程序健壯旷档,但在進程切換時,耗費資源較大歇拆,效率要差一些鞋屈。但對于一些要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程故觅,不能用進程厂庇。
-
優(yōu)缺點:
- 進程執(zhí)行開銷大,但利于資源的管理和保護输吏。
- 線程執(zhí)行開銷小权旷,但不利于資源的管理和保護。線程適合于在SMP(多核處理機)機器上運行贯溅。
4. 多線程
Mac拄氯、iPhone的操作系統(tǒng)OS X、iOS根據(jù)用戶的指示啟動應用程序后它浅,首先便將包含在應用程序中的CPU命令列配置到內存中译柏。CPU從應用程序知道的地址開始,一個一個執(zhí)行CPU命令列姐霍。
在OC的if或for語句等控制語句或函數(shù)調用的情況下鄙麦,執(zhí)行命令列的地址會遠離當前的位置(位置遷移)典唇。但是,由于一個CPU一次只能執(zhí)行一個命令胯府,不能執(zhí)行某處分開的并列的兩個命令介衔,因此通過CPU執(zhí)行的CPU命令列就好比一條無分叉的大道,其執(zhí)行不會出現(xiàn)分歧骂因。
這里所說的“1個CPU執(zhí)行的CPU命令列為一條無分叉路徑”炎咖,即為“線程”
這種無分叉路徑不只1條,存在有多條時即為“多線程”寒波。 1個CPU核執(zhí)行多條不同路徑上的不同命令塘装。
OS X和iOS的核心XNU內核在發(fā)生操作系統(tǒng)事件時(如每隔一定時間,喚起系統(tǒng)調用等情況)會切換執(zhí)行路徑影所。例如CPU的寄存器等信息保存到各自路徑專用的內存塊中,從切換目標路徑專用的內存塊中僚碎,復原CPU寄存器等信息猴娩,繼續(xù)執(zhí)行切換路徑的CPU命令列。這被稱為“上下文切換”
由于使用多線程的程序可以在某個線程和其他線程之間反復多次進行上下文切換勺阐,因此看上去就好像1個CPU核能夠并列地執(zhí)行多個線程一樣卷中。而且在具有多個CPU核的情況下,就不是“看上去像”了渊抽,而是真的提供了多個CPU核并行執(zhí)行多個線程的技術。
這種利用多線程編程的技術就被稱為“多線程編程”
但是,多線程編程實際上是一種易發(fā)生各種問題的編程技術引润。比如多個線程更新相同資源會導致數(shù)據(jù)的不一致(數(shù)據(jù)競爭)告抄、停止等待事件的線程會導致多個線程相互等待(死鎖)、使用太多線程會消耗大量內存等愤估。
4.多線程的優(yōu)點和缺點
-
優(yōu)點
- 能適當?shù)奶岣叱绦虻膱?zhí)行效率
- 能適當提高資源利用率(CPU 內存利用率)
-
缺點
- 開啟線程需要占用一定的內存空間,如果開啟大量的線程,則會占用大量的內存空間,降低程序的性能
- 線程越多,CPU 在調度線程上的開銷就越大
- 程序設計更加復雜: 比如線程之間的通信, 多線程的數(shù)據(jù)共享
5.多線程實際應用
- 使用單例模式時帮辟,可以使用GCD
- 耗時操作放入子線程處理,完成后回主線程顯示
- 從數(shù)據(jù)庫讀取大量數(shù)據(jù)玩焰,可開辟子線程操作
- 處理音頻由驹、視頻數(shù)據(jù)時,在子線程處理
- 數(shù)據(jù)同步操作昔园,如百度云蔓榄,可在子線程進入后臺后開始同步
6.主線程
- 也就是應用程序啟動的時候,系統(tǒng)默認幫我們創(chuàng)建的線程,稱之為'主線程'或者是'UI線程';
- 主線程的作用一般都是用來顯示或者刷新UI界面例如:點擊,滾動,拖拽等事件
7.串行(Serial)和 并行(Parallelism)
串行和并行描述的是任務和任務之間的執(zhí)行方式. 串行是任務A執(zhí)行完了任務B才能執(zhí)行, 它們倆只能順序執(zhí)行. 并行則是任務A和任務B可以同時執(zhí)行.
8.同步(Synchronous) 和 異步(Asynchronous)
同步和異步描述的其實就是函數(shù)什么時候返回. 比如用來下載圖片的函數(shù)A: {download image}, 同步函數(shù)只有在image下載結束之后才返回, 下載的這段時間函數(shù)A只能搬個小板凳在那兒坐等... 而異步函數(shù), 立即返回. 圖片會去下載, 但函數(shù)A不會去等它完成. So, 異步函數(shù)不會堵塞當前線程去執(zhí)行下一個函數(shù)!
9.并發(fā)(Concurrency) 和 并行(Parallelism)
用Ray大神的示意圖和說明來解釋一下:
并發(fā)是程序的屬性(property of the program), 而并行是計算機的屬性(property of the machine).
還是很抽象? 那我再來解釋一下, 并行和并發(fā)都是用來讓不同的任務可以"同時執(zhí)行", 只是并發(fā)是偽同時, 而并行是真同時. 假設你有任務T1和任務T2(這里的任務可以是進程也可以是線程):
a. 首先如果你的CPU是單核的, 為了實現(xiàn)"同時"執(zhí)行T1和T2, 那只能分時執(zhí)行, CPU執(zhí)行一會兒T1后馬上再去執(zhí)行T2, 切換的速度非常快(這里的切換也是需要消耗資源的, context switch), 以至于你以為T1和T2是同時執(zhí)行了(但其實同一時刻只有一個任務占有著CPU).
b. 如果你是多核CPU, 那么恭喜你, 你可以真正同時執(zhí)行T1和T2了, 在同一時刻CPU的核心core1執(zhí)行著T1, 然后core2執(zhí)行著T2, great!
其實我們平常說的并發(fā)編程包括狹義上的"并行"和"并發(fā)", 你不能保證你的代碼會被并行執(zhí)行, 但你可以以并發(fā)的方式設計你的代碼. 系統(tǒng)會判斷在某一個時刻是否有可用的core(多核CPU核心), 如果有就并行(parallelism)執(zhí)行, 否則就用context switch來分時并發(fā)(concurrency)執(zhí)行.
Parallelism requires Concurrency, but Concurrency does not guarantee Parallelism!(并行要求并發(fā)性默刚,但并發(fā)并不能保證并行性)
iOS中的多線程
類型 | 簡介| 實現(xiàn)語言| 線程生命周期|使用頻率|
---- |----- | ----- |-----
pthread| 1. 一套通用的多線程API
2. 適用于 Unix / Linux / Windows 等系統(tǒng)
3. 跨平臺\可移植
4. 使用難度大|C|程序員管理|幾乎不用
NSThread |1. 使用更加面向對象
2. 簡單易用甥郑,可直接操作線程對象|OC|程序員管理|偶爾使用
GCD|1. 旨在替代NSThread等線程技術
2. 充分利用設備的多核
3. 基于 C 的底層的 API|C|自動管理|經常使用
NSOperation|1. 是基于 GCD 實現(xiàn)的 Objective-C API
2. 比GCD多了一些更簡單實用的功能
3. 使用更加面向對象|OC|自動管理|經常使用
小結:上面只是介紹了多線程涉及的基本概念和基本知識(要理解啊)羡棵,防止在后面學習的過程中混淆壹若。下面才剛剛踏上多線程編程的道路:NSThread、GCD、NSOperation店展,pthread就不介紹了(有興趣的自行學習)养篓,因為我在開發(fā)中沒用過,用到再補充吧(哪那么多借口赂蕴,不會就不會唄)柳弄。下面先介紹簡單的NSThread。GCD和NSOperation會單獨拿出來介紹(東西可能比較多)概说!
NSThread的使用
創(chuàng)建線程的方式
1碧注、 通過NSThread的對象方法
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
2、 通過NSThread的類方法
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
3糖赔、通過NSObject的分類方法
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
NSThread代碼Demo
- (IBAction)downloadAction:(UIButton *)sender {
// [self categoryNSthreadMethod];
// [self classNSthreadMethod];
[self objectNSthreadMethod];
}
//通過NSObject的分類方法開辟線程
- (void)categoryNSthreadMethod{
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
}
//通過NSThread類方法開辟線程
- (void)classNSthreadMethod{
//異步1
// [NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
//異步方式2
[NSThread detachNewThreadWithBlock:^{
[self downloadImage];
}];
}
//通過NSThread對象方法去下載圖片
- (void)objectNSthreadMethod{
//創(chuàng)建一個程序去下載圖片
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(downloadImage) object:nil];
//開啟線程
[thread start];
thread.name = @"imageThread";
}
//下載圖片
- (void)downloadImage{
NSURL *url = [NSURL URLWithString:@"https://p1.bpimg.com/524586/475bc82ff016054ds.jpg"];
//線程延遲10s
[NSThread sleepForTimeInterval:5.0];
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(@"downLoadImage:%@",[NSThread currentThread]);//在子線程中下載圖片
//在主線程更新UI
[self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];
}
//更新imageView
- (void)updateImage:(NSData *)data{
NSLog(@"updateImage:%@",[NSThread currentThread]);//在主線程中更新UI
//將二進制數(shù)據(jù)轉換為圖片
UIImage *image=[UIImage imageWithData:data];
//設置image
self.imageView.image=image;
}
線程狀態(tài)
-
新建狀態(tài)
- 通過上面3中方法實 例化線程對象
- 程序還沒有開始運行線程中的代碼
-
就緒狀態(tài)
向線程對象發(fā)送 start 消息萍丐,線程對象被加入 可調度線程池 等待 CPU 調度
detachNewThreadSelector
方法
detachNewThreadWithBlock
和
performSelectorInBackground
方法
會直接實例化一個線程對象并加入 可調度線程池處于就緒狀態(tài)的線程并不一定立即執(zhí)行線程里的代碼,線程還必須同其他線程競爭CPU時間放典,只有獲得CPU時間才可以運行線程逝变。
-
運行狀態(tài)
- CPU 負責調度可調度線程池中線程的執(zhí)行
- 線程執(zhí)行完成之前(死亡之前),狀態(tài)可能會在就緒和運行之間來回切換
- 就緒和運行之間的狀態(tài)變化由 CPU 負責奋构,程序員不能干預
-
阻塞狀態(tài)
- 所謂阻塞狀態(tài)是正在運行的線程沒有運行結束壳影,暫時讓出CPU,這時其他處于就緒狀態(tài)的線程就可以獲得CPU時間弥臼,進入運行狀態(tài)宴咧。
- 線程通過調用sleep方法進入睡眠狀態(tài)
- 線程調用一個在I/O上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回到它的調用者
- 線程試圖得到一個鎖径缅,而該鎖正被其他線程持有掺栅;
- 線程在等待某個觸發(fā)條件
+ (void)sleepUntilDate:(NSDate *)date;//休眠到指定日期 + (void)sleepForTimeInterval:(NSTimeInterval)ti;//休眠指定時長 @synchronized(self):互斥鎖 sleep(unsigned int) __DARWIN_ALIAS_C(sleep);
-
死亡狀態(tài)
- 正常死亡
- 線程執(zhí)行完畢
- 非正常死亡
- 當滿足某個條件后,在線程內部自己中止執(zhí)行(自殺)
- 當滿足某個條件后芥驳,在主線程給其它線程打個死亡標記(下圣旨),讓子線程自行了斷.(被逼著死亡)
- 正常死亡
線程通信
線程在運行過程中柿冲,可能需要與其它線程進行通信,如在主線程中修改界面等等兆旬,可以使用如下接口:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
線程的屬性
-
thread.name = @"imageThread";
給線程添加一個名字假抄,方便后面出現(xiàn)問題的追蹤! -
[NSThread currentThread];
獲取當前的進程丽猬,打印出來name和number,如果Number為1宿饱,則為主線程 -
thread.isMainThread;
判斷當前線程是否是主線程 -
[NSThread isMultiThreaded];
判斷進程當前是否是多線程 -
thread.threadPriority = 0.5;
threadPriority是線程的優(yōu)先級,最高是1.0脚祟,最低為0.0谬以;默認我們創(chuàng)建的優(yōu)先級是0.5; -
thread.stackSize
默認情況下主線程和子線程在棧區(qū)大小都是512k - 線程執(zhí)行狀態(tài)
@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);//是否正在執(zhí)行
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);//是否完成
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);//是否取消
線程的同步與鎖
線程的同步與鎖什么時候會遇到由桌?就是我們在線程公用資源的時候为黎,導致的資源競爭邮丰。舉個例子:多個窗口同時售票的售票系統(tǒng)!
#import "SellTicketsViewController.h"
@interface SellTicketsViewController (){
NSInteger tickets;//總票數(shù)
NSInteger count;//當前賣出去票數(shù)
}
@property (nonatomic, strong) NSThread* ticketsThreadOne;
@property (nonatomic, strong) NSThread* ticketsThreadTwo;
@property (nonatomic, strong) NSLock *ticketsLock;
@end
@implementation SellTicketsViewController
- (void)viewDidLoad {
[super viewDidLoad];
tickets = 100;
count = 0;
//鎖對象
self.ticketsLock = [[NSLock alloc] init];
self.ticketsThreadOne = [[NSThread alloc] initWithTarget:self selector:@selector(sellAction) object:nil];
self.ticketsThreadOne.name = @"thread-1";
[self.ticketsThreadOne start];
self.ticketsThreadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(sellAction) object:nil];
self.ticketsThreadTwo.name = @"thread-2";
[self.ticketsThreadTwo start];
}
- (void)sellAction{
while (true) {
//上鎖
[self.ticketsLock lock];
if (tickets > 0) {
[NSThread sleepForTimeInterval:0.5];
count = 100 - tickets;
NSLog(@"當前總票數(shù)是:%ld----->賣出:%ld----->線程名:%@",tickets,count,[NSThread currentThread]);
tickets--;
}else{
break;
}
//解鎖
[self.ticketsLock unlock];
}
}
@end
通過上面的Demo應該理解線程的同步以及鎖的使用問題
[myLock lock]
資源處理....
[myLock unLock];
總結:又到了一篇最后的總結問題了铭乾,這篇主要講解了一些基本概念剪廉、基本知識點、以及簡單的NSThread炕檩。一些基本知識點在后面的博客中也會用到斗蒋,希望你們能理解,更好的理解多線程笛质!今天的博客就寫到這里了泉沾。
如果我的文章對你有幫助,請點個喜歡妇押,關注我(樓主真是厚顏無恥磅尉俊),如果還沒看過癮那就期待下期的GCD的知識吧_敲霍!
參考資料:
http://www.reibang.com/p/9f2fc08f9947
http://www.reibang.com/p/ebb3e42049fd
http://www.reibang.com/p/7ce30a806c51
書籍:Objective-C高級編程 iOS與OS X多線程和內存管理