什么是線程郎任?什么是進(jìn)程佛舱?線程和進(jìn)程的區(qū)別與聯(lián)系?
- 線程的定義
- 線程是CPU調(diào)度(執(zhí)行任務(wù))的最小單位
- 其實(shí)質(zhì)就是一段代碼(一個(gè)任務(wù))
- 進(jìn)程的定義
- 系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序
- 進(jìn)程是CPU分配資源和調(diào)度的單位
- 兩者的聯(lián)系與區(qū)別
- 線程是進(jìn)程的組成部分,一個(gè)進(jìn)程可以開啟多個(gè)子線程航棱,但是每1個(gè)進(jìn)程至少要有1個(gè)線程
- 1個(gè)進(jìn)程的所有任務(wù)都是在線程中執(zhí)行的
- 同1個(gè)進(jìn)程內(nèi)的線程共享進(jìn)程的資源
多線程的概念和原理
- 概念
- 一個(gè)程序開啟多條線程,每條線程可以并行(同時(shí))執(zhí)行不同的任務(wù)
- 原理
- 多個(gè)線程并發(fā)執(zhí)行靠胜,其實(shí)質(zhì)是CPU快速地在多條線程之間調(diào)度(切換)
- 當(dāng)CPU調(diào)度線程的時(shí)間足夠快掉瞳,就會(huì)造成多線程并發(fā)執(zhí)行的假象
多線程的優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn)
- 能適當(dāng)?shù)靥岣叱绦虻膱?zhí)行效率
- 能適當(dāng)提高資源利用率(CPU和內(nèi)存利用率)
- 缺點(diǎn)
- 創(chuàng)建多線程是有開銷的,包括內(nèi)存空間和創(chuàng)建時(shí)間上的開銷
- 如果開啟大量線程浪漠,會(huì)降低程序的性能
- 線程越多陕习,CPU在調(diào)度線程上的開銷就越大
什么是主線程?主線程的作用和使用注意有哪些址愿?
- 定義
- iOS程序運(yùn)行后该镣,默認(rèn)開啟1條線程,稱為“主線程”
- 作用
- 顯示/刷新UI界面
- 處理UI事件(點(diǎn)擊响谓、滾動(dòng)损合、拖拽事件等)
- 注意
- 別將耗時(shí)操作放在主線程,耗時(shí)操作會(huì)卡住主線程娘纷,影響UI的流暢度
實(shí)現(xiàn)多線程的方法一共有哪些?
項(xiàng)目中一般在哪用到多線程?
多線程一般用于耗時(shí)操作,如:
- 網(wǎng)絡(luò)請(qǐng)求
- 上傳,下載文件
- 讀取數(shù)據(jù)庫
- 文件操作
- 大循環(huán)
什么是線程安全問題嫁审?有什么解決方案?
- 定義
- 多個(gè)線程同時(shí)訪問同一塊資源(例如同一個(gè)對(duì)象赖晶、變量律适、文件)時(shí),引發(fā)的數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題,稱為線程安全問題
- 解決方法
- 添加互斥鎖
- 互斥鎖的使用格式 :@synchronized( 鎖對(duì)象 ){ //需要鎖定的代碼}
- 加鎖的原理:使線程同步執(zhí)行
- 使用互斥鎖的注意事項(xiàng):
- 鎖對(duì)象必須是全局唯一的捂贿,一般用self
- 加鎖必須滿足多個(gè)線程共享同一塊資源的前提條件
- 加鎖需要耗費(fèi)大量CPU資源
- 鎖定一段代碼只需要用一把鎖
線程安全問題(以下代碼的打印結(jié)果是?)
x = 1;
線程1: {x++;}
線程2: {x++;}
print x;
打印結(jié)果?
- 有可能是1
- 線程1和線程2還沒走完,主線程已經(jīng)結(jié)束了,結(jié)果 x = 1
- 有可能是2
- 線程1和線程2同時(shí)訪問x, 結(jié)果 x = 2
- 有可能是3
- 線程1和線程2不同時(shí)訪問x, 結(jié)果 x = 3
atomic和nonatomic的作用和區(qū)別纠修?
- atomic是線程安全的,會(huì)自動(dòng)為setter方法加鎖厂僧,需要消耗大小資源
- nonatomic是非線程安全的扣草,適合內(nèi)存小的移動(dòng)設(shè)備
FMDB是線程安全的么?
- FMDB如果使用FMDatabase類是線程不安全的
- 使用FMDatabaseQueue是線程安全的
線程通信的概念吁系,如何實(shí)現(xiàn)德召?
- 概念
- 一個(gè)線程傳遞數(shù)據(jù)給另一個(gè)線程
- 一個(gè)線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另一個(gè)線程繼續(xù)執(zhí)行任務(wù)
- 實(shí)現(xiàn)方法
- performSelectorOnMainThread和performSelectorOnThread方法
- performSelectorInBackground(后臺(tái)線程中執(zhí)行)
- 使用GCD的dispatch方法
GCD內(nèi)部怎么實(shí)現(xiàn)的汽纤,使用GCD有什么優(yōu)勢上岗?
- 實(shí)現(xiàn)方式
- 通過定制任務(wù)和將任務(wù)添加到隊(duì)列中來實(shí)現(xiàn)GCD的多線程功能
- GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)去出來,放到對(duì)應(yīng)的線程中執(zhí)行
- 任務(wù)的取出遵循FIFO原則(先進(jìn)先出)
- 優(yōu)勢
- 會(huì)自動(dòng)利用更多的CPU內(nèi)核
- 會(huì)自動(dòng)管理線程的生命周期
全局并發(fā)隊(duì)列和使用create函數(shù)創(chuàng)建的并發(fā)隊(duì)列有什么區(qū)別蕴坪?
- 全局并發(fā)隊(duì)列在整個(gè)程序中本身是默認(rèn)存在的肴掷,并且對(duì)應(yīng)有高優(yōu)先級(jí),默認(rèn)優(yōu)先級(jí)背传,低優(yōu)先級(jí)呆瞻,后臺(tái)優(yōu)先級(jí)一共四種并發(fā)隊(duì)列,我們只是選擇其中一個(gè)來用径玖,而使用create函數(shù)創(chuàng)建的并發(fā)隊(duì)列是實(shí)打?qū)嵉貜念^開始去創(chuàng)建一個(gè)隊(duì)列
- iOS6之前痴脾,使用create函數(shù)創(chuàng)建的隊(duì)列都要進(jìn)行一次Release,而全局并發(fā)隊(duì)列不需要我們手動(dòng)Release梳星。但在iOS6之后赞赖,GCD已經(jīng)納入ARC內(nèi)存操作中,不需要再進(jìn)行Release
- 使用柵欄函數(shù)時(shí)冤灾,使用全局并發(fā)隊(duì)列是無效的前域,只有使用create創(chuàng)建的并發(fā)隊(duì)列才有效
GCD中如何控制多線程并發(fā)執(zhí)行時(shí)的執(zhí)行順序
- 使用柵欄函數(shù)
dispatch_barrier_async
- 注意
- 給柵欄函數(shù)添加全局并發(fā)隊(duì)列是無效的
- 柵欄函數(shù)無法控制柵欄前的并發(fā)隊(duì)列的執(zhí)行順序
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"第1個(gè)線程執(zhí)行了----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"第2個(gè)線程執(zhí)行了----%@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"++++++++++++++++++++++++");
});
dispatch_async(queue, ^{
NSLog(@"第5個(gè)線程執(zhí)行了----%@",[NSThread currentThread]);
});
GCD除了開線程,還可以用在什么地方韵吨?
- 單例 (使用dispatch_once)
- 延遲執(zhí)行 (使用dispatch_after匿垄,可以定義執(zhí)行任務(wù)的線程)
- 定時(shí)器 (使用dispatch_source_set_event_handler)
- 快速迭代
GCD快速迭代的定義和作用?
- 定義:同時(shí)開啟主線程和子線程并發(fā)完成循環(huán)操作归粉,稱為快速迭代
- 作用:大大提高循環(huán)操作的效率
- 代碼示例:(從from文件夾中剪切其中的文件到to文件夾中)
- (void)moveFile{
NSString *fromPath = @"/Users/zkh/Desktop/from";
NSString *toPath = @"/Users/zkh/Desktop/to";
NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:fromPath];
NSInteger count = subPaths.count;
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSString *fromFilePath = [fromPath stringByAppendingPathComponent:subPaths[index]];
NSString *toFilePath = [toPath stringByAppendingPathComponent:subPaths[index]];
[[NSFileManager defaultManager] moveItemAtPath:fromFilePath toPath:toFilePath error:nil];
NSLog(@"當(dāng)前線程---%@", [NSThread currentThread]);
});
}
GCD中隊(duì)列組的作用
- 使用隊(duì)列組椿疗,除了可以開啟新的線程,同時(shí)還能通過group監(jiān)聽隊(duì)列中任務(wù)的執(zhí)行情況
- 代碼示例:
- (void)group1{
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"執(zhí)行任務(wù)1----%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"執(zhí)行任務(wù)2----%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"執(zhí)行任務(wù)3----%@", [NSThread currentThread]);
});
//dispatch_group_notify本事是異步執(zhí)行的盏浇,不會(huì)阻塞主線程
dispatch_group_notify(group, queue, ^{
NSLog(@"----任務(wù)執(zhí)行完畢-----");
});
NSLog(@"========end=========");
}
如何處理GCD造成的死鎖問題
- 將隊(duì)列改為非主隊(duì)列
- 將調(diào)度方式改為異步調(diào)度
NSOperation和GCD的區(qū)別变丧?
- 實(shí)現(xiàn)機(jī)制的區(qū)別
- 使用這兩者執(zhí)行任務(wù)都會(huì)由系統(tǒng)自動(dòng)創(chuàng)建、銷毀子線程
- GCD底層是基于C語言的绢掰,NSOperation則是對(duì)GCD的封裝痒蓬,是面向?qū)ο蟮?/li>
- NSOperation只有兩種類型的隊(duì)列
- 主隊(duì)列和非主隊(duì)列(非主隊(duì)列既可以是并行的也可以是串行的童擎,默認(rèn)是并行的)
- GCD擁有四種類型的隊(duì)列 (main, global, create[serial], create[concurrent] )
- 使用上的區(qū)別
- GCD的使用更輕量級(jí),而NSOperation作為對(duì)象提供了更豐富的API
- 在NSOperationQueue中攻晒,可以隨時(shí)取消要準(zhǔn)備執(zhí)行的任務(wù)顾复,而GCD沒法停止已經(jīng)加入queue的block中的任務(wù)
- KVO能應(yīng)用在NSOperation中,以監(jiān)聽一個(gè)Operation是否完成或取消鲁捏,這樣能比GCD更加有效地掌控我們執(zhí)行的后臺(tái)任務(wù)
- NSOperation通過繼承芯砸,可以提高代碼的復(fù)用度,這比GCD更有自由度和擴(kuò)展性
- 同一個(gè)并行隊(duì)列中的任務(wù)執(zhí)行時(shí)给梅,我們能夠設(shè)置NSOperation的priority(優(yōu)先級(jí))假丧,使之按順序執(zhí)行,而在GCD中动羽,要使block中的任務(wù)實(shí)現(xiàn)這個(gè)功能包帚,需要大量復(fù)雜代碼
自定義繼承于NSOperation的類有什么好處?要注意什么运吓?
- 好處
- 可提高代碼的封閉性
- 可提高代碼的復(fù)用性
- 注意點(diǎn)
- 必須在.m中重寫main方法渴邦,main方法是操作任務(wù)的入口
什么是最大任務(wù)并發(fā)數(shù)?NSOperationQueue中如何操縱隊(duì)列中的任務(wù)拘哨?
- 線程的最大任務(wù)并發(fā)數(shù):異步執(zhí)行時(shí)同一時(shí)間內(nèi)可以同時(shí)執(zhí)行的操作的最大數(shù)
- 當(dāng)maxConcurrentOperationCount = 1谋梭,只能執(zhí)行一個(gè)操作,隊(duì)列為串行隊(duì)列
- 當(dāng)maxConcurrentOperationCount = 0倦青,無法執(zhí)行任何操作
- 當(dāng)maxConcurrentOperationCount > 1瓮床,隊(duì)列為并發(fā)隊(duì)列
- 默認(rèn)條件下maxConcurrentOperationCount = -1,代表最大并發(fā)數(shù)沒有限制
- 代碼示例:
- (IBAction)startOperation:(id)sender {
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
[self.queue addOperationWithBlock:^{
for (NSInteger i = 0; i < 2000; i++) {
NSLog(@"1----%ld----%@",i,[NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
for (NSInteger i = 0; i < 2000; i++) {
NSLog(@"2----%ld----%@",i,[NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
for (NSInteger i = 0; i < 2000; i++) {
NSLog(@"3----%ld----%@",i,[NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
for (NSInteger i = 0; i < 2000; i++) {
NSLog(@"4----%ld----%@",i,[NSThread currentThread]);
}
}];
}
- (IBAction)suspendOperation:(id)sender {
//暫停隊(duì)列中的任務(wù)产镐,但是必須等隊(duì)列中當(dāng)前正在執(zhí)行的任務(wù)完成后才能暫停
self.queue.suspended = YES;
}
- (IBAction)continueOperation:(id)sender {
//繼續(xù)執(zhí)行隊(duì)列中的任務(wù)
self.queue.suspended = NO;
}
- (IBAction)cancelOperation:(id)sender {
//注意該方法內(nèi)部會(huì)調(diào)用NSOperationQueue的cancel方法纤垂,取消后無法恢復(fù)(繼續(xù))!
[self.queue cancelAllOperations];
}
NSOperationQueue處理A,B,C三個(gè)線程,要求執(zhí)行完A,B后才能執(zhí)行C,怎么做磷账?
- 添加依賴關(guān)系,AB都依賴C
- 代碼示例:
- (void)operation{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *opA = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"A----%@",[NSThread currentThread]);
}];
NSBlockOperation *opB = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"B----%@",[NSThread currentThread]);
}];
NSBlockOperation *opC = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"C----%@",[NSThread currentThread]);
}];
//注意:不能互相依賴
[opC addDependency:opA];
[opC addDependency:opB];
[queue addOperation:opA];
[queue addOperation:opB];
[queue addOperation:opC];
NSLog(@"----end----");
}
有一個(gè)需求贾虽,需要將N個(gè)請(qǐng)求全部完成之后執(zhí)行某個(gè)操作逃糟,該如何處理
- GCD中可以使用柵欄函數(shù)或者隊(duì)列組
- NSOperation中可以添加依賴關(guān)系
設(shè)計(jì)一個(gè)多線程,分別下載2張圖片后進(jìn)行組合
- 可以使用GCD的柵欄函數(shù)或者隊(duì)列組,可以使用NSOperation的依賴關(guān)系
- 代碼示例:(這里以GCD中使用隊(duì)列組為例)
- (void)combineImages{
/*
1.先開啟子線程下載圖片1和圖片2
2.通過任務(wù)組在步驟1執(zhí)行完后合成圖片
3.在主線程中設(shè)置圖片
*/
dispatch_queue_t queue = dispatch_queue_create("image", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://scimg.jb51.net/allimg/160918/103-16091Q12135548.jpg"]];
self.image1 = [UIImage imageWithData:data];
});
dispatch_group_async(group, queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://pic44.nipic.com/20140717/12432466_121957328000_2.jpg"]];
self.image2 = [UIImage imageWithData:data];
});
dispatch_group_notify(group, queue, ^{
UIGraphicsBeginImageContext(CGSizeMake(300, 300));
[self.image1 drawInRect:CGRectMake(0, 0, 150, 150)];
[self.image2 drawInRect:CGRectMake(150, 150, 150, 150)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
}
使用多線程做多圖下載并進(jìn)行緩存的思路蓬豁?
- 先檢查內(nèi)存中是否有圖片緩存-->有就調(diào)用
- 沒有就檢查磁盤(沙盒)中是否有圖片緩存-->有就調(diào)用
- 都沒有就顯示占位圖片, 并檢查是否正在下載
- 如果不是正在下載就創(chuàng)建子線程來下載圖片
- 下載完后移除下載操作的緩存, 并把圖片存入內(nèi)存和沙盒中, 再刷新表格
- 如果出現(xiàn)內(nèi)存警告, 應(yīng)將內(nèi)存中所有圖片緩存清空, 并取消正在子線程的所有下載任務(wù)