無忌哥哥跟多線程的情緣

前言:

第一次接觸多線程還是在寫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ù)工作;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • NSThread 第一種:通過NSThread的對象方法 NSThread *thread = [[NSThrea...
    攻城獅GG閱讀 947評論 0 3
  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項。當(dāng)然也會給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 691評論 0 0
  • 歡迎大家指出文章中需要改正或者需要補充的地方,我會及時更新,非常感謝。 一. 多線程基礎(chǔ) 1. 進程 進程是指在系...
    xx_cc閱讀 7,371評論 11 70
  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項。當(dāng)然也會給出幾種多線程的案...
    伯恩的遺產(chǎn)閱讀 275,579評論 251 2,329
  • CABasicAnimation 父類是CAPropertyAnimation CABasicAnimation—...
    翻這個墻閱讀 359評論 0 0

友情鏈接更多精彩內(nèi)容