- 說下線程和進程的區(qū)別?(要注意強調(diào)進程通信和同步)
一個程序運行后,就是一個進程,默認會開啟一個線程,叫做主線程,這個線程主要用于顯示/刷新界面和處理UI事件(比如點擊事件,滾動事件,拖拽事件),要注意的是不要將比較耗時的操作放到主線程,耗時的操作會卡主主線程,影響用戶體驗,如果程序中有比較耗時的操作,比如網(wǎng)絡(luò)請求,就會創(chuàng)建一個線程用于網(wǎng)絡(luò)請求, 所以線程是程序的基本執(zhí)行單元, 一個進程可以有多個線程,但是一個進程至少有一個線程.系統(tǒng)資源是分配給進程的,被進程的所有線程共享
知識點
-
進程
- 進程是指系統(tǒng)中正在運行的一個應用程序
- 每個進程之間是相互獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)
-
線程
- 線程是進程的基本執(zhí)行單元, 一個進程(程序)的所有任務都在線程中執(zhí)行
-
進程與線程的區(qū)別
- 一個線程只能屬于一個進程,而一個進程可以有多個線程, 但至少有一個線程, 線程是操作系統(tǒng)可識別的最小執(zhí)行和調(diào)度單位
- 資源分配給進程, 同一進程的所有線程共享該進程的所有資源
多線程(一個進程可以開啟多條線程, 每條線程可以并行執(zhí)行不同的任務,這個技術(shù)可以提高程序的執(zhí)行效率)
-
多線程原理
- 同一時間, CPU只能處理一條線程,只有一條線程在工作
- 多線程并發(fā)執(zhí)行,其實是CPU快速在多條線程之間切換
- 如果CPU切換的足夠快,就造成了多線程并發(fā)執(zhí)行的假象
多線程優(yōu)點
- 能提高程序的執(zhí)行效率
- 能提高CPU,內(nèi)存的利用率
多線程缺點
- 創(chuàng)建多線程是有開銷的, 開辟空間, 線程間的通信問題
- 開啟大量線程,會降低程序的性能(線程越多, 在調(diào)度線程上的開銷就越大)
- 線程之間的通信, 多線程的數(shù)據(jù)共享
多線程中會出現(xiàn)的問題
- 臨界代碼段 - 指的是不能同時被兩個線程訪問的代碼段, 比如一個變量,被并發(fā)進程訪問后可能會改變變量值, 造成數(shù)據(jù)污染(數(shù)據(jù)共享問題)
- 競態(tài)條件 - 當多個線程同時訪問共享數(shù)據(jù)時, 會放生爭用情形,第一個線程讀取改變了一個變量的值, 第二個線程也讀取改變了這個變量的值, 兩個線程通知操作了該變量, 此時他們會發(fā)生競爭來看哪個線程會最后寫入這個變量, 最后寫入的值將會被保留下來
- 死鎖 - 多個線程都要等待對方完成某個操作才能進行下一步, 這時就發(fā)生了死鎖
- 線程安全 - 一段線程安全的代碼(對象), 可以同時被多個線程或并發(fā)的任務調(diào)度,不會產(chǎn)生問題,非線程安全的只能按次序被訪問
如何保證線程安全
- 所有Mutable(可變)對象都是非線程安全的, 所有Immutable對象都是線程安全的, 使用Mutable對象,一定要用同步鎖來同步來訪問(@synchronized)
- 互斥鎖: 能夠防止多線程搶奪資源造成的數(shù)據(jù)安全問題, 但是需要消耗大量的資源
- 原子屬性(atomic)加鎖 atomic和nonatomic是否為屬性的setter方法加鎖
iOS中多線程的實現(xiàn)方案

