一、進(jìn)程與線程
1.進(jìn)程:
系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi)
2.線程:
1個(gè)進(jìn)程要想執(zhí)行任務(wù),必須得有線程(每一個(gè)進(jìn)程至少要有一條線程);線程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行
1個(gè)線程中執(zhí)行任務(wù)是串行的,同一時(shí)間只能執(zhí)行一個(gè)任務(wù),順序執(zhí)行所有任務(wù);
4.多線程:
一個(gè)進(jìn)程可以開啟多條線程,每條線程可以并發(fā)(同時(shí))執(zhí)行不同的任務(wù);多線程可以提高程序的執(zhí)行效率
多線程原理:
同一時(shí)間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行);多線程并發(fā)(同時(shí))執(zhí)行,其實(shí)是CPU快速地在多條線程之間調(diào)度(切換)如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
5.主線程:
一個(gè)iOS程序運(yùn)行后,默認(rèn)會開啟1條線程,稱為“主線程”或“UI線程”
主線程的主要作用
<1>顯示\刷新UI界面
<2>處理UI事件(比如點(diǎn)擊事件、滾動事件、拖拽事件等)
主線程的使用注意
<1>別將比較耗時(shí)的操作放到主線程中
<2>耗時(shí)操作會卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種“卡”的壞體驗(yàn)
二、多線程技術(shù)
column | column | |||
---|---|---|---|---|
計(jì)數(shù)方案 | 簡介 | 語言 | 線程生命周期 | 使用頻率 |
pthread | 一套通用的多線程API,適用于Unix\Linux\Windows等系統(tǒng),跨平臺\可移植,使用難度大 | C | 程序員管理 | 幾乎不用 |
NSTread | 使用更加面向?qū)ο?,簡單易用,可直接操作線程對象 | OC | 程序員管理 | 偶爾使用 |
GCD | 旨在代替NSTread等多線程技術(shù),充分利用設(shè)備的多核 | C | 自動管理 | 經(jīng)常使用 |
NSOperation | 基于GCD(底層是GCD),比GCD多了一些更簡單使用的功能,使用更加面向?qū)ο?/td> | OC | 自動管理 | 經(jīng)常使用 |
1.NSTread
一個(gè)NSThread對象就代表一條線程
<1>創(chuàng)建、啟動線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
// 線程一啟動,就會在線程thread中執(zhí)行self的run方法
<2>主線程相關(guān)用法
+ (NSThread *)mainThread; // 獲得主線程
- (BOOL)isMainThread; // 是否為主線程
+ (BOOL)isMainThread; // 是否為主線程
<3>獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];
<4>線程的調(diào)度優(yōu)先級
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
- (double)threadPriority;
- (BOOL)setThreadPriority:(double)p;
調(diào)度優(yōu)先級的取值范圍是0.0 ~ 1.0,默認(rèn)0.5,值越大,優(yōu)先級越高
自己開發(fā)時(shí),建議一般不要修改優(yōu)先級
<5>線程的名字
- (void)setName:(NSString *)n;
- (NSString *)name;
<6>創(chuàng)建線程后自動啟動線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
<7>隱式創(chuàng)建并啟動線程
[self performSelectorInBackground:@selector(run) withObject:nil];
<8>啟動線程
- (void)start;
// 進(jìn)入就緒狀態(tài) -> 運(yùn)行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢,自動進(jìn)入死亡狀態(tài)
<9>阻塞(暫停)線程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 進(jìn)入阻塞狀態(tài)
<10>強(qiáng)制停止線程
+ (void)exit;
// 進(jìn)入死亡狀態(tài)
注意:一旦線程停止(死亡)了,就不能再次開啟任務(wù)
2.多線程的安全問題
<1>資源共享
1塊資源可能會被多個(gè)線程共享,也就是多個(gè)線程可能會訪問同一塊資源
比如多個(gè)線程訪問同一個(gè)對象、同一個(gè)變量、同一個(gè)文件
當(dāng)多個(gè)線程訪問同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題
<2>安全隱患解決
1.互斥鎖使用格式
@synchronized(鎖對象) { // 需要鎖定的代碼 }
注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的
互斥鎖的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點(diǎn):需要消耗大量的CPU資源
互斥鎖的使用前提:多條線程搶奪同一塊資源
相關(guān)專業(yè)術(shù)語:線程同步
線程同步的意思是:多條線程按順序地執(zhí)行任務(wù)
互斥鎖,就是使用了線程同步技術(shù)
2.atomic和nonatomic
<1>OC在定義屬性時(shí)有nonatomic和atomic兩種選擇
自旋鎖:
atomic:原子屬性,為setter方法加鎖(默認(rèn)就是atomic)
nonatomic:非原子屬性,不會為setter方法加鎖
<2>nonatomic和atomic對比
atomic:線程安全,需要消耗大量的資源
nonatomic:非線程安全,適合內(nèi)存小的移動設(shè)備
<3>自旋鎖與互斥鎖的異同:
互斥鎖:@synchronized(){}
自旋鎖:atomic修飾的屬性,set方法中會添加自旋鎖
共同點(diǎn):當(dāng)多個(gè)線程訪問同一個(gè)資源的時(shí)候,通過加鎖可以避免數(shù)據(jù)安全問題。
不同點(diǎn):互斥鎖,其他線程(除了當(dāng)前正在訪問數(shù)據(jù)的其他想要訪問這個(gè)數(shù)據(jù)的線程)會處于阻塞狀態(tài)(有一個(gè)再回到就緒狀態(tài)的過程);自旋鎖,其他線程處于無限循環(huán)的過程(不會有狀態(tài)的改變)
<4>iOS開發(fā)的建議
所有屬性都聲明為nonatomic
盡量避免多線程搶奪同一塊資源
盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力
3.線程間通信
<1>什么叫做線程間通信
在1個(gè)進(jìn)程中,線程往往不是孤立存在的,多個(gè)線程之間需要經(jīng)常進(jìn)行通信
<2>線程間通信的體現(xiàn)
1個(gè)線程傳遞數(shù)據(jù)給另1個(gè)線程
在1個(gè)線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另1個(gè)線程繼續(xù)執(zhí)行任務(wù)
<3>線程間通信常用方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
4.GCD
<1>什么是GCD
1.全稱是Grand Central Dispatch,可譯為“牛逼的中樞調(diào)度器”
2.純C語言,提供了非常多強(qiáng)大的函數(shù)
<2>GCD的優(yōu)勢
1.GCD是蘋果公司為多核的并行運(yùn)算提出的解決方案
2.GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)
3.GCD會自動管理線程的聲明周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
4.程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼
<3>GCD的兩個(gè)核心概念
1.任務(wù);執(zhí)行什么操作
2.隊(duì)列:用來存放任務(wù)
<4>使用步驟
1.定制任務(wù):確定想要做的事情
2.將任務(wù)添加到隊(duì)列中
- GCD會自動將隊(duì)列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行
- 任務(wù)的取出遵循隊(duì)列的FIFO原則:先進(jìn)先出,后進(jìn)后出
<5>GCD中有2個(gè)用來執(zhí)行任務(wù)的函數(shù)
1.用同步的方式執(zhí)行任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- queue:隊(duì)列
- block:任務(wù)
2.用異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
3.同步和異步的區(qū)別
- 同步:在當(dāng)前線程中執(zhí)行,必須馬上執(zhí)行任務(wù)
- 異步:在另一條線程中執(zhí)行,可以延時(shí)執(zhí)行
<6>GCD的隊(duì)列可以分為2大類型
1.并發(fā)隊(duì)列(Concurrent Dispatch Queue)
- 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動開啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
- 并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
2.串行隊(duì)列(Serial Dispatch Queue)
- 讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))
<7>有4個(gè)術(shù)語比較容易混淆:同步、異步、并發(fā)、串行
1.同步和異步?jīng)Q定了要不要開啟新的線程
- 同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
- 異步:在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
2.并發(fā)和串行決定了任務(wù)的執(zhí)行方式
- 并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
- 串行:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)
3.在UI中當(dāng)前線程雙核主線程,即同步是在主線程中執(zhí)行任務(wù)的,而異步是在開辟子線程中執(zhí)行任務(wù)
<8>并發(fā)(并行)隊(duì)列
1.GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊(duì)列,供整個(gè)應(yīng)用使用,不需要手動創(chuàng)建
使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊(duì)列
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 隊(duì)列的優(yōu)先級
unsigned long flags); // 此參數(shù)暫時(shí)無用,用0即可
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 獲得全局并發(fā)隊(duì)列
2.全局并發(fā)隊(duì)列的優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
3.手動創(chuàng)建并行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("abc", DISPATCH_QUEUE_SERIAL);
<9>GCD中獲得串行有2種途徑
1.使用dispatch_queue_create函數(shù)創(chuàng)建串行隊(duì)列
dispatch_queue_t
dispatch_queue_create(const char *label, // 隊(duì)列名稱
dispatch_queue_attr_t attr); // 隊(duì)列屬性,一般用NULL即可
dispatch_queue_t queue = dispatch_queue_create("cn.qianfeng.queue", NULL); // 創(chuàng)建
dispatch_release(queue); // 非ARC需要釋放手動創(chuàng)建的隊(duì)列
2.使用主隊(duì)列(跟主線程相關(guān)聯(lián)的隊(duì)列)
主隊(duì)列是GCD自帶的一種特殊的串行隊(duì)列
放在主隊(duì)列中的任務(wù),都會放到主線程中執(zhí)行
使用dispatch_get_main_queue()獲得主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
<10>各種隊(duì)列的執(zhí)行效果
column | column | ||
---|---|---|---|
全局并行隊(duì)列 | 手動創(chuàng)建創(chuàng)建串行隊(duì)列 | 主隊(duì)列 | |
同步(sync) | 沒有開啟新線程;串行執(zhí)行任務(wù) | 沒有開啟新線程;串行執(zhí)行任務(wù) | 會死鎖 |
異步(async) | 有開啟新線程;并行執(zhí)行任務(wù) | 有開啟新線程;串行執(zhí)行任務(wù) | 沒有開啟新線程;串行執(zhí)行任務(wù) |
<11>從子線程回到主線程
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行耗時(shí)的異步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主線程,執(zhí)行UI刷新操作
});
});
<12>延時(shí)執(zhí)行
1.iOS常見的延時(shí)執(zhí)行有2種方式
調(diào)用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再調(diào)用self的run方法
2.使用GCD函數(shù)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后異步執(zhí)行這里的代碼...
});
<13>一次性代碼
使用dispatch_once函數(shù)能保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
//可用于UI中創(chuàng)建單例
});
<14>隊(duì)列組
1.有這么1種需求
- 首先:分別異步執(zhí)行2個(gè)耗時(shí)的操作
- 其次:等2個(gè)異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作
2.如果想要快速高效地實(shí)現(xiàn)上述需求,可以考慮用隊(duì)列組
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行1個(gè)耗時(shí)的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行1個(gè)耗時(shí)的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操作都執(zhí)行完畢后,回到主線程...
});
5.NSOperation
<1>NSOperation的作用
1.配合使用NSOperation和NSOperationQueue也能實(shí)現(xiàn)多線程編程
2.NSOperation和NSOperationQueue實(shí)現(xiàn)多線程的具體步驟
- 先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對象中
- 然后將NSOperation對象添加到NSOperationQueue中
- 系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來
- 將取出的NSOperation封裝的操作放到一條新線程中執(zhí)行
3.NSOperation是個(gè)抽象類,并不具備封裝操作的能力,必須使用它的子類
4.使用NSOperation子類的方式有3種
- NSInvocationOperation
- NSBlockOperation
- 自定義子類繼承NSOperation,實(shí)現(xiàn)內(nèi)部相應(yīng)的方法
5.創(chuàng)建NSInvocationOperation對象
-(id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
- 調(diào)用start方法開始執(zhí)行操作
-(void)start;
一旦執(zhí)行操作,就會調(diào)用target的sel方法
注意:
- 默認(rèn)情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作
- 只有將NSOperation放到一個(gè)NSOperationQueue中,才會異步執(zhí)行操作
6.創(chuàng)建NSBlockOperation對象
+(id)blockOperationWithBlock:(void (^)(void))block;
- 通過addExecutionBlock:方法添加更多的操作
-(void)addExecutionBlock:(void (^)(void))block;
- 注意:只要NSBlockOperation封裝的操作數(shù) > 1,就會異步執(zhí)行操作
<2>NSOperationQueue的作用
1.NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的
2.如果將NSOperation添加到NSOperationQueue(操作隊(duì)列)中,系統(tǒng)會自動異步執(zhí)行NSOperation中的操作
- 添加操作到NSOperationQueue中
-(void); addOperation:(NSOperation *)op
-(void)addOperationWithBlock:(void (^)(void))block;
<3>什么是并發(fā)數(shù)
1.同時(shí)執(zhí)行的任務(wù)數(shù)
2.比如,同時(shí)開3個(gè)線程執(zhí)行3個(gè)任務(wù),并發(fā)數(shù)就是3
- 最大并發(fā)數(shù)的相關(guān)方法
-(NSInteger)maxConcurrentOperationCount;
-(void)setMaxConcurrentOperationCount:(NSInteger)cnt;
<4>隊(duì)列的取消、暫停、恢復(fù)
1.取消隊(duì)列的所有操作
-(void)cancelAllOperations;
提示:也可以調(diào)用NSOperation的- (void)cancel方法取消單個(gè)操作
2.暫停和恢復(fù)隊(duì)列
-(void)setSuspended:(BOOL)b; // YES代表暫停隊(duì)列,NO代表恢復(fù)隊(duì)列
-(BOOL)isSuspended;
<5>操作優(yōu)先級
1.設(shè)置NSOperation在queue中的優(yōu)先級,可以改變操作的執(zhí)行優(yōu)先級
-(NSOperationQueuePriority)queuePriority;
-(void)setQueuePriority:(NSOperationQueuePriority)p;
2.優(yōu)先級的取值
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
<6>操作的監(jiān)聽
可以監(jiān)聽一個(gè)操作的執(zhí)行完畢
-(void (^)(void))completionBlock;
-(void)setCompletionBlock:(void (^)(void))block;
<7>操作依賴
1.NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序
- 比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,可以這么寫
[operationB addDependency:operationA]; // 操作B依賴于操作A
2.可以在不同queue的NSOperation之間創(chuàng)建依賴關(guān)系
3.注意:不能相互依賴
- 比如A依賴B,B依賴A
<8>自定義NSOperation
1.自定義NSOperation的步驟很簡單
- 重寫- (void)main方法,在里面實(shí)現(xiàn)想執(zhí)行的任務(wù)
2.重寫- (void)main方法的注意點(diǎn)
- 自己創(chuàng)建自動釋放池(因?yàn)槿绻钱惒讲僮?,無法訪問主線程的自動釋放池)
- 經(jīng)常通過- (BOOL)isCancelled方法檢測操作是否被取消,對取消做出響應(yīng)