串行并行的定義
串行:一個(gè)線程中執(zhí)行多個(gè)任務(wù),只能一個(gè)一個(gè)的按照順序執(zhí)行這些任務(wù)灿意。
并行:一個(gè)進(jìn)程中可以開(kāi)啟多條線程缤剧,每條線程可以同時(shí)執(zhí)行不同的任務(wù)
多線程的原理
同一時(shí)間,cpu只能處理1條線程汗销,只有1條線程在工作,如果存在多條線程弛针,實(shí)際上是線程間以非常非诚髯拢快的速度進(jìn)行切換,CPU調(diào)度線程的速度非澄看裕快瘾杭,看起來(lái)就像同時(shí)進(jìn)行。需要注意的是睬隶,線程不宜太多苏潜,會(huì)消耗大量CPU資源
優(yōu)點(diǎn)
能適當(dāng)提高程序的執(zhí)行效率变勇,適當(dāng)?shù)奶岣逤PU和內(nèi)存利用率
缺點(diǎn):創(chuàng)建線程是有開(kāi)銷的,線程的創(chuàng)建需要90毫秒的時(shí)間飞袋,如果大量的開(kāi)啟線程巧鸭,會(huì)降低程序的性能麻捻,而且線程越多,程序越復(fù)雜郑叠,比如線程之間的通信乡革,多線程的數(shù)據(jù)共享
安全隱患
- 資源共享
一塊資源可以被多個(gè)線程同時(shí)訪問(wèn)(例如,3個(gè)線程同時(shí)修改一個(gè)文件)
解決方案:加同步鎖 @synchronized(鎖對(duì)象){需要鎖定的代碼 }
多個(gè)線程之間鎖對(duì)象必須是同一個(gè)沸版,保證線程之間用的鎖是同一個(gè),否則鎖是無(wú)效的心包,一般用self就可以
使用前提:多條線程搶奪同一塊資源的時(shí)候,需要對(duì)數(shù)據(jù)訪問(wèn)修改等問(wèn)題時(shí)
@synchronized (self) {
// 要鎖定的操作
NSLog(@"%@", [NSThread currentThread]);
}
- 原子和非原子屬性
atomic:原子屬性区宇,setter方法加鎖值戳,調(diào)用setter方法前加鎖,調(diào)用之后鎖打開(kāi)卧晓,防止多條線程對(duì)屬性值的修改逼裆,會(huì)消耗大量的資源
nonatomic:非原子屬性赦政,適合內(nèi)存小的移動(dòng)設(shè)備,setter方法和getter方法大部分都在主線程訪問(wèn)桐愉,如有在子線程訪問(wèn)的从诲,可單獨(dú)加鎖靡羡,在寫代碼的過(guò)程中盡量避免多線程搶奪同一塊資源
線程間通信
包括線程間數(shù)據(jù)傳遞、在一個(gè)線程中執(zhí)行完特定任務(wù)后跳轉(zhuǎn)到其他線程繼續(xù)執(zhí)行任務(wù)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
_picView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
[self.view addSubview:_picView];
// 創(chuàng)建線程
[self performSelectorInBackground:@selector(downLoadPic) withObject:nil];
}
- (void)downLoadPic
{
NSURL *url = [NSURL URLWithString:@""];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *pic = [UIImage imageWithData:data];
// 回主線程
// [_picView performSelectorOnMainThread:@selector(setPicView:) withObject:pic waitUntilDone:YES];
[_picView performSelector:@selector(setPicView:) onThread:[NSThread mainThread] withObject:pic waitUntilDone:YES];
}
線程實(shí)現(xiàn)方法
1.pthread
基于C語(yǔ)言,需要手動(dòng)管理纳像,平時(shí)幾乎不用
先導(dǎo)入頭文件#import <pthread.h>
// 指向函數(shù)的指針
void *run(void *param)
{
return NULL;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
pthread_t thread;
pthread_create(&thread, NULL, run, NULL);
}
2.NSThread
基于OC, 需要手動(dòng)管理,常用的就幾個(gè)方法
+(NSThread *)mainThread; // 獲取主線程
+(BOOL)isMainThread; // 是否為主線程
-(BOOL)isMainThread; // 是否為主線程
+(NSThread *)currentThread; // 獲取當(dāng)前線程
-(void)setName:(NSString *)name; // 設(shè)置線程名字
-(NSString *)name; // 獲取線程名字
+(void)sleepUntilDate:(NSDate *)date; // 休眠到什么時(shí)間
+(void)sleepForTimeInterval:(NSTimeInterval)ti; // 休眠幾秒鐘
+(void)exit; // 退出當(dāng)前線程憔购,停止了就不能恢復(fù)了
三種創(chuàng)建方法如下
- (void)run
{
// 打印當(dāng)前線程
NSLog(@"%@", [NSThread currentThread]);
}
- (void)createThread1
{
// 創(chuàng)建線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:@"louise"];
// 啟動(dòng)線程
[thread start];
}
- (void)createThread2
{
// 不需要啟動(dòng)玫鸟,沒(méi)有返回值,拿不到剛剛創(chuàng)建的線程妥曲,所以不能設(shè)置名字檐盟,用來(lái)處理簡(jiǎn)單的操作
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:@"louise"];
}
- (void)createThread3
{
// 隱式創(chuàng)建,看不出來(lái)是創(chuàng)建線程
[self performSelectorInBackground:@selector(run) withObject:@"louise"];
}
3.GCD
優(yōu)勢(shì):自動(dòng)管理線程的生命周期(創(chuàng)建葵萎,任務(wù)的調(diào)度,銷毀線程)
核心概念:任務(wù)(執(zhí)行什么操作)羡忘、隊(duì)列(用來(lái)存放任務(wù))執(zhí)行任務(wù):
同步:只能在當(dāng)前線程中執(zhí)行任務(wù)卷雕,不具備開(kāi)啟新線程的能力:dispatch_sync
異步:可以在新的線程中執(zhí)行任務(wù)爽蝴,具備開(kāi)啟新線程的能力:dispatch_async隊(duì)列的類型
并發(fā)隊(duì)列:可以讓多個(gè)任務(wù)并發(fā)執(zhí)行纫骑,并發(fā)功能只能在異步(dispatch_async)函數(shù)下才有效
串行隊(duì)列:讓任務(wù)一個(gè)接著一個(gè)的執(zhí)行-
區(qū)分幾個(gè)比較容易混淆的詞語(yǔ)
同步和異步:在于能否開(kāi)啟新的線程先馆,同步只能在當(dāng)前線程執(zhí)行任務(wù),不具備開(kāi)線程的能力梅惯,異步可以在新的線程中執(zhí)行任務(wù)仿野,具備開(kāi)線程的能力并發(fā)和串行:并發(fā)是多個(gè)任務(wù)并發(fā)執(zhí)行脚作,串行是一個(gè)任務(wù)接一個(gè)任務(wù)的執(zhí)行
并發(fā)/串行隊(duì)列&同步/異步代碼實(shí)現(xiàn)
// label相當(dāng)于隊(duì)列的名字
// DISPATCH_QUEUE_CONCURRENT 并發(fā)隊(duì)列
// DISPATCH_QUEUE_SERIAL 串行隊(duì)列
// 1.創(chuàng)建一個(gè)串行隊(duì)列
// dispatch_queue_t queue = dispatch_queue_create("serial_chuan", DISPATCH_QUEUE_SERIAL);
// 1.創(chuàng)建一個(gè)并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("concurrent_bing", DISPATCH_QUEUE_CONCURRENT);
// 2.將任務(wù)同步加入隊(duì)列
// dispatch_sync(queue, ^{
// 要執(zhí)行的代碼
// NSLog(@"1----%@", [NSThread currentThread]);
// });
// dispatch_sync(queue, ^{
// 要執(zhí)行的代碼
// NSLog(@"2----%@", [NSThread currentThread]);
// });
// 2.將任務(wù)異步加入隊(duì)列
dispatch_async(queue, ^{
// 要執(zhí)行的代碼
NSLog(@"1----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
// 要執(zhí)行的代碼
NSLog(@"2----%@", [NSThread currentThread]);
});
- GCD其他常用函數(shù)
- 延時(shí)操作
除了NSObject的performSelector和NSTimer之外還可以使用GCD的方法 dispatch_after(dispatch_time_t when, dispatch_queue_t queue, ^(void)block)
- 延時(shí)操作
// 2.0 * NSEC_PER_SEC:兩秒之后執(zhí)行
// dispatch_queue_t queue:在哪個(gè)隊(duì)列里執(zhí)行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 兩秒后執(zhí)行這里的代碼
});
- 一次性代碼
整個(gè)程序運(yùn)行過(guò)程中只調(diào)用一次劣针,不是每個(gè)對(duì)象各調(diào)用一次,這里需要注意
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 整個(gè)程序運(yùn)行過(guò)程中只調(diào)用一次
});
- 快速迭代
同時(shí)遍歷所有的數(shù)據(jù) dispatch_apply(size_t iterations, dispatch_queue_t queue, ^(size_t)block)
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
// 同時(shí)遍歷10個(gè)數(shù)據(jù),比如剪切復(fù)制粘貼等 是不需要順序的鸟廓,可以同時(shí)進(jìn)行操作
NSLog(@"----%zu-----%@", index, [NSThread currentThread]);
});
4.NSOperationQueue
通過(guò)NSOperation 和NSOperationQueue配合實(shí)現(xiàn)多線程
優(yōu)點(diǎn):不需要管理線程的創(chuàng)建和調(diào)用引谜,只要注重執(zhí)行的操作即可
原理:將操作封裝到NSOperation對(duì)象中擎浴,然后將NSOperation對(duì)象添加到NSOperationQueue隊(duì)列中退客,系統(tǒng)會(huì)自動(dòng)將任務(wù)取出放到線程中執(zhí)行
NSOperation的子類:NSOperation是個(gè)抽象類,使用它必須用它的子類: NSInvocationOperation链嘀,NSBlockOperation怀泊,自定義繼承自NSOperation的子類.
NSOperation的代碼實(shí)現(xiàn)
- (void)InvocationOperation
{
// 在當(dāng)前線程執(zhí)行,只有將任務(wù)添加到隊(duì)列中才會(huì)開(kāi)新線程
// NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// [op start];
}
- (void)BlockOperation
{
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 在當(dāng)前線程執(zhí)行
NSLog(@"1----%@",[NSThread currentThread]);
}];
// 添加額外的任務(wù)务傲,在子線程執(zhí)行
[op addExecutionBlock:^{
NSLog(@"2----%@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
[op start];
}
}
- (void)run
{
NSLog(@"%@",[NSThread currentThread]);
}
- NSOperationQueue的代碼實(shí)現(xiàn) - 串行
// 默認(rèn)的都是串行隊(duì)列
- (void)NSOperationQueue
{
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建NSInvocationOperation任務(wù)
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 只要將任務(wù)添加到隊(duì)列售葡,就新開(kāi)啟線程挟伙,并且不需要調(diào)用start方法
[queue addOperation:op1];
[queue addOperation:op2];
// 創(chuàng)建NSBlockOperation任務(wù)
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
// 還可再添加額外的任務(wù)尖阔,在子線程執(zhí)行
[op3 addExecutionBlock:^{
NSLog(@"3-----1----%@",[NSThread currentThread]);
}];
// 只要添加到隊(duì)列中介却,就新開(kāi)線程
[queue addOperation:op3];
}
- (void)run
{
NSLog(@"%@",[NSThread currentThread]);
}
當(dāng)異步執(zhí)行的代碼特別多特別長(zhǎng)的時(shí)候就使用自定義NSOperation块茁,可以將任務(wù)封裝起來(lái),使用的時(shí)候只需要調(diào)用自定義類的alloc init方法轴或,然后把任務(wù)添加到隊(duì)列中即可
- NSOperationQueue的代碼實(shí)現(xiàn) - 并行
只要設(shè)置最大并發(fā)數(shù)不為1就是并行
- (void)NSOperationQueue
{
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 設(shè)置最大并發(fā)操作數(shù)仰禀,如果設(shè)置最大并發(fā)數(shù)為1,就是串行隊(duì)列
queue.maxConcurrentOperationCount = 3;
// 創(chuàng)建NSBlockOperation任務(wù)
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread currentThread]);
}];
// 只要添加到隊(duì)列中饺蚊,就新開(kāi)線程
[queue addOperation:op1];
// 也可以不創(chuàng)建NSOperation 直接創(chuàng)建任務(wù)
[queue addOperationWithBlock:^{
NSLog(@"2-------%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"3-------%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"4-------%@", [NSThread currentThread]);
}];
}
- 線程的暫停
需要注意的是悬嗓,暫停是等當(dāng)前的任務(wù)執(zhí)行完成之后暫停后面的任務(wù)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.suspended = YES;
- 取消線程任務(wù)
任務(wù)取消了之后不可恢復(fù)污呼,同樣的,也是等當(dāng)前的任務(wù)執(zhí)行完成之后在取消
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue cancelAllOperations];
- 線程間依賴
- (void)addDependency
{
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建NSBlockOperation任務(wù)
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2----%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4----%@",[NSThread currentThread]);
}];
// 設(shè)置依賴,op3依賴于op1和op2包竹,需要op1和op2都執(zhí)行完才能執(zhí)行op3
[op3 addDependency:op1];
[op3 addDependency:op2];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
}
需要注意的是燕酷,可以跨隊(duì)列依賴,但是不能互相依賴(A依賴B,B依賴A)
- 線程間通信
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建NSBlockOperation任務(wù)
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
// 開(kāi)子線程
NSLog(@"1----%@",[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 回到主線程
}];
}];
[queue addOperation:op1];
到這里多線程基本上就介紹完了周瞎,是不是很簡(jiǎn)單呢苗缩,多多實(shí)踐就會(huì)發(fā)現(xiàn)真的很簡(jiǎn)單,有什么問(wèn)題可以私信我喲声诸,如果喜歡酱讶,感覺(jué)對(duì)你有點(diǎn)幫助,可以點(diǎn)個(gè)關(guān)注喲O(∩_∩)O哈哈