多線程(英語(yǔ):multithreading),是指從軟件或者硬件上實(shí)現(xiàn)多個(gè)線程并發(fā)執(zhí)行的技術(shù)。具有多線程能力的計(jì)算機(jī)因有硬件支持而能夠在同一時(shí)間執(zhí)行多于一個(gè)執(zhí)行緒,進(jìn)而提升整體處理性能。 —— 維基百科
- 多線程概念
- 概念
- 例子
- 目的
- 優(yōu)缺點(diǎn)
- 多線程的同步異步
- 同步
- 異步
- 多線程的線程進(jìn)程
- 線程
- 進(jìn)程
- 多線程的方式
- PThread
- NSThread
- GCD
- GCD的概念
- GCD的簡(jiǎn)單使用
- GCD的任務(wù)和隊(duì)列
- NSOPeration
- NSOperation的簡(jiǎn)介
- NSOperation的簡(jiǎn)單使用
- NSOperation的高級(jí)功能
- 面試題
多線程的概念
-
概念
- 多線程:
一個(gè)進(jìn)程中可以開(kāi)啟多條線程,多條線程可以同時(shí)執(zhí)行不同的任務(wù)。
- 多線程:
-
例子
- 舉個(gè)例子:
酷我音樂(lè)的邊下載邊聽(tīng)歌,迅雷的邊下載邊播放。
- 舉個(gè)例子:
-
多線程目的
將耗時(shí)操作放在后臺(tái)處理,保證UI界面的正常顯示和交互。
網(wǎng)絡(luò)操作是非常耗時(shí)的,在做網(wǎng)絡(luò)開(kāi)發(fā),所有網(wǎng)絡(luò)訪問(wèn)都是耗時(shí)操作.需要在后臺(tái)線程中執(zhí)行。
多線程開(kāi)發(fā)的原則:越簡(jiǎn)單越好。
-
多線程優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):
能‘適當(dāng)’提高程序的執(zhí)行效率,能適當(dāng)提高CPU的內(nèi)存利用率,線程上的任務(wù)執(zhí)行完成后,線程會(huì)自動(dòng)銷(xiāo)毀節(jié)省內(nèi)存。
- 缺點(diǎn):
如果開(kāi)啟線程過(guò)多會(huì)占用大量CPU資源降低程序性能。
- 優(yōu)點(diǎn):
同步異步(同步和異步是兩種執(zhí)行任務(wù)的方式。)
-
同步
- 同步:
代碼從上到下順序執(zhí)行就叫做同步執(zhí)行(多個(gè)任務(wù)依次執(zhí)行)。
- 同步:
-
異步
- 異步:
多個(gè)任務(wù)同時(shí)執(zhí)行就是異步執(zhí)行。
- 異步:
多線程的線程進(jìn)程
-
進(jìn)程
- 進(jìn)程:
在系統(tǒng)中正在運(yùn)行的一個(gè)程序叫做進(jìn)程,進(jìn)程可以類比成正在正常運(yùn)營(yíng)的公司。
- 進(jìn)程:
-
線程
- 線程:
線程可以類比成公司里的員工,程序啟動(dòng)默認(rèn)會(huì)開(kāi)啟一個(gè)線程。
- 線程:
多線程的方式
-
PThread(幾乎不用)
Pthreads定義了一套C語(yǔ)言的類型、函數(shù)與常量,它以pthread.h頭文件和一個(gè)線程庫(kù)實(shí)現(xiàn)。
-
NSThread(很少使用)
NSThread是基于線程使用,輕量級(jí)的多線程編程方法(相對(duì)GCD和NSOperation),一個(gè)NSThread對(duì)象代表一個(gè)線程,需要手動(dòng)管理線程的生命周期,處理線程同步等問(wèn)題。
-
GCD(經(jīng)常使用)
- GCD的概念
- 什么是GCD:
全稱是Grand Central Dispatch,純C語(yǔ)言的,提供了非常多強(qiáng)大的函數(shù)。
- GCD的核心:
將任務(wù)添加到隊(duì)列。
- GCD使用的兩個(gè)步驟:
創(chuàng)建任務(wù),確定要做的事情,GCD中的任務(wù)是使用BLOCK封裝的。將任務(wù)添加到隊(duì)列中,GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出,放到對(duì)應(yīng)的線程中執(zhí)行。任務(wù)的取出遵循隊(duì)列的FIFO原則 : 先進(jìn)先出,后進(jìn)后出
- 什么是GCD:
-
GCD的簡(jiǎn)單使用
-
任務(wù)添加到隊(duì)列
- (void)GCDDemo1 { // 1. 創(chuàng)建隊(duì)列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 2. 創(chuàng)建任務(wù) : 用block指定的 (無(wú)參無(wú)返回值的) void (^task)() = ^ { NSLog(@"%@",[NSThread currentThread]); }; // 3. 把任務(wù)添加到隊(duì)列 // dispatch_async : 表示任務(wù)是異步的 // dispatch_sync : 表示任務(wù)是同步的 dispatch_async(queue, task); }
-
簡(jiǎn)寫(xiě)
- (void)GCDDemo2 { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"%@",[NSThread currentThread]); }); }
-
線程間的通信
- (void)GCDDemo4 { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"假裝在努力下載...%@",[NSThread currentThread]); // 下載結(jié)束之后,回到主線程更新UI dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"假裝在更新UI...%@",[NSThread currentThread]); }); }); }
-
使用GCD的線程間的通信實(shí)現(xiàn)異步下載網(wǎng)絡(luò)圖片
-
- GCD的概念
- (void)downloadImage
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"downloadImage %@",[NSThread currentThread]);
// URL
NSURL *URL = [NSURL URLWithString:@"http://atth.eduu.com/album/201203/12/1475134_1331559643qMzc.jpg"];
// data
NSData *data = [NSData dataWithContentsOfURL:URL];
// image
UIImage *image = [UIImage imageWithData:data];
// 拿到圖片對(duì)象之后,回到主線程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"updateUI %@",[NSThread currentThread]);
self.myImageView.image = image;
[self.myImageView sizeToFit];
[self.myScrollView setContentSize:image.size];
});
});
}
-
GCD的任務(wù)和隊(duì)列
- GCD的任務(wù):
- 同步的方式執(zhí)行任務(wù) :
在當(dāng)前線程中依次執(zhí)行任務(wù)。
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- 異步的方式執(zhí)行任務(wù) :
新開(kāi)線程在新線程中執(zhí)行任務(wù)。
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 同步的方式執(zhí)行任務(wù) :
- GCD隊(duì)列:
- 串行隊(duì)列:
讓任務(wù)一個(gè)接著一個(gè)有序的執(zhí)行:不管隊(duì)列里面放的是什么任務(wù),一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù),同時(shí)只能調(diào)度一個(gè)任務(wù)執(zhí)行。
- 并發(fā)隊(duì)列:
可以讓多個(gè)任務(wù)并發(fā)/同時(shí)執(zhí)行,自動(dòng)開(kāi)啟多個(gè)線程同時(shí)執(zhí)行多個(gè)任務(wù),同時(shí)可以調(diào)度多個(gè)任務(wù)執(zhí)行。并發(fā)隊(duì)列的并發(fā)功能只有內(nèi)部的任務(wù)是異步任務(wù)時(shí),才有效。
- 串行隊(duì)列:
- GCD的任務(wù):
-
代碼小結(jié)
串行隊(duì)列+同步任務(wù)
/*
1.不開(kāi)線程
2.有序執(zhí)行
*/
- (void)GCDDemo1
{
/*
創(chuàng)建串行隊(duì)列
參數(shù)1 : 隊(duì)列的標(biāo)識(shí)符
參數(shù)2 : 隊(duì)列的屬性,決定了隊(duì)列是串行的還是并行的
DISPATCH_QUEUE_SERIAL : 串行隊(duì)列
*/
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
// 循環(huán)的創(chuàng)建了10個(gè)同步任務(wù),添加到隊(duì)列
for (NSInteger i = 0; i<10; i++) {
// 把同步任務(wù)添加到串行隊(duì)列
dispatch_sync(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"哈哈哈");
}
串行隊(duì)列+異步任務(wù)
- (void)GCDDemo2
{
// 串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i<10; i++) {
// 把異步任務(wù)添加到串行隊(duì)列
dispatch_async(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"嘿嘿嘿");
}
并行隊(duì)列+同步任務(wù)
/*
不開(kāi)線程
有序執(zhí)行
*/
- (void)GCDDemo1
{
// 創(chuàng)建并行隊(duì)列
// DISPATCH_QUEUE_CONCURRENT : 并行隊(duì)列
// 并行隊(duì)列只能決定"是否"可以同時(shí)調(diào)度多個(gè)任務(wù);不能決定開(kāi)不開(kāi)線程
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<10; i++) {
// 把同步任務(wù)添加到并行隊(duì)列
dispatch_sync(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"哈哈哈");
}
并行隊(duì)列+異步任務(wù)
/*
開(kāi)線程
無(wú)序執(zhí)行
*/
- (void)GCDDemo2
{
// 并行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<10; i++) {
// 把異步任務(wù)添加到并發(fā)隊(duì)列
dispatch_async(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"嘿嘿嘿");
}
-
NSOperation(經(jīng)常使用)
-
NSOperation的簡(jiǎn)介
是OC語(yǔ)言中基于GCD的面向?qū)ο蟮姆庋b,使用起來(lái)比GCD更加簡(jiǎn)單。提供了一些GCD不好實(shí)現(xiàn)的功能,蘋(píng)果推薦使用。NSOperation還不用關(guān)心線程和線程的聲明周期。
NSOperation是個(gè)抽象類無(wú)法直接使用。因?yàn)榉椒ㄖ挥新暶鳑](méi)有實(shí)現(xiàn)。
子類:NSInvocationOperation和NSBlockOperation,自定義NSOperation操作默是異步的。
隊(duì)列 : NSOperationQueue隊(duì)列默認(rèn)是并發(fā)的。
核心:GCD的核心 : 將任務(wù)添加到隊(duì)列中。OP的核心 : 將操作添加到隊(duì)列中。
-
NSOperation的簡(jiǎn)單使用
先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對(duì)象中,創(chuàng)建NSOperation對(duì)象。
將NSOperation對(duì)象添加到NSOperationQueue中。
NSOperationQueue會(huì)自動(dòng)將NSOperation取出來(lái)
將取出的NSOperation封裝的操作自動(dòng)放到一條對(duì)應(yīng)的新線程中執(zhí)行。
-
NSOperation的高級(jí)功能
- 最大并發(fā)數(shù)
-
設(shè)置最大并發(fā)數(shù)
// 隊(duì)列的最大并發(fā)數(shù)的屬性
// 作用 : 控制隊(duì)列同時(shí)調(diào)度任務(wù)執(zhí)行的個(gè)數(shù);
// 間接控制了線程的數(shù)量;
// 注意 : 隊(duì)列的最大并發(fā)數(shù),不是線程數(shù);
@implementation ViewController {
/// 全局隊(duì)列
NSOperationQueue *_queue;
}
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
// 設(shè)置隊(duì)列的最大并發(fā)數(shù) : 至少開(kāi)兩個(gè)
_queue.maxConcurrentOperationCount = 2;
}
演示
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self GCDDemo];
}
- (void)GCDDemo
{
for (NSInteger i = 0; i<50; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
}];
[_queue addOperation:op];
}
}
執(zhí)行結(jié)果:任務(wù)是兩個(gè)兩個(gè)的執(zhí)行。
繼續(xù)、暫停、取消全部
- 在最大并發(fā)數(shù)代碼的基礎(chǔ)上增加暫停、繼續(xù)、取消。
#pragma 取消全部
/*
1.正在執(zhí)行的操作無(wú)法被取消;
2.如果非要取消正在執(zhí)行的操作,需要自定義NSOperation
3.這個(gè)取消全部的操作有一定的時(shí)間延遲
*/
- (IBAction)cancelAll:(id)sender
{
// 移除隊(duì)列里面"所有"的操作
[_queue cancelAllOperations];
NSLog(@"取消全部 %tu",_queue.operationCount);
}
#pragma 繼續(xù)
- (IBAction)jixu:(id)sender
{
// 不掛起隊(duì)列,使隊(duì)列繼續(xù)調(diào)度任務(wù)執(zhí)行
_queue.suspended = NO;
NSLog(@"繼續(xù) %tu",_queue.operationCount);
}
#pragma 暫停
/*
1.正在執(zhí)行的操作無(wú)法被暫停
2.operationCount : 隊(duì)列里面的操作個(gè)數(shù);統(tǒng)計(jì)的是隊(duì)列里面還沒(méi)有執(zhí)行完的操作;
3.隊(duì)列里面的任務(wù)一旦執(zhí)行完,會(huì)從隊(duì)列里面移除;
*/
- (IBAction)zanting:(id)sender
{
// 掛起隊(duì)列,使隊(duì)列暫停調(diào)度任務(wù)執(zhí)行
_queue.suspended = YES;
NSLog(@"暫停 %tu",_queue.operationCount);
}
- 暫停隊(duì)列結(jié)論
將隊(duì)列掛起之后,隊(duì)列中的操作就不會(huì)被調(diào)度,但是正在執(zhí)行的操作不受影響。operationCount:操作計(jì)數(shù),沒(méi)有執(zhí)行和沒(méi)有執(zhí)行完的操作,都會(huì)計(jì)算在操作計(jì)數(shù)之內(nèi)。注意:如果先暫停隊(duì)列,再添加操作到隊(duì)列,隊(duì)列不會(huì)調(diào)度操作執(zhí)行。所以在暫停隊(duì)列之前要判斷隊(duì)列中有沒(méi)有任務(wù),如果沒(méi)有操作就不暫停隊(duì)列。
- 取消隊(duì)列結(jié)論
一旦調(diào)用的 cancelAllOperations方法,隊(duì)列中的操作,都會(huì)被移除,正在執(zhí)行的操作除外。 正在執(zhí)行的操作取消不了,如果要取消,需要自定義NSOperation。 隊(duì)列取消全部操作時(shí),會(huì)有一定的時(shí)間延遲。
- 操作間依賴關(guān)系。
場(chǎng)景:登陸-->付費(fèi)-->下載-->通知用戶
準(zhǔn)備需要執(zhí)行的操作
// 登錄
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"登錄 %@",[NSThread currentThread]);
}];
// 付費(fèi)
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"付費(fèi) %@",[NSThread currentThread]);
}];
// 下載
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載 %@",[NSThread currentThread]);
}];
// 通知用戶
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用戶 %@",[NSThread currentThread]);
}];
添加依賴(核心代碼)
/*
添加依賴關(guān)系
1.不能在操作添加到隊(duì)列之后,在建立依賴關(guān)系;因?yàn)橐呀?jīng)晚了
2.可以跨隊(duì)列建立依賴關(guān)系
3.不能建立循環(huán)依賴
*/
[op2 addDependency:op1]; // 付費(fèi)依賴登錄
[op3 addDependency:op2]; // 下載依賴付費(fèi)
[op4 addDependency:op3]; // 通知用戶依賴下載
// [op1 addDependency:op4]; // 登錄依賴通知用戶 : 循環(huán)依賴;會(huì)卡死
// 批量把操作添加到隊(duì)列
// waitUntilFinished : 是否等待前面的異步任務(wù)執(zhí)行完,在執(zhí)行后面的代碼
[_queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];
// 一個(gè)操作不能同時(shí)添加到兩個(gè)隊(duì)列
[[NSOperationQueue mainQueue] addOperation:op4];
-
結(jié)論
不能循環(huán)建立操作間依賴關(guān)系,否則隊(duì)列不調(diào)度操作執(zhí)行。
操作間可以跨隊(duì)列建立依賴關(guān)系。
要將操作間的依賴建立好了之后,再添加到隊(duì)列中,先建立操作依賴關(guān)系,再把操作添加到隊(duì)列。
面試題
-
面試題僅供參考
用 NSOPeration 和 NSOpertionQueue 處理 A,B,C 三個(gè)線程,要求執(zhí)行完 A,B 后才能執(zhí)行 C, 怎么做?
添加操作依賴,C依賴于A同時(shí)依賴于B。創(chuàng)建操作隊(duì)列,將操作添加到操作隊(duì)列中。
線程間怎么通信?
什么是線程通信:不同線程之間傳遞數(shù)據(jù),一般線程傳遞到主線程。 iOS中開(kāi)啟多線程的方式:三種。比如:在子線程下載圖片,然后回到主線程顯示圖片。
簡(jiǎn)述多線程的作用以及什么地方會(huì)用到多線程?OC實(shí)現(xiàn)多線程的方法有哪些?談?wù)劧嗑€程安全問(wèn)題的幾種解決方案?何為線程同步,如何實(shí)現(xiàn)的?分線程回調(diào)主線程方法是什么,有什么作用?
1. 耗時(shí)操作、界面卡死的時(shí)候使用多線程
2. 作用:可以同時(shí)執(zhí)行多個(gè)任務(wù),適當(dāng)提高程序的執(zhí)行效率。為了提高CPU的使用率,采用多線程的方式去同時(shí)完成幾件事互不干擾。
3. iOS中多線程的方法:NSThread、NSOperation、GCD、pthread
4. 使用場(chǎng)景:同時(shí)上傳和下載多個(gè)文件:加載網(wǎng)絡(luò)數(shù)據(jù)同時(shí)展示Loading的UI、大量數(shù)據(jù)I/O操作。
5. 資源共享造成的安全問(wèn)題:多線程環(huán)境下,當(dāng)多個(gè)線程同時(shí)操作共享資源的setter和getter方法時(shí),會(huì)造成數(shù)據(jù)的讀寫(xiě)錯(cuò)亂就是線程安全問(wèn)題。
6. 線程同步技術(shù):使多個(gè)線程一次有序的執(zhí)行,實(shí)現(xiàn)方案是加鎖,把共享資源的讀寫(xiě)操作鎖起來(lái)常用的是互斥鎖。
7. 線程間的通信:一個(gè)線程執(zhí)行完任務(wù)之后,把執(zhí)行的結(jié)果傳遞到另外一個(gè)線程叫線程間通信。線程間通信可用來(lái)在兩個(gè)線程間傳遞數(shù)據(jù)。
感謝讀到最后的朋友,最后請(qǐng)點(diǎn)贊支持一下,謝謝!