線程生命周期
- 新建:實例化線程對象
- 就緒:向線程對象發(fā)送start消息, 線程對象被加入可調(diào)度線程池等待CPU調(diào)度
- 運行:線程執(zhí)行
- 阻塞:若需要,可以使線程休眠或者鎖, 阻塞線程執(zhí)行
- 死亡: 線程執(zhí)行完畢或被終止
- 還有exit和cancel
- pthread(基于C,跨平臺,使用難度大,需要程序員管理線程生命周期)
- NSThread(基于OC, 簡單易用,可直接操作線程對象,需要程序員管理線程生命周期)
- GCD(基于C,用于替代NSThread的線程技術(shù), 自動管理線程聲明周期)
- NSOperation(基于GCD,就是讓GCD更加面向?qū)ο? 自動管理線程聲明周期)
NSOperation和GCD
GCD優(yōu)點: GCD主要與block結(jié)合使用,代碼簡潔高效
- NSOperation擁有更多的函數(shù)可用,NSOperationQueue是在GCD基礎(chǔ)上實現(xiàn)的,是GCD更高一層的抽象
- 在NSOperationQueue中, 可以建立各個NSOperation之間的依賴關(guān)系
- NSOperationQueue支持KVO,可以監(jiān)測operation是否在執(zhí)行(isExecuted), 是否結(jié)束(isFinished),是否取消(isCanceld)
- GCD只支持FIFO的隊列(FIFO:先進先出), 而NSOperationQueue可以調(diào)整隊列的執(zhí)行順序(通過調(diào)整權(quán)重),NSOperationQueue可以方便的管理并發(fā),NSOperation之間的優(yōu)先級
使用NSOperationQueue的情況
- 各個操作之間有依賴關(guān)系
- 操作需要取消, 暫停
- 并發(fā)管理
- 控制操作之間的優(yōu)先級
- 限制同時執(zhí)行的線程數(shù)量
- 讓線程在某時刻停止/繼續(xù)
使用GCD的情況
一般的需求很簡單的多線程操作,用GCD
GCD(Grand Central Dispatch)大中樞派發(fā)
//回到主線程操作
//1
[self performSelectorOnMainThread:@selector(downLoad) withObject:nil waitUntilDone:YES];
//2,GCD
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"要執(zhí)行的操作");
});
//3,NSOperationQueue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"要執(zhí)行的操作");
}];
GCD核心概念 任務和隊列
有兩個執(zhí)行任務的函數(shù):(同步,異步):區(qū)別是會不會創(chuàng)建的新的線程 有兩種隊列(并行,串行)
- 串行:順序執(zhí)行
- 并行:有多少線程就在所有線程中同時執(zhí)行任務
GCD常用
//GCD默認提供的全局并行隊列 參數(shù)1: 隊列的優(yōu)先級 參數(shù)2: 保留參數(shù)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//GCD默認提供的全局串行隊列 - 主隊列
dispatch_queue_t main = dispatch_get_main_queue();
//手動創(chuàng)建并發(fā)隊列 參數(shù)1: 隊列名 參數(shù)2: 隊列屬性
dispatch_queue_t queueCreate = dispatch_queue_create("Never", DISPATCH_QUEUE_CONCURRENT);
//GCD線程間通信
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//耗時操作
dispatch_async(dispatch_get_main_queue(), ^{
//回主線程
});
});
GCD其他用法
//GCD延遲操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//要執(zhí)行的代碼
});
//GCD一次性執(zhí)行
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//只會執(zhí)行一次的代碼: 如創(chuàng)建單例
});
//GCD柵欄函數(shù) - 在異步函數(shù)中控制任務的執(zhí)行順序, 只有當柵欄函數(shù)執(zhí)行完畢之后才會執(zhí)行后面的任務
//注意: 柵欄函數(shù)不能使用全局并發(fā)隊列
dispatch_barrier_async(dispatch_get_main_queue(), ^{
});
//GCD的快速遍歷
dispatch_apply(1000, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%ld", index);
});
//GCD調(diào)度組(Dispatch_groups): Dispatch_group會在整個組的任務都完成時發(fā)出通知, 這些任務可以是同步的, 也可以是異步的, 即便在不同的隊列也行, 對多個異步任務的完成進行監(jiān)控
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//創(chuàng)建一個dispatch_group
dispatch_group_t group = dispatch_group_create();
//手動通知進入dispatch_group_t(必須保證 dispatch_group_enter 和 dispatch_group_leave成對出現(xiàn))
dispatch_group_enter(group);
NSLog(@"任務一");
[NSThread sleepForTimeInterval:3.0];
NSLog(@"任務二");
NSLog(@"任務三");
//手動通知結(jié)束
dispatch_group_leave(group);
//等待所有任務執(zhí)行完畢
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
//刷新UI
});
});
//用法二
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 創(chuàng)建一個dispatch_group
dispatch_group_t group = dispatch_group_create();
// 手動通知進入dispatch_group_t(你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對出現(xiàn),否則你可能會遇到詭異的崩潰問題)
dispatch_group_enter(group);
NSLog(@"任務1");
[NSThread sleepForTimeInterval:3.0];
NSLog(@"任務2");
NSLog(@"任務3");
// 手動通知結(jié)束dispatch_group_t
dispatch_group_leave(group);
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"拉回主線程刷新UI");
});
});
GCD可以取消, 暫停和重新開始任務嗎?
iOS8之后可以調(diào)用dispatch_block_cancel來取消
- (void)gcdBlockCancel{
dispatch_queue_t queue = dispatch_queue_create("com.gcdtest.www", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block1 = dispatch_block_create(0, ^{
sleep(5);
NSLog(@"block1 %@",[NSThread currentThread]);
});
dispatch_block_t block2 = dispatch_block_create(0, ^{
NSLog(@"block2 %@",[NSThread currentThread]);
});
dispatch_block_t block3 = dispatch_block_create(0, ^{
NSLog(@"block3 %@",[NSThread currentThread]);
});
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_block_cancel(block3);
}
但是: dispatch_block_cancel也只能取消尚未執(zhí)行的任務, 對正在執(zhí)行的任務不起作用
dispatch_suspend: 暫停
dispatch_resume: 恢復執(zhí)行
NSOperation:配合使用NSOperation和NSOperationQueue實現(xiàn)多線程編程
- 先將需要執(zhí)行的操作封裝到一個NSOperation對象中
- 然后將NSOperation對象添加到NSOperationQueue中
- 系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來
- 取出來的NSOperation封裝的操作放到一條新線程中執(zhí)行
創(chuàng)建操作
NSOperation是個抽象類, 不能用來封裝操作, 只有使用它的子類來封裝操作
- 使用子類NSInvocationOperation
- 使用子類NSBlockOperation
- 自定義繼承自NSOperation的子類, 通過實現(xiàn)內(nèi)部相應的方法來封裝操作
//1. 創(chuàng)建NSInvocationOperation對象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task) object:nil];
//2. 調(diào)用start方法執(zhí)行操作
[op start];
//1. 創(chuàng)建NSBlockOperation對象
NSBlockOperation *opBlock = [NSBlockOperation blockOperationWithBlock:^{
//耗時操作
}];
//2. 調(diào)用start開始執(zhí)行操作
[opBlock start];
創(chuàng)建隊列
NSOperationQueue一共有兩種隊列:主隊列,自定義隊列. 其中自定義隊列同時包含并發(fā)功能
//主隊列
NSOperationQueue *queueMain = [NSOperationQueue mainQueue];
//自定義隊列
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
最大并發(fā)操作數(shù)maxConcurrentOperationCount
- maxConcurrentOperationCount 默認情況下為-1, 表示不進行限制, 可進行并發(fā)執(zhí)行
- maxConcurrentOperationCount = 1時, 表示是串行隊列
- maxConcurrentOperationCount > 1時, 并發(fā)隊列
操作依賴addDependency
- addDependency 添加依賴
- removeDependency 移除依賴
- dependencies 屬性dependencies是一個數(shù)組, 表示當前操作開始前要完成這個數(shù)組內(nèi)的所有操作
NSOperation優(yōu)先級queuePriority
// 優(yōu)先級的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) { NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
NSOperation, NSOperationQueue線程同步和線程安全(NSOperation非線程安全)
- 線程安全:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。 - 線程同步:可理解為線程 A 和 線程 B 一塊配合,A 執(zhí)行到一定程度時要依靠線程 B 的某個結(jié)果,于是停下來,示意 B 運行;B 依言執(zhí)行,再將結(jié)果給 A;A 再繼續(xù)操作
要安全就加鎖(GCD也不是線程安全的)
NSOperation常用
- (void)cancel; 取消操作
- (BOOL)isFinished;是否結(jié)束
- (BOOL)isCancelled;是否取消
- (BOOL)isExecuting;是否正在運行
- (BOOL)isReady;是否準備就緒
- (void)waitUntilFinished; 阻塞當前線程,直到該操作結(jié)束??捎糜诰€程執(zhí)行順序的同步。
- (void)setCompletionBlock:(void (^)(void))block; completionBlock 會在當前操作執(zhí)行完畢時執(zhí)行 completionBlock。
- (void)addDependency:(NSOperation *)op; 添加依賴,使當前操作依賴于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op; 移除依賴,取消當前操作對操作 op 的依賴。
@property (readonly, copy) NSArray<NSOperation *> *dependencies; 在當前操作開始執(zhí)行之前完成執(zhí)行的所有操作對象數(shù)組。
NSOperationQueue常用屬性和方法
-
取消/暫停/恢復操作
- (void)cancelAllOperations; 可以取消隊列的所有操作。
- (BOOL)isSuspended; 判斷隊列是否處于暫停狀態(tài)。 YES 為暫停狀態(tài),NO 為恢復狀態(tài)。
- (void)setSuspended:(BOOL)b; 可設(shè)置操作的暫停和恢復,YES 代表暫停隊列,NO 代表恢復隊列。
-
操作同步
- (void)waitUntilAllOperationsAreFinished; 阻塞當前線程,直到隊列中的操作全部執(zhí)行完畢。
-
添加/獲取操作`
- (void)addOperationWithBlock:(void (^)(void))block; 向隊列中添加一個 NSBlockOperation 類型操作對象。
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向隊列中添加操作數(shù)組,wait 標志是否阻塞當前線程直到所有操作結(jié)束
- (NSArray *)operations; 當前在隊列中的操作數(shù)組(某個操作執(zhí)行結(jié)束后會自動從這個數(shù)組清除)。
- (NSUInteger)operationCount; 當前隊列中的操作數(shù)。
-
獲取隊列
- (id)currentQueue; 獲取當前隊列,如果當前線程不是在 NSOperationQueue 上運行則返回 nil。
- (id)mainQueue; 獲取主隊列。