多線程(英語:multithreading)星澳,是指從軟件或者硬件上實現(xiàn)多個線程并發(fā)執(zhí)行的技術。具有多線程能力的計算機因有硬件支持而能夠在同一時間執(zhí)行多于一個執(zhí)行緒旱易,進而提升整體處理性能禁偎。 —— 維基百科
- 多線程概念
- 概念
- 例子
- 目的
- 優(yōu)缺點
- 多線程的同步異步
- 同步
- 異步
- 多線程的線程進程
- 線程
- 進程
- 多線程的方式
- PThread
- NSThread
- GCD
- GCD的概念
- GCD的簡單使用
- GCD的任務和隊列
- NSOPeration
- NSOperation的簡介
- NSOperation的簡單使用
- NSOperation的高級功能
- 面試題
多線程的概念
-
概念
- 多線程:
一個進程中可以開啟多條線程,多條線程可以同時執(zhí)行不同的任務阀坏。
- 多線程:
-
例子
- 舉個例子:
酷我音樂的邊下載邊聽歌,迅雷的邊下載邊播放如暖。
- 舉個例子:
-
多線程目的
將耗時操作放在后臺處理,保證UI界面的正常顯示和交互忌堂。
網(wǎng)絡操作是非常耗時的盒至,在做網(wǎng)絡開發(fā),所有網(wǎng)絡訪問都是耗時操作.需要在后臺線程中執(zhí)行士修。
多線程開發(fā)的原則:越簡單越好枷遂。
-
多線程優(yōu)缺點
- 優(yōu)點:
能‘適當’提高程序的執(zhí)行效率,能適當提高CPU的內(nèi)存利用率棋嘲,線程上的任務執(zhí)行完成后,線程會自動銷毀節(jié)省內(nèi)存酒唉。
- 缺點:
如果開啟線程過多會占用大量CPU資源降低程序性能。
- 優(yōu)點:
同步異步(同步和異步是兩種執(zhí)行任務的方式沸移。)
-
同步
- 同步:
代碼從上到下順序執(zhí)行就叫做同步執(zhí)行(多個任務依次執(zhí)行)痪伦。
- 同步:
-
異步
- 異步:
多個任務同時執(zhí)行就是異步執(zhí)行。
- 異步:
多線程的線程進程
-
進程
- 進程:
在系統(tǒng)中正在運行的一個程序叫做進程雹锣,進程可以類比成正在正常運營的公司流妻。
- 進程:
-
線程
- 線程:
線程可以類比成公司里的員工,程序啟動默認會開啟一個線程笆制。
- 線程:
多線程的方式
-
PThread(幾乎不用)
Pthreads定義了一套C語言的類型绅这、函數(shù)與常量,它以pthread.h頭文件和一個線程庫實現(xiàn)在辆。
-
NSThread(很少使用)
NSThread是基于線程使用证薇,輕量級的多線程編程方法(相對GCD和NSOperation)度苔,一個NSThread對象代表一個線程,需要手動管理線程的生命周期浑度,處理線程同步等問題寇窑。
-
GCD(經(jīng)常使用)
- GCD的概念
- 什么是GCD:
全稱是Grand Central Dispatch,純C語言的箩张,提供了非常多強大的函數(shù)甩骏。
- GCD的核心:
將任務添加到隊列。
- GCD使用的兩個步驟:
創(chuàng)建任務先慷,確定要做的事情饮笛,GCD中的任務是使用BLOCK封裝的。將任務添加到隊列中论熙,GCD會自動將隊列中的任務取出福青,放到對應的線程中執(zhí)行。任務的取出遵循隊列的FIFO原則 : 先進先出,后進后出
- 什么是GCD:
-
GCD的簡單使用
-
任務添加到隊列
- (void)GCDDemo1 { // 1. 創(chuàng)建隊列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 2. 創(chuàng)建任務 : 用block指定的 (無參無返回值的) void (^task)() = ^ { NSLog(@"%@",[NSThread currentThread]); }; // 3. 把任務添加到隊列 // dispatch_async : 表示任務是異步的 // dispatch_sync : 表示任務是同步的 dispatch_async(queue, task); }
-
簡寫
- (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]); // 下載結束之后,回到主線程更新UI dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"假裝在更新UI...%@",[NSThread currentThread]); }); }); }
-
使用GCD的線程間的通信實現(xiàn)異步下載網(wǎng)絡圖片
-
- 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];
// 拿到圖片對象之后,回到主線程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"updateUI %@",[NSThread currentThread]);
self.myImageView.image = image;
[self.myImageView sizeToFit];
[self.myScrollView setContentSize:image.size];
});
});
}
-
GCD的任務和隊列
- GCD的任務:
- 同步的方式執(zhí)行任務 :
在當前線程中依次執(zhí)行任務脓诡。
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- 異步的方式執(zhí)行任務 :
新開線程在新線程中執(zhí)行任務无午。
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 同步的方式執(zhí)行任務 :
- GCD隊列:
- 串行隊列:
讓任務一個接著一個有序的執(zhí)行:不管隊列里面放的是什么任務,一個任務執(zhí)行完畢后祝谚,再執(zhí)行下一個任務宪迟,同時只能調(diào)度一個任務執(zhí)行。
- 并發(fā)隊列:
可以讓多個任務并發(fā)/同時執(zhí)行交惯,自動開啟多個線程同時執(zhí)行多個任務次泽,同時可以調(diào)度多個任務執(zhí)行。并發(fā)隊列的并發(fā)功能只有內(nèi)部的任務是異步任務時商玫,才有效。
- 串行隊列:
- GCD的任務:
-
代碼小結
串行隊列+同步任務
/*
1.不開線程
2.有序執(zhí)行
*/
- (void)GCDDemo1
{
/*
創(chuàng)建串行隊列
參數(shù)1 : 隊列的標識符
參數(shù)2 : 隊列的屬性,決定了隊列是串行的還是并行的
DISPATCH_QUEUE_SERIAL : 串行隊列
*/
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
// 循環(huán)的創(chuàng)建了10個同步任務,添加到隊列
for (NSInteger i = 0; i<10; i++) {
// 把同步任務添加到串行隊列
dispatch_sync(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"哈哈哈");
}
串行隊列+異步任務
- (void)GCDDemo2
{
// 串行隊列
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i<10; i++) {
// 把異步任務添加到串行隊列
dispatch_async(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"嘿嘿嘿");
}
并行隊列+同步任務
/*
不開線程
有序執(zhí)行
*/
- (void)GCDDemo1
{
// 創(chuàng)建并行隊列
// DISPATCH_QUEUE_CONCURRENT : 并行隊列
// 并行隊列只能決定"是否"可以同時調(diào)度多個任務;不能決定開不開線程
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<10; i++) {
// 把同步任務添加到并行隊列
dispatch_sync(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"哈哈哈");
}
并行隊列+異步任務
/*
開線程
無序執(zhí)行
*/
- (void)GCDDemo2
{
// 并行隊列
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<10; i++) {
// 把異步任務添加到并發(fā)隊列
dispatch_async(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"嘿嘿嘿");
}
-
NSOperation(經(jīng)常使用)
-
NSOperation的簡介
是OC語言中基于GCD的面向對象的封裝牡借,使用起來比GCD更加簡單拳昌。提供了一些GCD不好實現(xiàn)的功能,蘋果推薦使用钠龙。NSOperation還不用關心線程和線程的聲明周期炬藤。
NSOperation是個抽象類無法直接使用。因為方法只有聲明沒有實現(xiàn)碴里。
子類:NSInvocationOperation和NSBlockOperation沈矿,自定義NSOperation操作默是異步的。
隊列 : NSOperationQueue隊列默認是并發(fā)的咬腋。
核心:GCD的核心 : 將任務添加到隊列中羹膳。OP的核心 : 將操作添加到隊列中。
-
NSOperation的簡單使用
先將需要執(zhí)行的操作封裝到一個NSOperation對象中根竿,創(chuàng)建NSOperation對象陵像。
將NSOperation對象添加到NSOperationQueue中就珠。
NSOperationQueue會自動將NSOperation取出來
將取出的NSOperation封裝的操作自動放到一條對應的新線程中執(zhí)行。
-
NSOperation的高級功能
- 最大并發(fā)數(shù)
-
設置最大并發(fā)數(shù)
// 隊列的最大并發(fā)數(shù)的屬性
// 作用 : 控制隊列同時調(diào)度任務執(zhí)行的個數(shù);
// 間接控制了線程的數(shù)量;
// 注意 : 隊列的最大并發(fā)數(shù),不是線程數(shù);
@implementation ViewController {
/// 全局隊列
NSOperationQueue *_queue;
}
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
// 設置隊列的最大并發(fā)數(shù) : 至少開兩個
_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í)行結果:任務是兩個兩個的執(zhí)行醒颖。
繼續(xù)妻怎、暫停、取消全部
- 在最大并發(fā)數(shù)代碼的基礎上增加暫停泞歉、繼續(xù)逼侦、取消。
#pragma 取消全部
/*
1.正在執(zhí)行的操作無法被取消;
2.如果非要取消正在執(zhí)行的操作,需要自定義NSOperation
3.這個取消全部的操作有一定的時間延遲
*/
- (IBAction)cancelAll:(id)sender
{
// 移除隊列里面"所有"的操作
[_queue cancelAllOperations];
NSLog(@"取消全部 %tu",_queue.operationCount);
}
#pragma 繼續(xù)
- (IBAction)jixu:(id)sender
{
// 不掛起隊列,使隊列繼續(xù)調(diào)度任務執(zhí)行
_queue.suspended = NO;
NSLog(@"繼續(xù) %tu",_queue.operationCount);
}
#pragma 暫停
/*
1.正在執(zhí)行的操作無法被暫停
2.operationCount : 隊列里面的操作個數(shù);統(tǒng)計的是隊列里面還沒有執(zhí)行完的操作;
3.隊列里面的任務一旦執(zhí)行完,會從隊列里面移除;
*/
- (IBAction)zanting:(id)sender
{
// 掛起隊列,使隊列暫停調(diào)度任務執(zhí)行
_queue.suspended = YES;
NSLog(@"暫停 %tu",_queue.operationCount);
}
- 暫停隊列結論
將隊列掛起之后,隊列中的操作就不會被調(diào)度腰耙,但是正在執(zhí)行的操作不受影響榛丢。operationCount:操作計數(shù),沒有執(zhí)行和沒有執(zhí)行完的操作沟优,都會計算在操作計數(shù)之內(nèi)涕滋。注意:如果先暫停隊列,再添加操作到隊列,隊列不會調(diào)度操作執(zhí)行挠阁。所以在暫停隊列之前要判斷隊列中有沒有任務宾肺,如果沒有操作就不暫停隊列。
- 取消隊列結論
一旦調(diào)用的 cancelAllOperations方法侵俗,隊列中的操作锨用,都會被移除,正在執(zhí)行的操作除外。 正在執(zhí)行的操作取消不了隘谣,如果要取消,需要自定義NSOperation寻歧。 隊列取消全部操作時掌栅,會有一定的時間延遲。
- 操作間依賴關系码泛。
場景:登陸-->付費-->下載-->通知用戶
準備需要執(zhí)行的操作
// 登錄
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"登錄 %@",[NSThread currentThread]);
}];
// 付費
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"付費 %@",[NSThread currentThread]);
}];
// 下載
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載 %@",[NSThread currentThread]);
}];
// 通知用戶
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用戶 %@",[NSThread currentThread]);
}];
添加依賴(核心代碼)
/*
添加依賴關系
1.不能在操作添加到隊列之后,在建立依賴關系;因為已經(jīng)晚了
2.可以跨隊列建立依賴關系
3.不能建立循環(huán)依賴
*/
[op2 addDependency:op1]; // 付費依賴登錄
[op3 addDependency:op2]; // 下載依賴付費
[op4 addDependency:op3]; // 通知用戶依賴下載
// [op1 addDependency:op4]; // 登錄依賴通知用戶 : 循環(huán)依賴;會卡死
// 批量把操作添加到隊列
// waitUntilFinished : 是否等待前面的異步任務執(zhí)行完,在執(zhí)行后面的代碼
[_queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];
// 一個操作不能同時添加到兩個隊列
[[NSOperationQueue mainQueue] addOperation:op4];
-
結論
不能循環(huán)建立操作間依賴關系猾封,否則隊列不調(diào)度操作執(zhí)行。
操作間可以跨隊列建立依賴關系噪珊。
要將操作間的依賴建立好了之后,再添加到隊列中晌缘,先建立操作依賴關系,再把操作添加到隊列痢站。
面試題
-
面試題僅供參考
用 NSOPeration 和 NSOpertionQueue 處理 A,B,C 三個線程,要求執(zhí)行完 A,B 后才能執(zhí)行 C, 怎么做磷箕?
添加操作依賴,C依賴于A同時依賴于B阵难。創(chuàng)建操作隊列岳枷,將操作添加到操作隊列中。
線程間怎么通信?
什么是線程通信:不同線程之間傳遞數(shù)據(jù)嫩舟,一般線程傳遞到主線程氢烘。 iOS中開啟多線程的方式:三種。比如:在子線程下載圖片家厌,然后回到主線程顯示圖片播玖。
簡述多線程的作用以及什么地方會用到多線程?OC實現(xiàn)多線程的方法有哪些?談談多線程安全問題的幾種解決方案?何為線程同步,如何實現(xiàn)的?分線程回調(diào)主線程方法是什么,有什么作用?
1. 耗時操作、界面卡死的時候使用多線程
2. 作用:可以同時執(zhí)行多個任務饭于,適當提高程序的執(zhí)行效率蜀踏。為了提高CPU的使用率,采用多線程的方式去同時完成幾件事互不干擾掰吕。
3. iOS中多線程的方法:NSThread果覆、NSOperation、GCD殖熟、pthread
4. 使用場景:同時上傳和下載多個文件:加載網(wǎng)絡數(shù)據(jù)同時展示Loading的UI局待、大量數(shù)據(jù)I/O操作。
5. 資源共享造成的安全問題:多線程環(huán)境下菱属,當多個線程同時操作共享資源的setter和getter方法時钳榨,會造成數(shù)據(jù)的讀寫錯亂就是線程安全問題。
6. 線程同步技術:使多個線程一次有序的執(zhí)行纽门,實現(xiàn)方案是加鎖薛耻,把共享資源的讀寫操作鎖起來常用的是互斥鎖。
7. 線程間的通信:一個線程執(zhí)行完任務之后赏陵,把執(zhí)行的結果傳遞到另外一個線程叫線程間通信饼齿。線程間通信可用來在兩個線程間傳遞數(shù)據(jù)。
感謝讀到最后的朋友蝙搔,最后請點贊支持一下缕溉,謝謝!