前言:
第一次接觸多線程還是在寫android的時候,咋一看,覺得這玩意好難(面試必問);其實從字眼上看多線程分為“多”跟“線程”,只要搞明白線程是什么東西,那多線程就迎刃而解了(就是多條線程同時存在嘛),哪線程是什么東西? 我門必須了解一個非?;A(chǔ)的的概念 “進程”,正在進行的程序,至于進程更多的概念就自行百度吧! ?
一個進程要想執(zhí)行任務(wù)必須要有線程(線程是進程的一條執(zhí)行路徑),每一個程序至少有一條線程,進程要執(zhí)行的任務(wù)都是放在線程中執(zhí)行的,線程的串行,一個線程中的所有任務(wù)是串行(一個任務(wù)一個任務(wù)的執(zhí)行)的,同一時間一個線程只能執(zhí)行一個任務(wù);
一個 進程中可以開啟多條線程,每條線程可以并行執(zhí)行不同的任務(wù);提高程序的執(zhí)行效率;
多線程的原理:
同一時間,CPU 只能處理一條線程,CPU很快的在多條線程中切換(調(diào)度),造成了多線程并發(fā)執(zhí)行的原理;? 如果CPU在n多個線程來回調(diào)度,會消耗大量的CPU資源; 每條線程被調(diào)度的頻次會降低;
多線程的優(yōu)點:
能適當(dāng)?shù)奶岣叱绦虻膱?zhí)行效率;能適當(dāng)?shù)奶岣哔Y源利用率;
多線程的缺點:
創(chuàng)建子線程是有開銷的,主要包括內(nèi)核數(shù)據(jù)結(jié)構(gòu),??臻g(子線程512k,主線程1m,也可以使用-setStackSize設(shè)置,必須是4k的倍數(shù)),創(chuàng)建一個線程大約是90毫秒的時間;程序設(shè)計更加復(fù)雜;多線程之間的數(shù)據(jù)共享;多個線程同時占用同一個資源;
多線程在IOS中的應(yīng)用?
主線程:默認(rèn)開啟的子線程,叫主線程 也叫 UI線程;
作用: 顯示 刷新 UI,處理UI事件, 不要將比較耗時的操作放在主線程;
線程的狀態(tài):
內(nèi)存 -》線程對象(新建狀態(tài))?
調(diào)用 Start -》就緒狀態(tài)(Runable)
CPU 調(diào)度當(dāng)前線程 ?運行狀態(tài)(Running)
調(diào)用了sleep 進入 阻塞狀態(tài)(Blocked)
正常執(zhí)行 或者 異常退出之后,就會進入死亡狀態(tài)([使用NSThread exit(0)]可以強制殺死線程)
耗時操作的執(zhí)行:
開啟子線程的的方式:
1. pthread :跨平臺的線程,使用難度比較大, ?幾乎不用;(手動創(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ū)ο?,簡單易用,可是直接操作線程對象, 偶爾使用,使用 NSThread管理多個線程非常困難;
(1) [NSThread currentThread] //跟蹤任務(wù)所在線程,適用于這三種技術(shù).
(2) [NSThread sleepForTimeInterval:] //睡眠多長時間(秒)
/**
*? 創(chuàng)建線程的方式1
*/
- (void)createThread1
{
// 1.初始化
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(download) object:nil];
thread.name=@"線程2號";
//啟動線程
[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è)備的多核并行運算;線程的生命周期是自動管理的,經(jīng)常使用;
GCD的核心概念:任務(wù)(執(zhí)行什么操作) 跟 隊列(用來存放任務(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 隊列:并發(fā)隊列(concurrent),可以讓多個任務(wù)并發(fā)執(zhí)行,并發(fā)功能只有在dispatch_async才有效,蘋果提供了DISPATCH_QUEUE_PRIORITY_DEFAULT ,使用dispatch_get_global_queue獲取就好了;
異步執(zhí)行是在執(zhí)行函數(shù)完成之后,在返回去開辟子線程;
串行隊列,一個任務(wù)一個任務(wù)的執(zhí)行,主隊列
同步和異步主要區(qū)別:能不能開啟新的線程
同步:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:可以在新的線程中執(zhí)行任務(wù),有開啟新線程的能力
并發(fā)和串行的主要區(qū)別:任務(wù)的執(zhí)行方式
并發(fā):允許多個任務(wù)同時執(zhí)行
串行:一個任務(wù)執(zhí)行完畢之后在執(zhí)行下一個任務(wù)
排列組合共有四種方式:
/**
*? 異步的+全局隊列? 最常用
*/
- (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]);
});
}
/**
*? 同步+并行隊列
*/
- (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]);
});
}
/**
*? 異步的串行隊列,? 一個一個按順序執(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]);
});
}
/**
*? 同步的串行隊列,? 一個一個按順序執(zhí)行
在主獻
*/
- (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í)行完成之后才會執(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];
// 整個程序運行過程中只執(zhí)行一次, 跟懶加載不同 ?(一次性代碼)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"只會執(zhí)行一次,資源加載 ? 線程安全的");
});
// 遍歷線程
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t) {
});
GCD隊列小案例:
//1. 隊列組
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實現(xiàn)單例模式(設(shè)計模式,保證在程序運行的過程中一個類只有一個實例, 在整個應(yīng)用程序中要共享同一份資源)
實現(xiàn)方式(想辦法讓對象的內(nèi)存空間只存在一份, 也可以在load中初始化,也可以使用@synchronized,這里就演示了);
/**
*? alloc 內(nèi)部會調(diào)用這個方法
*/
+(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)用定義好的宏就好了,是不是很簡單;
NSOperation:基于GCD的,對GCD的一層封裝,自動管理;
多線程的安全隱患:
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];
//只有將操作放在隊列中,才會異步執(zhí)行
[queue addOperation:invo];
NSBlockOperation
自定義子類繼承NSOperation
NSOperationQueue : 并發(fā)隊列 / 串行隊列
+ (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);
+ (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);
// 主隊列
[NSOperationQueue mainQueue];
// 同時包含了:串行? 并發(fā) 的功能? ,就會自動放到子線程中
NSOperationQueue *queque = [[NSOperationQueue alloc]init];
NSOperationQueue 的掛起和取消 (suspended):
[queue cancelAllOperations];//取消隊列中的任務(wù)
[queue setSuspended:YES];//暫停隊列中的任務(wù)
[queue setSuspended:NO];// 恢復(fù)隊列中的任務(wù)
addDependency : 依賴
//假如有ABC三個操作, 要求, 三個操作 異步執(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];
?資源共享: 一塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊兒資源;
比如多個線程同時訪問 同一個對象 ?同一個變量 ?同一個文件;
老生常談的demo:
1.存取錢, 多個任務(wù)在多條線程同時執(zhí)行存取錢的任務(wù);
2.賣票, 多個窗口同時在賣票,先查詢票數(shù)夠不夠,出現(xiàn)一張票被多個人買走的問題;
解決方案: 加互斥鎖 ,線程同步
優(yōu)點:能有效的防治因多線程搶奪資源造成的數(shù)據(jù)安全隱患 ?(互斥鎖的應(yīng)用場景)
缺點:需要消耗大量的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;}
補充: 原子跟非原子屬性
atomic: 原子屬性,為setter方法加鎖 (默認(rèn)是atomic), 消耗大量的資源
nonatomic: 非原子屬性,不會加鎖, iOS開發(fā)建議 都聲明為nonatomic
線程之間的通信:
1.圖片下載
主線程添加UIImageView,子線程下載圖片,在回到主線程渲染圖片;
2.NSPort ?NSMessagePort NSMachPort ?可以在線程之間進行通信
主線程(port:8080) 傳子線程(port:8181)
事件處理
Runtime
Runloop : 既能保住線程的命, 就能讓線程繼續(xù)工作;