前言:
第一次接觸多線程還是在寫android的時(shí)候,咋一看拍顷,覺得這玩意好難(面試必問)抚太;其實(shí)從字眼上看多線程分為“多”跟“線程”,只要搞明白線程是什么東西昔案,那多線程就迎刃而解了(就是多條線程同時(shí)存在嘛)尿贫,哪線程是什么東西? 我門必須了解一個(gè)非程ごВ基礎(chǔ)的的概念 “進(jìn)程”庆亡,正在進(jìn)行的程序,至于進(jìn)程更多的概念就自行百度吧呼伸! ?
一個(gè)進(jìn)程要想執(zhí)行任務(wù)必須要有線程(線程是進(jìn)程的一條執(zhí)行路徑),每一個(gè)程序至少有一條線程钝尸,進(jìn)程要執(zhí)行的任務(wù)都是放在線程中執(zhí)行的括享,線程的串行,一個(gè)線程中的所有任務(wù)是串行(一個(gè)任務(wù)一個(gè)任務(wù)的執(zhí)行)的珍促,同一時(shí)間一個(gè)線程只能執(zhí)行一個(gè)任務(wù)铃辖;
一個(gè) 進(jìn)程中可以開啟多條線程,每條線程可以并行執(zhí)行不同的任務(wù)猪叙;提高程序的執(zhí)行效率娇斩;
多線程的原理:
同一時(shí)間,CPU 只能處理一條線程穴翩,CPU很快的在多條線程中切換(調(diào)度)犬第,造成了多線程并發(fā)執(zhí)行的原理;? 如果CPU在n多個(gè)線程來回調(diào)度芒帕,會(huì)消耗大量的CPU資源歉嗓; 每條線程被調(diào)度的頻次會(huì)降低;
多線程的優(yōu)點(diǎn):
能適當(dāng)?shù)奶岣叱绦虻膱?zhí)行效率背蟆;能適當(dāng)?shù)奶岣哔Y源利用率鉴分;
多線程的缺點(diǎn):
創(chuàng)建子線程是有開銷的哮幢,主要包括內(nèi)核數(shù)據(jù)結(jié)構(gòu),椫菊洌空間(子線程512k橙垢,主線程1m,也可以使用-setStackSize設(shè)置,必須是4k的倍數(shù))伦糯,創(chuàng)建一個(gè)線程大約是90毫秒的時(shí)間柜某;程序設(shè)計(jì)更加復(fù)雜;多線程之間的數(shù)據(jù)共享舔株;多個(gè)線程同時(shí)占用同一個(gè)資源莺琳;
多線程在IOS中的應(yīng)用?
主線程:默認(rèn)開啟的子線程载慈,叫主線程 也叫 UI線程惭等;
作用: 顯示 刷新 UI,處理UI事件, 不要將比較耗時(shí)的操作放在主線程办铡;
線程的狀態(tài):
內(nèi)存 -》線程對(duì)象(新建狀態(tài))?
調(diào)用 Start -》就緒狀態(tài)(Runable)
CPU 調(diào)度當(dāng)前線程 ?運(yùn)行狀態(tài)(Running)
調(diào)用了sleep 進(jìn)入 阻塞狀態(tài)(Blocked)
正常執(zhí)行 或者 異常退出之后辞做,就會(huì)進(jìn)入死亡狀態(tài)([使用NSThread exit(0)]可以強(qiáng)制殺死線程)
耗時(shí)操作的執(zhí)行:
開啟子線程的的方式:
1. pthread :跨平臺(tái)的線程,使用難度比較大寡具, ?幾乎不用秤茅;(手動(dòng)創(chuàng)建線程,管理線程);
// 示例代碼
pthread_t myRestrict;
pthread_create(&myRestrict, NULL, run, NULL);
void *run(void *data)
{
for (int i=0; i<1000000; i++) {
NSLog(@"%d",i);
}
NSLog(@"run----&@",[NSThread currentThread]);
return NULL;
}
2. NSThread : 面向?qū)ο笸?jiǎn)單易用框喳,可是直接操作線程對(duì)象, 偶爾使用厦坛,使用 NSThread管理多個(gè)線程非常困難五垮;
(1) [NSThread currentThread] //跟蹤任務(wù)所在線程,適用于這三種技術(shù).
(2) [NSThread sleepForTimeInterval:] //睡眠多長(zhǎng)時(shí)間(秒)
/**
*? 創(chuàng)建線程的方式1
*/
- (void)createThread1
{
// 1.初始化
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(download) object:nil];
thread.name=@"線程2號(hào)";
//啟動(dòng)線程
[thread start];
//獲取主線程
[NSThread mainThread];
}
/**
*? 創(chuàng)建線程的方式2,? 直接創(chuàng)建
*/
- (void)createThread2
{
// 分離 ,派遣
[NSThread detachNewThreadSelector:@selector(download:) toTarget:self withObject:@"www.baidu.com"];
}
/**
*? 創(chuàng)建線程的方式3,? 隱身創(chuàng)建線程
*/
- (void)createThread3
{
[self performSelectorInBackground:@selector(download:) withObject:self];
}
/**
*? 創(chuàng)建線程的方式4,? 隱身創(chuàng)建線程
*/
- (void)createThread4
{
[self performSelector:@selector(download:) onThread:[NSThread mainThread] withObject:self waitUntilDone:YES];}
3. GCD:(Grand Central Dispatch),偉大的中樞調(diào)度器杜秸,取代NSThread,充分利用設(shè)備的多核并行運(yùn)算放仗;線程的生命周期是自動(dòng)管理的,經(jīng)常使用撬碟;
GCD的核心概念:任務(wù)(執(zhí)行什么操作) 跟 隊(duì)列(用來存放任務(wù)诞挨,相當(dāng)于線程池)
3.1 任務(wù)執(zhí)行方式: 同步或者異步執(zhí)行
//同步執(zhí)行
dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
//異步執(zhí)行
dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
3.2 隊(duì)列:并發(fā)隊(duì)列(concurrent),可以讓多個(gè)任務(wù)并發(fā)執(zhí)行呢蛤,并發(fā)功能只有在dispatch_async才有效惶傻,蘋果提供了DISPATCH_QUEUE_PRIORITY_DEFAULT ,使用dispatch_get_global_queue獲取就好了其障;
異步執(zhí)行是在執(zhí)行函數(shù)完成之后达罗,在返回去開辟子線程;
串行隊(duì)列,一個(gè)任務(wù)一個(gè)任務(wù)的執(zhí)行粮揉,主隊(duì)列
同步和異步主要區(qū)別:能不能開啟新的線程
同步:只能在當(dāng)前線程中執(zhí)行任務(wù)巡李,不具備開啟新線程的能力
異步:可以在新的線程中執(zhí)行任務(wù),有開啟新線程的能力
并發(fā)和串行的主要區(qū)別:任務(wù)的執(zhí)行方式
并發(fā):允許多個(gè)任務(wù)同時(shí)執(zhí)行
串行:一個(gè)任務(wù)執(zhí)行完畢之后在執(zhí)行下一個(gè)任務(wù)
排列組合共有四種方式:
/**
*? 異步的+全局隊(duì)列? 最常用
*/
- (void)asyncGlobalQueue
{
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
}
/**
*? 同步+并行隊(duì)列
*/
- (void)syncGlobalQueue
{
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
}
/**
*? 異步的串行隊(duì)列,? 一個(gè)一個(gè)按順序執(zhí)行
*/
- (void)asyncSerialQueue
{
dispatch_queue_t queue=dispatch_queue_create("com.zhangkun", NULL);
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
}
/**
*? 同步的串行隊(duì)列,? 一個(gè)一個(gè)按順序執(zhí)行
在主獻(xiàn)
*/
- (void)syncSerialQueue
{
dispatch_queue_t queue=dispatch_queue_create("com.zhangkun", NULL);
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
}
GCD線程之間的通信:
// 返回主線程
dispatch_sync(dispatch_get_main_queue(), <#^(void)block#>)
self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>
GCD常用的函數(shù):
// 在前面的任務(wù)執(zhí)行結(jié)束后 它才執(zhí)行扶认,而且它后面的任務(wù)等它執(zhí)行完成之后才會(huì)執(zhí)行
dispatch_barrier_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
// 延遲執(zhí)行
[self performSelector:@selector(run) withObject:param afterDelay:3];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"----2秒之后執(zhí)行");
});
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
// 整個(gè)程序運(yùn)行過程中只執(zhí)行一次侨拦, 跟懶加載不同 ?(一次性代碼)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"只會(huì)執(zhí)行一次,資源加載 ? 線程安全的");
});
// 遍歷線程
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t) {
});
GCD隊(duì)列小案例:
//1. 隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//2.
__block UIImage *image = nil;
__block UIImage *image1 = nil;
dispatch_group_async(group, kGlobalQueue, ^{
//下載一張·圖片
NSURL *url= [NSURL URLWithString:@"http://img5.iqilu.com/c/u/2015/0811/1439259819581.jpg"];
NSData *data=[NSData dataWithContentsOfURL:url];
image=[UIImage imageWithData:data];
});
dispatch_group_async(group, kGlobalQueue, ^{
//下載第二張圖片
NSURL *url1= [NSURL URLWithString:@"https://www.baidu.com/img/bd_logo1.png"];
NSData *data1=[NSData dataWithContentsOfURL:url1 ];
image1=[UIImage imageWithData:data1];
});
//保證組里邊的事情都執(zhí)行完辐宾, 才執(zhí)行block塊
dispatch_group_notify(group, kGlobalQueue, ^{
//合并圖片? 搞一張大的圖片狱从, 然后把第一張圖片畫上去
//開啟圖片上下文
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
[image1 drawInRect:CGRectMake(0, 0, image1.size.width*0.5, image1.size.height*0.5)];
//繪制完畢,得到上文的圖片
UIImage *imageC= UIGraphicsGetImageFromCurrentImageContext();
//結(jié)束圖片上下文
UIGraphicsEndImageContext();
//回到主線程設(shè)置圖片
dispatch_async(kMainQueue, ^{
NSLog(@"%@",[NSThread currentThread]);
[self.img setImage:imageC];
});
});
使用GCD實(shí)現(xiàn)單例模式(設(shè)計(jì)模式叠纹,保證在程序運(yùn)行的過程中一個(gè)類只有一個(gè)實(shí)例季研, 在整個(gè)應(yīng)用程序中要共享同一份資源)
實(shí)現(xiàn)方式(想辦法讓對(duì)象的內(nèi)存空間只存在一份, 也可以在load中初始化誉察,也可以使用@synchronized与涡,這里就演示了);
/**
*? alloc 內(nèi)部會(huì)調(diào)用這個(gè)方法
*/
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)shareStudentTool
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc]init];//防止創(chuàng)建多次
});
return _instance;
}
-(id)copyWithZone:(NSZone *)zone
{
return _instance;
}
在 pch中使用:
//.h
#define kSingH(name) + (instancetype)shareTool##name;
//.m
#define kSingM(name) ?static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance=[super allocWithZone:zone];\
});\
return _instance;\
}\
+ (instancetype)shareTool##name\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance=[[self alloc]init];\
});\
return _instance;\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
\
}
以后想使用單例直接在.h .m 中調(diào)用定義好的宏就好了持偏,是不是很簡(jiǎn)單驼卖;
NSOperation:基于GCD的,對(duì)GCD的一層封裝鸿秆,自動(dòng)管理酌畜;
多線程的安全隱患:
NSOperation 跟 NSOperationQueue
NSOperation 并不具備封裝任務(wù)的能力,使用NSOperation子類有三種:
NSInvocationOperation
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSInvocationOperation *invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil];
//operation 調(diào)用start卿叽,是同步執(zhí)行
[invo start];
//只有將操作放在隊(duì)列中桥胞,才會(huì)異步執(zhí)行
[queue addOperation:invo];
NSBlockOperation
自定義子類繼承NSOperation
NSOperationQueue : 并發(fā)隊(duì)列 / 串行隊(duì)列
+ (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);
+ (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);
// 主隊(duì)列
[NSOperationQueue mainQueue];
// 同時(shí)包含了:串行? 并發(fā) 的功能? ,就會(huì)自動(dòng)放到子線程中
NSOperationQueue *queque = [[NSOperationQueue alloc]init];
NSOperationQueue 的掛起和取消 (suspended):
[queue cancelAllOperations];//取消隊(duì)列中的任務(wù)
[queue setSuspended:YES];//暫停隊(duì)列中的任務(wù)
[queue setSuspended:NO];// 恢復(fù)隊(duì)列中的任務(wù)
addDependency : 依賴
//假如有ABC三個(gè)操作考婴, 要求贩虾, 三個(gè)操作 異步執(zhí)行
NSOperationQueue *queue=[[NSOperationQueue alloc]init];
NSBlockOperation *operaA=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1%@",[NSThread currentThread]);
}];
[operaA? setCompletionBlock:^{
NSLog(@"1%@",[NSThread currentThread]);
}];
NSBlockOperation *operaB=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2%@",[NSThread currentThread]);
}];
NSBlockOperation *operaC=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4%@",[NSThread currentThread]);
}];
[operaB addDependency:operaA];
[operaC addDependency:operaB];
[queue addOperation:operaA];
[queue addOperation:operaB];
[queue addOperation:operaC];
?資源共享: 一塊資源可能會(huì)被多個(gè)線程共享,也就是多個(gè)線程可能會(huì)訪問同一塊兒資源蕉扮;
比如多個(gè)線程同時(shí)訪問 同一個(gè)對(duì)象 ?同一個(gè)變量 ?同一個(gè)文件整胃;
老生常談的demo:
1.存取錢颗圣, 多個(gè)任務(wù)在多條線程同時(shí)執(zhí)行存取錢的任務(wù)喳钟;
2.賣票, 多個(gè)窗口同時(shí)在賣票在岂,先查詢票數(shù)夠不夠奔则,出現(xiàn)一張票被多個(gè)人買走的問題;
解決方案: 加互斥鎖 蔽午,線程同步
優(yōu)點(diǎn):能有效的防治因多線程搶奪資源造成的數(shù)據(jù)安全隱患 ?(互斥鎖的應(yīng)用場(chǎng)景)
缺點(diǎn):需要消耗大量的CPU資源
使用@synchronized將要鎖的代碼{}起來
while (1) {
@synchronized(self){//開始加鎖
int count = self.lastSale;
if (count > 0) {
self.lastSale = count-1;
[NSThread sleepForTimeInterval:0.1];
NSLog(@"%@---%d",[NSThread currentThread].name,count);
}else{return;}
補(bǔ)充: 原子跟非原子屬性
atomic: 原子屬性易茬,為setter方法加鎖 (默認(rèn)是atomic), 消耗大量的資源
nonatomic: 非原子屬性,不會(huì)加鎖抽莱, iOS開發(fā)建議 都聲明為nonatomic
線程之間的通信:
1.圖片下載
主線程添加UIImageView,子線程下載圖片范抓,在回到主線程渲染圖片;
2.NSPort ?NSMessagePort NSMachPort ?可以在線程之間進(jìn)行通信
主線程(port:8080) 傳子線程(port:8181)
事件處理
Runtime
Runloop : 既能保住線程的命食铐, 就能讓線程繼續(xù)工作匕垫;