iOS開發(fā)筆記-多線程01
理論部分:
一.進(jìn)程
1)概念:是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,每個(gè)進(jìn)程的存儲(chǔ)空間是獨(dú)立的(終端中輸入top可以查看進(jìn)程,按Q退出)
P.S.進(jìn)程與應(yīng)用程序的差別:進(jìn)程是有狀態(tài)的,正在執(zhí)行的
2)進(jìn)程是CPU進(jìn)行資源分配與調(diào)度的基本單位
二.線程
1)線程是CPU調(diào)度的基本單位(1個(gè)進(jìn)程想執(zhí)行任務(wù),必須要有線程 ),一個(gè)進(jìn)程中所有的任務(wù)都在線程中執(zhí)行
2)一個(gè)線程中的任務(wù)的執(zhí)行是串行的
P.S.進(jìn)程與線程的辨析
1)一個(gè)程序可以對(duì)應(yīng)多個(gè)進(jìn)程,一個(gè)進(jìn)程中可以有多個(gè)線程,但至少要有一個(gè)線程,線程是進(jìn)程的一條路徑
2)同一個(gè)進(jìn)程內(nèi)的線程共享進(jìn)程的資源
3)進(jìn)程可以分配資源,而線程不可以
3)多線程并發(fā)執(zhí)行:其實(shí)是CPU快速地在多個(gè)線程之間調(diào)度(只是假象,因?yàn)橥粫r(shí)間,CPU只能處理一條線程),建議開辟線程數(shù)量為3-5條
4)主線程:也稱為UI線程,作用是:1.顯示\刷新UI界面 2.處理UI事件 (所有和UI相關(guān)的操作都需要在主線程中執(zhí)行)
打印時(shí),若顯示的number等于1,則為主線程,其他數(shù)字,則為子線程
P.S.主線程注意點(diǎn)
1.不要將耗時(shí)的任務(wù)放在主線程
2.UI刷新操作要放在主線程中
3.耗時(shí)的操作放在子線程中(后臺(tái)線程)
三.pthread
說(shuō)明:pthread的基本使用(需要包含頭文件)
//使用pthread創(chuàng)建線程對(duì)象
pthread_t thread;
//使用pthread創(chuàng)建線程
//第一個(gè)參數(shù):線程對(duì)象地址
//第二個(gè)參數(shù):線程屬性
//第三個(gè)參數(shù):指向函數(shù)的指針
//第四個(gè)參數(shù):傳遞給該函數(shù)的參數(shù)
pthread_create(&thread, NULL, run, NULL);
四.NSThread
1.一個(gè)NSThread代表一條線程
2.優(yōu)先級(jí)高,cpu調(diào)用的概率高(影響的也是CPU調(diào)度到的概率)
1)NSThread創(chuàng)建線程的四種方式
//第一種創(chuàng)建線程的方式:alloc initWithTarget.
//特點(diǎn):需要手動(dòng)開啟線程,可以拿到線程對(duì)象進(jìn)行詳細(xì)設(shè)置
//創(chuàng)建線程
/*
第一個(gè)參數(shù):目標(biāo)對(duì)象
第二個(gè)參數(shù):選擇器,線程啟動(dòng)要調(diào)用哪個(gè)方法
第三個(gè)參數(shù):前面方法要接收的參數(shù)(最多只能接收一個(gè)參數(shù),沒有則傳nil)
*/
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"hmx"];
//啟動(dòng)線程
[thread start];
//第二種創(chuàng)建線程的方式:分離出一條子線程
//特點(diǎn):自動(dòng)啟動(dòng)線程,無(wú)法對(duì)線程進(jìn)行更詳細(xì)的設(shè)置
/*
第一個(gè)參數(shù):線程啟動(dòng)調(diào)用的方法
第二個(gè)參數(shù):目標(biāo)對(duì)象
第三個(gè)參數(shù):傳遞給調(diào)用方法的參數(shù)
*/
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"我是分離出來(lái)的子線程"];
//第三種創(chuàng)建線程的方式:后臺(tái)線程
//特點(diǎn):自動(dòng)啟動(dòng)線程,無(wú)法進(jìn)行更詳細(xì)設(shè)置
[self performSelectorInBackground:@selector(run:) withObject:@"我是后臺(tái)線程"];
//第四種創(chuàng)建線程方法:alloc init
//新建一個(gè)類 繼承自NSThread,重寫內(nèi)部的main方法來(lái)封裝任務(wù)
NSThread *thread = [[NSThread alloc]init];
//啟動(dòng)線程
[thread start];
2)設(shè)置線程的屬性
//設(shè)置線程的名稱
thread.name = @"線程A";
//設(shè)置線程的優(yōu)先級(jí),注意線程優(yōu)先級(jí)的取值范圍為0.0~1.0之間,1.0表示線程的優(yōu)先級(jí)最高,如果不設(shè)置該值,那么理想狀態(tài)下默認(rèn)為0.5
thread.threadPriority = 1.0;
3.線程狀態(tài)相關(guān)問(wèn)題:
1.線程的各種狀態(tài):新建-就緒-運(yùn)行-阻塞-死亡
2.當(dāng)線程的任務(wù)執(zhí)行完畢之后就銷毀了
3.當(dāng)線程放入可調(diào)度線程池中,CPU才會(huì)調(diào)度
4.線程已經(jīng)死了,是不能再重新打開的
5.當(dāng)線程解除阻塞狀態(tài)時(shí),會(huì)進(jìn)入就緒狀態(tài),而不是運(yùn)行狀態(tài)
4.線程安全相關(guān)問(wèn)題:
1. 互斥鎖:@synchronized(self){//需要鎖定的代碼}(推薦使用self)
2. 前提條件:多個(gè)線程可能會(huì)訪問(wèn)同一塊資源
3. 注意點(diǎn):1)要注意加鎖的位置
2)鎖對(duì)象必須是對(duì)象,且全局唯一
3)加上互斥鎖之后,就會(huì)使線程同步(即線程永遠(yuǎn)都是按一個(gè)順序調(diào)用,例:一開始是2->1->3的順序,之后依舊為2->1->3的順序)
4)線程是需要消耗性能的
4. 專業(yè)術(shù)語(yǔ)-線程同步
5. 原子和非原子屬性(是否對(duì)setter方法加鎖)
atomic 線程安全,需要消耗大量資源
nonatomic 非線程安全,適合內(nèi)存小的移動(dòng)設(shè)備 (開發(fā)中聲明為這個(gè))
5.計(jì)算代碼段間的執(zhí)行時(shí)間
//第一種方法
NSDate *start = [NSDate date];
//2.根據(jù)url地址下載圖片數(shù)據(jù)到本地(二進(jìn)制數(shù)據(jù))
NSData *data = [NSData dataWithContentsOfURL:url];
NSDate *end = [NSDate date];
NSLog(@"第二步操作花費(fèi)的時(shí)間為%f",[end timeIntervalSinceDate:start]);
//第二種方法
CFTimeInterval start = CFAbsoluteTimeGetCurrent();
NSData *data = [NSData dataWithContentsOfURL:url];
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"第二步操作花費(fèi)的時(shí)間為%f",end - start);
6.回到主線程刷新UI
//4.1 第一種方式
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
//4.2 第二種方式
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
//4.3 第三種方式
[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
五.PCD
1.GCD基本知識(shí)
1) 兩個(gè)核心概念:隊(duì)列和任務(wù):
隊(duì)列:用來(lái)存放任務(wù)(決定在哪個(gè)線程執(zhí)行任務(wù))
任務(wù):執(zhí)行什么操作
2) 同步函數(shù)和異步函數(shù)的區(qū)別:
同步:1.只能在當(dāng)前線程中執(zhí)行,不具備開啟新線程的能力
2.執(zhí)行任務(wù)的方式:當(dāng)執(zhí)行到我時(shí)必須等我執(zhí)行完才能執(zhí)行后面的任務(wù)
異步:1.可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
2.執(zhí)行任務(wù)的方式:可以不用等我執(zhí)行完畢,就可以直接執(zhí)行后面的任務(wù)
3) 串行隊(duì)列:1.取出一個(gè)任務(wù)后,等到該任務(wù)執(zhí)行完畢之后,接著去第二個(gè)任務(wù)
2.創(chuàng)建方式:a.自己創(chuàng)建 b.主隊(duì)列
并行隊(duì)列:1.取出一個(gè)任務(wù)后,接著執(zhí)行第二個(gè)任務(wù)
2.創(chuàng)建方式:a.自己創(chuàng)建 b.全局并發(fā)隊(duì)列
主隊(duì)列:1.在安排任務(wù)的時(shí)候,會(huì)先檢查主線程的狀態(tài),如果主線程忙,那么久暫停調(diào)度直到空閑為止
2.想要實(shí)現(xiàn)控制系統(tǒng)開幾條線程時(shí),只需要控制創(chuàng)建幾個(gè)隊(duì)列
3.凡是放在主隊(duì)列里的任務(wù)都在主線程完成
2.GCD基本使用【重要】
01 異步函數(shù)+并發(fā)隊(duì)列:開啟多條線程,并發(fā)執(zhí)行任務(wù)
02 異步函數(shù)+串行隊(duì)列:開啟一條線程,串行執(zhí)行任務(wù)
03 同步函數(shù)+并發(fā)隊(duì)列:不開線程,串行執(zhí)行任務(wù)
04 同步函數(shù)+串行隊(duì)列:不開線程,串行執(zhí)行任務(wù)
05 異步函數(shù)+主隊(duì)列:不開線程,在主線程中串行執(zhí)行任務(wù)
06 同步函數(shù)+主隊(duì)列:不開線程,串行執(zhí)行任務(wù)(注意死鎖發(fā)生)
P.S若當(dāng)前是子線程執(zhí)行的同步函數(shù)加上主隊(duì)列的方式,不會(huì)發(fā)生死鎖
07 注意同步函數(shù)和異步函數(shù)在執(zhí)行順序上面的差異
3.GCD的線程間通信:
//0.獲取一個(gè)全局的隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//1.先開啟一個(gè)線程,把下載圖片的操作放在子線程中處理
dispatch_async(queue, ^{
//2.下載圖片
NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
NSLog(@"下載操作所在的線程--%@",[NSThread currentThread]);
//3.回到主線程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
//打印查看當(dāng)前線程
NSLog(@"刷新UI---%@",[NSThread currentThread]);
});
});
4.GCD相關(guān)注意點(diǎn)
1. 開線程的兩個(gè)條件:1.必須是異步函數(shù) 2.必須不是主隊(duì)列(主線程)
2. 放到主隊(duì)列里的任務(wù),必須要在主線程中執(zhí)行(不一定在當(dāng)前線程中執(zhí)行,即即使在子線程中調(diào)用了主隊(duì)列,還是子主線程中調(diào)用)
5.GCD常用函數(shù):
1)柵欄函數(shù)(控制任務(wù)的執(zhí)行順序)
dispatch_barrier_async(queue, ^{
NSLog(@"--dispatch_barrier_async-");
});
/*
P.S 注意點(diǎn):1.柵欄函數(shù)可以控制線程的執(zhí)行順序
2.柵欄函數(shù)不能使用全局并發(fā)隊(duì)列
3.柵欄函數(shù)在執(zhí)行時(shí)是獨(dú)占的
4.dispatch_barrier_async == dispatch_async 這兩個(gè)是等同的
*/
2)延遲執(zhí)行(延遲·控制在哪個(gè)線程執(zhí)行)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"---%@",[NSThread currentThread]);
});
/*
Q:GCD的延遲執(zhí)行 是先等2秒再提交 OR 先提交再等2秒?
A:先等2秒再提交(延遲提交) 因?yàn)槿蝿?wù)提交到隊(duì)列里就不好控制了
*/
3)一次性代碼(注意不能放到懶加載)
-(void)once
{
//整個(gè)程序運(yùn)行過(guò)程中只會(huì)執(zhí)行一次
//onceToken用來(lái)記錄該部分的代碼是否被執(zhí)行過(guò)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-----");
});
}
/*
一次性代碼的特點(diǎn):1.整個(gè)程序運(yùn)行過(guò)程中只會(huì)執(zhí)行一次
2.它本身是線程安全的
*/
4)快速迭代(開多個(gè)線程并發(fā)完成迭代操作)
dispatch_apply(subpaths.count, queue, ^(size_t index) {
});
/*
快速迭代:多個(gè)線程(子線程與主線程一起工作的)一起并發(fā)執(zhí)行任務(wù)的--->對(duì)順序沒有要求時(shí)使用
Q:快速迭代若不是從0開始,怎么處理
A:初始值若不是0,則不需要使用GCD的快速迭代,使用for循環(huán)即可
*/
5)隊(duì)列組(同柵欄函數(shù))--->調(diào)度組
//創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//隊(duì)列組中的任務(wù)執(zhí)行完畢之后,執(zhí)行該函數(shù)
dispatch_group_notify(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);//這個(gè)方法本身也是異步的
/*
方便管理一個(gè)組內(nèi)的操作
*/
6)進(jìn)入群組和離開群組
dispatch_group_enter(group);//執(zhí)行該函數(shù)后,后面異步執(zhí)行的block會(huì)被gruop監(jiān)聽
dispatch_group_leave(group);//異步block中,所有的任務(wù)都執(zhí)行完畢,最后離開群組
/*
注意:dispatch_group_enter|dispatch_group_leave必須成對(duì)使用
當(dāng)需要監(jiān)聽多個(gè)任務(wù)時(shí),則重復(fù)寫一組即可
*/
//死等方法:知道隊(duì)列組中所有都執(zhí)行完畢之后才過(guò)掉該方法(同步)
//diepatch_group_wait(group,DISPATCH_TIME_FOREVER)