進程
什么是進程?
- 進程是指在系統(tǒng)中
正在運行的一個應(yīng)用程序。 - 每個進程之間是
獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)。
比如同時打開QQ音樂、Xcode,系統(tǒng)就會分別啟動2個進程
thread-01.png
通過“活動監(jiān)視器”可以查看Mac系統(tǒng)中所開啟的進程
線程
什么是線程?
- 1個進程要想執(zhí)行
任務(wù),必須得有線程(每1個進程至少要有1條線程)。 -
線程是進程的基本執(zhí)行單元,一個進程(程序)的所有任務(wù)都在線程中執(zhí)行。
比如使用酷狗播放音樂、使用迅雷下載電影,都需要在線程中執(zhí)行
thread-02.png
線程的串行
- 1個線程中任務(wù)的執(zhí)行是
串行的; - 如果要在1個線程中執(zhí)行多個任務(wù),那么只能一個一個地按順序執(zhí)行這些任務(wù);
- 也就是說,在同一時間內(nèi),1個線程只能執(zhí)行1個任務(wù)。
比如在1個線程中下載3個文件(分別是文件A、文件B、文件C)
thread-03.png
因此,也可以認(rèn)為線程是進程中的1條執(zhí)行路徑
進程和線程的比較
- 線程是CPU調(diào)用(執(zhí)行任務(wù))的最小單位。
- 進程是CPU分配資源和調(diào)度的單位。
- 一個程序可以對應(yīng)多個進程,一個進程中可以有多個線程,但至>少要有一個線程。
- 同一個進程內(nèi)的線程共享進程的資源。
多線程
基本概念
- 1個進程中可以開啟多條線程,每條線程可以
并行(同時)執(zhí)行不同的任務(wù)。
線程的并行:
- 并行即同時執(zhí)行。
比如同時開啟3條線程分別下載3個文件(分別是文件A、文件B、文件C)
thread-04.png
多線程并發(fā)執(zhí)行的原理:
- 同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)。
- 多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間
調(diào)度(切換)。- 如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象。
思考:如果線程非常非常多,會發(fā)生什么情況?
1.CPU會在N多線程之間調(diào)度,CPU會累死,消耗大量的CPU資源。
2.每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)。
多線程的優(yōu)缺點:
- 優(yōu)點
1、能適當(dāng)提高程序的執(zhí)行效率。
2、能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)。
- 缺點
1、創(chuàng)建線程是有開銷的,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、棧空間(子線程512KB、主線程1MB,也可以使用-setStackSize:設(shè)置,但必須是4K的倍數(shù),而且最小是16K),創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間。
2、如果開啟大量的線程,會降低程序的性能。
3、線程越多,CPU在調(diào)度線程上的開銷就越大。
4、程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享。
多線程在iOS開發(fā)中的應(yīng)用
主線程:
- 一個iOS程序運行后,默認(rèn)會開啟1條線程,稱為
主線程”或“UI線程”。
主線程的主要作用:
- a、
顯示\刷新UI界面。- b、
處理UI事件(比如點擊事件、滾動事件、拖拽事件等)。
主線程的使用注意 !
a、不要將比較耗時的操作放到主線程中(耗時操作會卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種“卡”的壞體驗)。b、UI必須放在主線程中處理。
耗時操作的執(zhí)行
- a、如果將耗時操作放在
主線程。
thread-05.png
問題在哪?
1.在用戶點擊按鈕5秒后才給出反應(yīng)
- b、如果將耗時操作放在 子線程(后臺線程、非主線程)。
thread-06.png - 好處在哪?
1.在用戶點擊按鈕那一刻就有反應(yīng)。
2.能同時處理耗時操作和用UI控件的事件。
iOS開發(fā)中 多線程 的實線方案
| 技術(shù)方案 | 簡介 | 語言 | 線程生命周期 | 使用頻率 |
|---|---|---|---|---|
| pthread | a.一套通用的多線程API b.適用于Unix\Linux\Windows等系統(tǒng); c.跨平臺\可移植 d.使用難度大 |
C | 程序員管理 | 幾乎不用 |
| NSThread | a.使用更加面向?qū)ο?br>b.簡單易用,可直接操作線程對象 | OC | 程序員管理 | 偶爾使用 |
| GCD | a.旨在替代NSThread等線程技術(shù) b.充分利用設(shè)備的多核 |
C | 自動管理 | 經(jīng)常使用 |
| NSOperation | a.基于GCD(底層是GCD) b.比GCD多了一些更單實用的功能 c.使用更加面向?qū)ο?/td> | OC | 自動管理 | 經(jīng)常使用 |
pthread
- pthread的基本使用
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@end
@implementation ViewController
- (IBAction)btnClick:(id)sender {
NSLog(@"mainThread:%@",[NSThread mainThread]);
//pthread創(chuàng)建線程,執(zhí)行任務(wù)
//01 包含頭文件
//02 創(chuàng)建線程對象
pthread_t thread = nil;
//03 創(chuàng)建線程,執(zhí)行任務(wù)
/* 參數(shù)說明
*
* 第一個參數(shù):線程對象 傳地址
* 第二個參數(shù):線程的屬性 (優(yōu)先級)
* 第三個參數(shù):指向函數(shù)的指針
* 第四個參數(shù):傳給第三個參數(shù)的(參數(shù))
*/
pthread_create(&thread, NULL, run, NULL);
}
//技巧:(*)改寫成函數(shù)的名稱,補全參數(shù)
void *run(void *str)
{
//NSLog(@"run-------%@",[NSThread currentThread]);
//把耗時操作放在子線程中執(zhí)行
for (int i = 0; i < 1000000; ++i) {
NSLog(@"%zd---%@",i,[NSThread currentThread]);
}
return NULL;
}
//$\color{red}{正在運行}$
@end
NSThread
- 一個NSThread對象就代表一條線程
- 主線程相關(guān)用法
+ (NSThread *)mainThread; // 獲得主線程
- (BOOL)isMainThread; // 是否為主線程
+ (BOOL)isMainThread; // 是否為主線程
- 獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];
- 創(chuàng)建、啟動線程:
- 創(chuàng)建線程方式1
/**
* 需要手動調(diào)用 start方法開啟線程
*/
-(void)createThread1
{
//01 創(chuàng)建線程對象
/* 參數(shù)說明
*
* 第一個參數(shù):目標(biāo)對象
* 第二個參數(shù):方法選擇器 要執(zhí)行的任務(wù)(方法)
* 第三個參數(shù):調(diào)用函數(shù)需要傳遞的參數(shù)
*/
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
//02 啟動線程
[thread start];
}
- 創(chuàng)建線程方式2
//創(chuàng)建線程后自動啟動線程
-(void)createThread2
{
//直接分離出一條子線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
- 創(chuàng)建線程方式3
//隱式創(chuàng)建并啟動線程
-(void)createThread3
{
//開啟后臺線程
[self performSelectorInBackground:@selector(run) withObject:nil];
}
- 三種創(chuàng)建線程方式對比
方式1:代碼量更大|能夠拿到線程對象(需要手動開啟)
方式2:分離出子線程(不需要手動開啟),無法拿到線程對象進行詳細設(shè)置(名字|優(yōu)先級)
方式3:開啟后臺線程 (不需要手動開啟),無法拿到線程對象進行詳細設(shè)置(名字|優(yōu)先級)
- 線程的屬性設(shè)置
(名稱|優(yōu)先級):
//設(shè)置線程的名字
- (void)setName:(NSString *)name;
- (NSString *)name;
//設(shè)置線程的優(yōu)先級 `范圍 0.0~1.0`
// 默認(rèn)是0.5
//1.0最高的
//優(yōu)先級更高的線程,被CPU調(diào)度到的概率會更高
- (void)setThreadPriority:(double)priority;
- (double)threadPriority;
- 線程的生命周期:當(dāng)線程內(nèi)部的任務(wù)執(zhí)行完畢會被釋放
- 線程的狀態(tài)
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];

- 控制線程狀態(tài)
啟動線程
- (void)start;
// 進入就緒狀態(tài) -> 運行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢,自動進入死亡狀態(tài)
阻塞(暫停)線程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 進入阻塞狀態(tài)
強制停止線程
+ (void)exit;
// 進入死亡狀態(tài)
注意:一旦線程停止(死亡)了,就不能再次開啟任務(wù)
- 多線程的安全隱患
a、資源共享
1>1塊資源可能會被多個線程共享,也就是“多個線程可能會同一時間訪問同一塊資源”。
2>比如多個線程訪問同一個對象、同一個變量、同一個文件。
b、當(dāng)多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題。
-
安全隱患分析
thread-08.png -
安全隱患解決 – 互斥鎖
thread-09.png 互斥鎖:
1.互斥鎖使用格式
為代碼添加同步鎖(互斥鎖)
/*
* token:鎖對象 (要使用全局的對象) 建議直接使用self
* {} 要加鎖的代碼段
* 注意點:①加多把鎖死無效的;②要注意加鎖的位置
*/
@synchronized(token) {
// 需要鎖定的代碼
}
注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的。
2.互斥鎖的優(yōu)缺點
優(yōu)點:能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題。
缺點:需要消耗大量的CPU資源。
3.互斥鎖的使用前提
多條線程可能存在同一時間搶奪同一塊資源(即:多個線程同時訪問同一個變量)。
4.相關(guān)專業(yè)術(shù)語:線程同步
a>`線程同步的意思是`:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))。
b>互斥鎖,就是使用了線程同步技術(shù)。
- 售票案例
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic)NSThread *threadA;
@property (strong, nonatomic)NSThread *threadB;
@property (strong, nonatomic)NSThread *threadC;
//票總數(shù)
@property (assign, nonatomic)NSInteger totalCount;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化票總數(shù)
self.totalCount = 100;
self.threadA = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
self.threadB = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
self.threadC = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
self.threadA.name = @"售票員A";
self.threadB.name = @"售票員B";
self.threadC.name = @"售票員C";
[self.threadA start];
[self.threadB start];
[self.threadC start];
}
- (void)saleTickets{
while (1) {
//為代碼添加同步鎖(互斥鎖)
/*
* token:鎖對象 (要使用全局的對象) 建議直接使用self
* {} 要加鎖的代碼段
* 注意點: ①加多把鎖死無效的 ②要注意加鎖的位置
*/
//先加鎖
@synchronized (self) {
//先讀取余票數(shù)
NSInteger count = self.totalCount;
if (count>0) {
//售出一張(重新寫入)
self.totalCount = count - 1;
// for (NSInteger i = 0; i < 100000000; i++) {
//
// }
NSLog(@"%@售出一張票,余票:%zd",[NSThread currentThread].name,self.totalCount);
}else{
NSLog(@"%@:票已經(jīng)售空",[NSThread currentThread].name);
break;
}
}
}
}
@end
- 線程間通信
什么叫做線程間通信?
1、 在1個進程中,線程往往不是孤立存在的,多個線程之間需要經(jīng)常進行通信。線程間通信的體現(xiàn):
1、1個線程傳遞數(shù)據(jù)給另1個線程。
2、在1個線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另1個線程繼續(xù)執(zhí)行任務(wù)。線程間通信常用方法
/* 參數(shù)說明
*
* 第一個參數(shù):方法選擇器 回到主線程要做什么(方法)
* 第二個參數(shù):調(diào)用函數(shù)需要傳遞的參數(shù)
* 第三個參數(shù):是否等待該方法執(zhí)行完畢才繼續(xù)往下執(zhí)行 YES:等待
*/
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
-
線程間通信案例
thread-10.png
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//創(chuàng)建子線程方式一
// NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downLoadImage) object:nil];
// [thread start];
//創(chuàng)建子線程方式二
[NSThread detachNewThreadSelector:@selector(downLoadImage) toTarget:self withObject:nil];
//創(chuàng)建子線程方式三(隱式創(chuàng)建線程)
// [self performSelectorInBackground:@selector(downLoadImage) withObject:nil];
}
//執(zhí)行下載任務(wù)
- (void)downLoadImage
{
NSLog(@"---%@---",[NSThread currentThread]);
NSURL *url = [NSURL URLWithString:@"https://ns-strategy.cdn.bcebos.com/ns-strategy/upload/fc_big_pic/part-00654-2651.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData];
//回到主線程刷新UI界面
/* 參數(shù)說明
*
* 第一個參數(shù):方法選擇器 回到主線程要做什么(方法)
* 第二個參數(shù):調(diào)用函數(shù)需要傳遞的參數(shù)
* 第三個參數(shù):是否等待該方法執(zhí)行完畢才繼續(xù)往下執(zhí)行 YES:等待
*/
//方式一
// [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
//方式二
[self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
//方式三
// [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
}
//回到主線程刷新UI界面
- (void)showImage:(UIImage *)image{
NSLog(@"---%@---",[NSThread currentThread]);
self.imageView.image = image;
}
@end
- 原子和非原子屬性
1、OC在定義屬性時有
nonatomic和atomic兩種選擇
2、atomic:原子屬性,為setter方法加鎖(默認(rèn)就是atomic)
3、nonatomic:非原子屬性,不會為setter方法加鎖
- 原子和非原子屬性的選擇?
1、
nonatomic和atomic對比
2、atomic:線程安全,需要消耗大量的資源
3、nonatomic:非線程安全,適合內(nèi)存小的移動設(shè)備
- iOS開發(fā)的建議
1、所有屬性都聲明為
nonatomic
2、盡量避免多線程搶奪同一塊資源
3、盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力
GCD
- 基本概念:
1、全稱是Grand Central Dispatch,可譯為
牛逼的中樞調(diào)度器。
2、純C語言,提供了非常多強大的函數(shù)。
- GCD的優(yōu)勢
1、GCD是蘋果公司為
多核的并行運算提出的解決方案
2、GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)
3、GCD會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
4、程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼
任務(wù)和隊列
GCD中有2個核心概念
1、任務(wù):執(zhí)行什么操作。
2、隊列:用來存放任務(wù)。GCD的使用就2個步驟
1、定制任務(wù):確定想做的事情。
2、將任務(wù)添加到隊列中:(同步函數(shù)|異步函數(shù))
a、GCD會自動將隊列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行;
b、任務(wù)的取出遵循隊列的FIFO原則:先進先出,后進后出。封裝任務(wù)的函數(shù):
1、同步函數(shù)
dispatch_sync
a.不具備開啟線程的能力,不能開線程
b.任務(wù)的執(zhí)行方式:同步執(zhí)行
2、異步函數(shù)
dispatch_async
a.具備開啟線程的能力,能開線程
b.任務(wù)的執(zhí)行方式:異步執(zhí)行
執(zhí)行任務(wù)
- GCD中有2個用來執(zhí)行任務(wù)的常用函數(shù)
- 用
同步的方式執(zhí)行任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:隊列
block:任務(wù)
- 用
異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 同步和異步的區(qū)別:(決定了是否具備開線程的能力)
同步:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
- 隊列的類型:GCD的隊列可以分為
2大類型:并發(fā)隊列、串行隊列
1.
并發(fā)隊列(Concurrent Dispatch Queue)
a.可以讓多個任務(wù)并發(fā)(同時)執(zhí)行自動開啟多個線程同時執(zhí)行任務(wù)
b.并發(fā)功能只有在異步dispatch_async函數(shù)下才有效
2.
串行隊列(Serial Dispatch Queue)
a.讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))
- 隊列的創(chuàng)建
使用dispatch_queue_create函數(shù)創(chuàng)建隊列
/**
*第一個參數(shù): 隊列名稱
*第一個參數(shù): 隊列的類型
*/
dispatch_queue_t queue = dispatch_queue_create(const char *label, // 隊列名稱
dispatch_queue_attr_t attr); // 隊列的類型
- 創(chuàng)建
并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("queue_Name", DISPATCH_QUEUE_CONCURRENT);
- GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊列,供整個應(yīng)用使用,可以無需手動創(chuàng)建
使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊列
/**
*第一個參數(shù): 隊列的優(yōu)先級
*第一個參數(shù): 此參數(shù)暫時無用,用0即可
*/
dispatch_queue_t queue = dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 隊列的優(yōu)先級
unsigned long flags); // 此參數(shù)暫時無用,用0即可
- 獲得全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 全局并發(fā)隊列的優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
- 創(chuàng)建
串行隊列
使用dispatch_queue_create函數(shù)創(chuàng)建串行隊列
// 創(chuàng)建串行隊列(隊列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("queue_Name", NULL);
- 使用
主隊列(跟主線程相關(guān)聯(lián)的隊列)
1.主隊列是GCD自帶的一種特殊的串行隊列。
2.放在主隊列中的任務(wù),都會放到主線程中執(zhí)行。
3.使用dispatch_get_main_queue()獲得主隊列。
dispatch_queue_t queue = dispatch_get_main_queue();。
- 有4個術(shù)語比較容易混淆:
同步、異步、并發(fā)、串行
1.
同步和異步主要影響:能不能開啟新的線程
同步:只是在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
2.
并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):允許多個任務(wù)并發(fā)(同時)執(zhí)行
串行:一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)
- GCD 的基本使用
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
#pragma mark -----------------------
#pragma mark Events
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self syncConcurrent];
//[self performSelectorInBackground:@selector(syncMain) withObject:nil];
}
#pragma mark -----------------------
#pragma mark GCD的基本使用(函數(shù) + 隊列)
//異步函數(shù) + 并發(fā)隊列:會開啟多條子線程,所有的任務(wù)并發(fā)執(zhí)行
//注意:開幾條線程并不是由任務(wù)的數(shù)量決定的,是有GCD內(nèi)部自動決定的
-(void)asyncConcurrent
{
//01 創(chuàng)建隊列
/* 參數(shù)說明
*
* 第一個參數(shù):C語言的字符串 給隊列起個名字(建議:com.DB001.www.DownloadQueue)
* 第二個參數(shù):類型
* DISPATCH_QUEUE_CONCURRENT 并發(fā)隊列
* DISPATCH_QUEUE_SERIAL 串行隊列
*/
//創(chuàng)建并發(fā)隊列
//dispatch_queue_t queue = dispatch_queue_create("com.DB001.www.DownloadQueue", DISPATCH_QUEUE_CONCURRENT);
//獲取全局并發(fā)隊列
//DISPATCH_QUEUE_PRIORITY_DEFAULT == 0 默認(rèn)優(yōu)先級
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSLog(@"-----start----");
//02 封裝任務(wù),把任務(wù)添加到隊列
dispatch_async(queue, ^{
NSLog(@"1-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"4-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"5-----%@",[NSThread currentThread]);
});
NSLog(@"-----end----");
}
//異步函數(shù) + 串行隊列:會開啟一條子線程,所有的任務(wù)在該子線程中串行執(zhí)行
-(void)asyncSerial
{
//01 創(chuàng)建隊列
/* 參數(shù)說明
*
* 第一個參數(shù):C語言的字符串 給隊列起個名字(建議:com.DB001.www.DownloadQueue)
* 第二個參數(shù):類型
* DISPATCH_QUEUE_CONCURRENT 并發(fā)隊列
* DISPATCH_QUEUE_SERIAL 串行隊列
*/
dispatch_queue_t queue = dispatch_queue_create("com.DB001.www.DownloadQueue", DISPATCH_QUEUE_SERIAL);
//02 封裝任務(wù),把任務(wù)添加到隊列
dispatch_async(queue, ^{
NSLog(@"1-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"4-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"5-----%@",[NSThread currentThread]);
});
}
//同步函數(shù) + 并發(fā)隊列:不會開啟子線程,所有的任務(wù)在當(dāng)前線程中串行執(zhí)行
-(void)syncConcurrent
{
//01 創(chuàng)建隊列
/* 參數(shù)說明
*
* 第一個參數(shù):C語言的字符串 給隊列起個名字(建議:com.DB001.www.DownloadQueue)
* 第二個參數(shù):類型
* DISPATCH_QUEUE_CONCURRENT 并發(fā)隊列
* DISPATCH_QUEUE_SERIAL 串行隊列
*/
dispatch_queue_t queue = dispatch_queue_create("com.DB001.www.DownloadQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"-----start----");
//02 封裝任務(wù),把任務(wù)添加到隊列
dispatch_sync(queue, ^{
NSLog(@"1-----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"4-----%@",[NSThread currentThread]);
});
NSLog(@"-----end----");
}
//同步函數(shù) + 串行隊列:不會開啟子線程,所有的任務(wù)在當(dāng)前線程中串行執(zhí)行
-(void)syncSerial
{
//01 創(chuàng)建隊列
/* 參數(shù)說明
*
* 第一個參數(shù):C語言的字符串 給隊列起個名字(建議:com.DB001.www.DownloadQueue)
* 第二個參數(shù):類型
* DISPATCH_QUEUE_CONCURRENT 并發(fā)隊列
* DISPATCH_QUEUE_SERIAL 串行隊列
*/
dispatch_queue_t queue = dispatch_queue_create("com.DB001.www.DownloadQueue", DISPATCH_QUEUE_SERIAL);
//02 封裝任務(wù),把任務(wù)添加到隊列
dispatch_sync(queue, ^{
NSLog(@"1-----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"4-----%@",[NSThread currentThread]);
});
}
//異步函數(shù) + 主隊列:不會開線程,所有的任務(wù)都在主線程中串行執(zhí)行
-(void)asyncMain
{
//01 獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
//02 封裝任務(wù)并把任務(wù)添加到隊列
dispatch_async(queue, ^{
NSLog(@"1-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"4-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"5-----%@",[NSThread currentThread]);
});
}
/**
* 同步函數(shù) + 主隊列:
* 這時會產(chǎn)生 -- “死鎖”問題
* 產(chǎn)生死鎖的原因:是因為在主隊列中的任務(wù)必須在主線程中執(zhí)行,當(dāng)主隊列中有任務(wù)時,主隊列就會安排主線程來執(zhí)行任務(wù),但是在調(diào)度之前會先檢查主線程的狀態(tài)(是否在忙)如果在忙就暫停調(diào)度直到主線程空閑位置;又因為是同步的方式在執(zhí)行任務(wù),任務(wù)1執(zhí)行完之后才會執(zhí)行之后的任務(wù),這時主線程是繁忙的狀態(tài),會一直卡在執(zhí)行任務(wù)1之前,出現(xiàn)了相互等待的現(xiàn)象(即死鎖)
* 解決“死鎖”問題: [self performSelectorInBackground:@selector(syncMain) withObject:nil];
*/
-(void)syncMain
{
//01 獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"--------");
//02 封裝任務(wù)并把任務(wù)添加到隊列
dispatch_sync(queue, ^{
NSLog(@"1-----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"4-----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"5-----%@",[NSThread currentThread]);
});
}
@end
任務(wù)的執(zhí)行方式:
(1) 同步執(zhí)行:必須等當(dāng)前任務(wù)執(zhí)行完畢,才能執(zhí)行后面的任務(wù)
(2 )異步執(zhí)行:不必須等當(dāng)前任務(wù)執(zhí)行完畢,就能執(zhí)行后面的任務(wù)
- 各種組合情況:
同步函數(shù) + 串行隊列 不會開線程,所有的任務(wù)在當(dāng)前線程串行執(zhí)行
同步函數(shù) + 并發(fā)隊列 不會開線程,所有的任務(wù)在當(dāng)前線程串行執(zhí)行
同步函數(shù) + 主隊列 死鎖
異步函數(shù) + 串行隊列 會開1條線程,所有的任務(wù)在子線程中串行執(zhí)行;
異步函數(shù) + 并發(fā)隊列 會開N條線程,所有的任務(wù)在子線程中并發(fā)執(zhí)行 (?? 線程的數(shù)量!= 任務(wù)的數(shù)量);
異步函數(shù) + 主隊列 不會開線程,所有的任務(wù)在主線程串行執(zhí)行。
總結(jié):
開線程的情況:①異步函數(shù) ② 不能是主隊列
開幾條線程:看隊列,如果是并發(fā)隊列那么就N條,如果是串行隊列那么就開1條
- 各種隊列的執(zhí)行效果
| 并發(fā)隊列 | 手動創(chuàng)建的串行隊列 | 主隊列 | |
|---|---|---|---|
| 同步(sync) | a.沒有開啟新線程 b. 串行執(zhí)行任務(wù) |
a.沒有開啟新線程 b. 串行執(zhí)行任務(wù) |
a.沒有開啟新線程 b. 串行執(zhí)行任務(wù) |
| 異步(async) | a.有開啟新線程 b. 并發(fā)執(zhí)行任務(wù) |
a.有開啟新線程 (僅開1條)b. 串行執(zhí)行任務(wù) |
a.沒有開啟新線程 b. 串行執(zhí)行任務(wù) |
注意
使用sync函數(shù)往當(dāng)前串行隊列中添加任務(wù),會卡住當(dāng)前的串行隊列
GCD的線程之間的通信
- 從子線程回到主線程
//獲取全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// 執(zhí)行耗時的異步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主線程,執(zhí)行UI刷新操作
});
});
GCD的其他函數(shù):
(1) 一次性代碼
dispatch_once
(2) 延時執(zhí)行dispatch_after
(3) 快速迭代dispatch_apply
(4) 柵欄函數(shù)dispatch_barrier_async
- (1) 一次性代碼:
dispatch_once - 整個程序運行過程中只會執(zhí)行一次 + 本身是線程安全
- 應(yīng)用:單例模式
使用
dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self once];
}
-(void)once
{
static dispatch_once_t onceToken;
NSLog(@"++++++%zd",onceToken);
內(nèi)部實現(xiàn)原理:判斷onceToken的值 == 0 來決定是否執(zhí)行block中的任務(wù)
dispatch_once(&onceToken, ^{
NSLog(@"once------");
});
}
- (2) 延時執(zhí)行:
dispatch_after - iOS常見的延時執(zhí)行有三種方法:
1、調(diào)用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再調(diào)用self的run方法
2、使用GCD函數(shù)
dispatch_queue_t queue = dispatch_get_main_queue()“主隊列”
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);“全局并發(fā)隊列”
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
// 2秒后執(zhí)行這里的代碼...
});
3、使用NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self delay];
}
-(void)delay
{
NSLog(@"--delay-");
//延遲執(zhí)行
// [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
//GCD中的延遲執(zhí)行
/* 參數(shù)說明
*
* 第一個參數(shù):設(shè)置時間(GCD中的時間單位是納秒)
* 第二個參數(shù):隊列(決定block中的任務(wù)在哪個線程中執(zhí)行,如果是主隊列就是主線程,否在就在子線程)
* 第三個參數(shù):設(shè)置任務(wù)
* 原理:(哪個簡單)
* A 先把任務(wù)提交到隊列,然后等兩秒再執(zhí)行 錯誤
* B 先等兩秒,再把任務(wù)提交到隊列 正確
*/
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
// NSLog(@"-----GCD------%@",[NSThread currentThread]);
// });
}
-(void)run
{
NSLog(@"run--%@",[NSThread currentThread]);
}
- (3) 快速迭代:
dispatch_apply
使用
dispatch_apply函數(shù)能進行快速迭代遍歷
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
// 執(zhí)行10次代碼,index順序不確定
});
//快速迭代(遍歷)
-(void)apply
{
//在當(dāng)前線程中串行執(zhí)行(提高效率)
//1000 1 ==>10天
//1000 10 ==>1天
for (int i = 0; i < 10; ++i) {
NSLog(@"%zd----%@",i,[NSThread currentThread]);
}
NSLog(@"____________");
/* 參數(shù)說明
*
* 第一個參數(shù):遍歷的次數(shù)
* 第二個參數(shù):隊列
*/
//并發(fā)隊列:會開啟多條子線程和主線程一起并發(fā)的執(zhí)行任務(wù)
//主隊列:死鎖
//普通的串行的隊列:和for循環(huán)一樣
dispatch_queue_t queue = dispatch_queue_create("DownloadQueue", DISPATCH_QUEUE_SERIAL);
dispatch_apply(10, queue, ^(size_t i) {
NSLog(@"%zd----%@",i,[NSThread currentThread]);
});
}
快速迭代的案例:將from文件夾中的文件剪切到to文件夾中
-(void)test2
{
//01 獲得文件夾的路徑
NSString *from = @"/Users/xiaomage/Desktop/from";
NSString *to = @"/Users/xiaomage/Desktop/to";
//02 獲得該文件夾下面的所有文件
NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:from];
NSLog(@"%@",subPaths);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_apply(subPaths.count, queue , ^(size_t i) {
NSString *fileName = subPaths[i];
//拼接文件的全路徑
NSString *fromFullPath = [from stringByAppendingPathComponent:fileName];
NSString *toFullPath = [to stringByAppendingPathComponent:fileName];
//執(zhí)行文件的剪切操作
[[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
NSLog(@"%@--%@--%@",fromFullPath,toFullPath,[NSThread currentThread]);
});
}
- (4) 柵欄函數(shù):
dispatch_barrier_async
在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會執(zhí)行
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
?? 注意:這個queue不能是全局的并發(fā)隊列
-(void)barrier
{
//需求:有4個任務(wù),要求開啟多條線程來執(zhí)行這些任務(wù)
//增加需求:新的任務(wù)++++++,要求在12執(zhí)行之后執(zhí)行,要保證該任務(wù)執(zhí)行完之后才能執(zhí)行后面的34任務(wù)
//柵欄函數(shù):前面的任務(wù)并發(fā)執(zhí)行,后面的任務(wù)也是并發(fā)執(zhí)行
//當(dāng)前面的任務(wù)執(zhí)行完畢之后,執(zhí)行柵欄函數(shù)中的任務(wù),等該任務(wù)執(zhí)行完畢后再執(zhí)行后面的任務(wù)
//?? 不能使用全局并發(fā)隊列
//01 獲得隊列
dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
//dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//02 封裝任務(wù),并且添加到隊列
dispatch_async(queue, ^{
NSLog(@"1-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@",[NSThread currentThread]);
});
//柵欄函數(shù)
dispatch_barrier_async(queue, ^{
NSLog(@"+++++++++++");
});
dispatch_async(queue, ^{
NSLog(@"3-----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"4-----%@",[NSThread currentThread]);
});
}
GCD的隊列組:
- 基本概念:隊列組是用來管理隊列中任務(wù)的執(zhí)行。
- 使用步驟:
- 創(chuàng)建隊列組:
dispatch_group_create();- 創(chuàng)建隊列:
dispatch_queue_create(param1 , parame2);- 使用隊列組異步函數(shù)來封裝任務(wù), 然后提交到隊列中
dispatch_group_async(group, queue, ^{
});- 把當(dāng)前所有任務(wù)執(zhí)行的情況, 都納入到隊列組
監(jiān)聽的范圍中
- 異步函數(shù)
dispatch_async(queue, ^{ })和隊列組dispatch_group_async(group, queue, ^{})的區(qū)別:
(1)
dispatch_group_async(group, queue, ^{ });
1、 封裝任務(wù)
2 、把任務(wù)添加到隊列
3 、監(jiān)聽任務(wù)的執(zhí)行情況
(2)dispatch_async(queue, ^{ });
1 、封裝任務(wù)
2 、把任務(wù)添加到隊列
- 隊列組的簡單使用:
監(jiān)聽任務(wù)的完成
1、所有的任務(wù)會并發(fā)的執(zhí)行(不按序)。
2、所有的異步函數(shù), 都是添加到隊列中, 然后再納入到隊列組的監(jiān)聽范圍。
3、使用dispatch_group_notify(隊列組, 隊列)函數(shù), 來監(jiān)聽在這個函數(shù)上面的任務(wù)執(zhí)行是否完成, 當(dāng)任務(wù)完成, 就會調(diào)用這個方法。
- 案例需求:
需求1:4個任務(wù),在子線程中并發(fā)執(zhí)行,添加任務(wù)"+++++",必須等所有的任務(wù)都執(zhí)行完再執(zhí)行。
-(void)group{
1. 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
2. 創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
3.封裝任務(wù),添加到隊列并監(jiān)聽任務(wù)的執(zhí)行情況
dispatch_group_async(group, queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"4---%@",[NSThread currentThread]);
});
4.攔截通知,當(dāng)所有的任務(wù)都執(zhí)行完畢后,執(zhí)行++++操作
dispatch_group_notify(group, queue, ^{
NSLog(@"++++++++%@",[NSThread currentThread]);
});
}
需求2:攔截
多個隊列中任務(wù)。
-(void)group{
1. 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
2. 創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue1 = dispatch_queue_create("Test1", DISPATCH_QUEUE_CONCURRENT);
3.封裝任務(wù),添加到隊列并監(jiān)聽任務(wù)的執(zhí)行情況
dispatch_group_async(group, queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue1, ^{
NSLog(@"4---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue1, ^{
NSLog(@"5---%@",[NSThread currentThread]);
});
4.攔截通知,當(dāng)所有的任務(wù)都執(zhí)行完畢后,執(zhí)行++++操作
dispatch_group_notify(group, queue, ^{
NSLog(@"++++++++%@",[NSThread currentThread]);
});
}
探討:
dispatch_group_notify內(nèi)部是異步的執(zhí)行
dispatch_group_notify隊列:決定該block在哪個線程中處理(主:主線程 非主隊列:子線程)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"++++++++%@",[NSThread currentThread]);
});
- 使用隊列組的小實例 --- 下載兩張圖片, 然后合成后展示
- (void)downLoadTwoPicturesThenComposeOnePicture{
//需求:開子線程下載兩張圖片,合成圖片,顯示出來
//01 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//02 獲得并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//03 下載圖片1
dispatch_group_async(group, queue, ^{
//001 url
NSURL *URL = [NSURL URLWithString:@"http://pridelands.ru/pictures/real/puma/Cougar_018.jpg"];
//002 Data
NSData *imageData = [NSData dataWithContentsOfURL:URL];
//003 轉(zhuǎn)換
self.image1 = [UIImage imageWithData:imageData];
NSLog(@"Download1-----%@",[NSThread currentThread]);
});
//04 下載圖片2
dispatch_group_async(group, queue, ^{
//001 url
NSURL *URL = [NSURL URLWithString:@"http://m2.quanjing.com/2m/pust017/1574r-012067.jpg"];
//002 Data
NSData *imageData = [NSData dataWithContentsOfURL:URL];
//003 轉(zhuǎn)換
self.image2 = [UIImage imageWithData:imageData];
NSLog(@"Download2-----%@",[NSThread currentThread]);
});
//05 攔截通知,合成圖片
dispatch_group_notify(group, queue, ^{
//001 開啟上下文
UIGraphicsBeginImageContext(CGSizeMake(300, 300));
//002 畫圖1,2
[self.image1 drawInRect:CGRectMake(0, 0, 150, 300)];
[self.image2 drawInRect:CGRectMake(150, 0, 150, 300)];
NSLog(@"Combie-----%@",[NSThread currentThread]);
//003 根據(jù)上下文得到圖片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//004 關(guān)閉上下文
UIGraphicsEndImageContext();
//06 顯示圖片(線程間通信)
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"UI-----%@",[NSThread currentThread]);
});
});
}
NSOperation 與 NSOperationQueue
-
NSOperation的作用
配合使用
NSOperation和NSOperationQueue也能實現(xiàn)多線程編程。
-
NSOperation和NSOperationQueue實現(xiàn)多線程的具體步驟:
- 先將需要執(zhí)行的操作封裝到一個
NSOperation對象中- 然后將
NSOperation對象添加到NSOperationQueue中- 系統(tǒng)會自動將
NSOperationQueue中的NSOperation取出來- 將取出的
NSOperation封裝的操作放到一條新線程中執(zhí)行
NSOperation的子類NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類。使用
NSOperation子類有3種:
NSInvocationOperationNSBlockOperation- 自定義子類繼承
NSOperation,實現(xiàn)內(nèi)部相應(yīng)的方法
NSInvocationOperation
- 創(chuàng)建
NSInvocationOperation對象.
-(id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;- 調(diào)用
start方法開始執(zhí)行操作.
-(void)start;
一旦執(zhí)行操作,就會調(diào)用target的sel方法.
注意:
默認(rèn)情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作。
只有將NSOperation放到一個NSOperationQueue中,才會異步執(zhí)行操作。
-(void)invocationOperation
{
//01 封裝操作對象
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
//02 執(zhí)行操作
[op1 start];
}
NSBlockOperation
- 創(chuàng)建NSBlockOperation對象
+(id)blockOperationWithBlock:(void (^)(void))block;
-(void)blockOperation
{
//操作:NSBlockOperation對象
//任務(wù):block
//01 封裝操作對象
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread currentThread]);
}];
//02 執(zhí)行操作
[op1 start];
}
- 通過addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;
注意:只要NSBlockOperation封裝的操作數(shù) > 1,就會異步執(zhí)行操作.
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
//追加任務(wù)
//當(dāng)一個操作中的任務(wù)數(shù)量>1的時候,就會開啟子線程和當(dāng)前線程一起執(zhí)行任務(wù)
[op3 addExecutionBlock:^{
NSLog(@"4----%@",[NSThread currentThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"5----%@",[NSThread currentThread]);
}];
[op3 start];
NSOperationQueue
NSOperationQueue的作用.NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的.- 如果將
NSOperation添加到NSOperationQueue(操作隊列)中,系統(tǒng)會自動異步執(zhí)行NSOperation中的操作.
- 添加操作到NSOperationQueue中
-(void)addOperation:(NSOperation *)op;
-(void)addOperationWithBlock:(void (^)(void))block;
- 操作隊列的基本使用(操作 + 隊列)
-(void)invocationOperationWithQueue
{
//01 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//02 封裝操作
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download3) object:nil];
//03 把操作添加到隊列
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
注意:
- 開啟幾條子線程并不是由操作的數(shù)量決定的
-[queue addOperation:op1];該方法內(nèi)部會自動的調(diào)用start方法執(zhí)行任務(wù)
-(void)blockOperationWithQueue
{
//01 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//02 封裝操作對象
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2----%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
//03 把操作添加到隊列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
//簡便方法:該方法內(nèi)部首先會把block中的任務(wù)封裝成一個操作(Operation),然后把該操作直接添加到隊列
[queue addOperationWithBlock:^{
NSLog(@"4----%@",[NSThread currentThread]);
}];
}
簡便方法:該方法內(nèi)部首先會把block中的任務(wù)封裝成一個操作(Operation),然后把該操作直接添加到隊列
[queue addOperationWithBlock:^{
NSLog(@"4----%@",[NSThread currentThread]);
}];
- 最大并發(fā)數(shù):同時執(zhí)行的任務(wù)數(shù)
最大并發(fā)數(shù)的相關(guān)方法
-(NSInteger)maxConcurrentOperationCount;
-(void)setMaxConcurrentOperationCount:(NSInteger)cnt;
案例:讓多個操作在子線程中順序執(zhí)行
把隊列的最大并發(fā)數(shù)設(shè)置為 1 ,就可以達到多個操作在子線程中順序執(zhí)行的效果。
-(void)changeSerialQueue
{
//01 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//02 封裝操作對象
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]);
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"5----%@",[NSThread currentThread]);
}];
//設(shè)置最大并發(fā)數(shù)對于任務(wù)數(shù)量大于1的操作是無效的
//當(dāng)操作中任務(wù)數(shù)量>1的時候,會開啟多條子線程和當(dāng)前線程一起工作
// [op5 addExecutionBlock:^{
// NSLog(@"6----%@",[NSThread currentThread]);
// }];
//
// [op5 addExecutionBlock:^{
// NSLog(@"7----%@",[NSThread currentThread]);
// }];
//設(shè)置最大并發(fā)數(shù) == 同一時間最多有多少條線程在執(zhí)行
//maxConcurrentOperationCount == 0 不能執(zhí)行任務(wù)
//NSOperationQueueDefaultMaxConcurrentOperationCount = -1 -1指的是一個最大的值(表示不受限制)
queue.maxConcurrentOperationCount = 1;
//03 把操作添加到隊列中
// [queue addOperation:op1];
// [queue addOperation:op2];
// [queue addOperation:op3];
// [queue addOperation:op4];
// [queue addOperation:op5];
[queue addOperations:@[op1,op2,op3,op4,op5] waitUntilFinished:YES];
NSLog(@"------");
}
-
注意點:設(shè)置最大并發(fā)數(shù)對于任務(wù)數(shù)量大于1的操作是無效的
創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
封裝操作
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4----%@",[NSThread currentThread]);
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"5----%@",[NSThread currentThread]);
}];
追加操作
[op5 addExecutionBlock:^{
NSLog(@"6----%@",[NSThread currentThread]);
}];
[op5 addExecutionBlock:^{
NSLog(@"7----%@",[NSThread currentThread]);
}];
設(shè)置最大并發(fā)數(shù)為1
queue.maxConcurrentOperationCount = 1;
將操作添加到隊列執(zhí)行任務(wù)
[queue addOperation:op4];
[queue addOperation:op5];
另一種添加操作到隊列的方法
[queue addOperations:@[op4,op5] waitUntilFinished:YES];
NSLog(@"--------");
這時的執(zhí)行順序?qū)τ?op5 是無效的
- 注意點:
1.maxConcurrentOperationCount == 0不能執(zhí)行任務(wù)。
2.默認(rèn)NSOperationQueueDefaultMaxConcurrentOperationCount = -1-1指的是一個最大的值(表示不受限制)。
3.最大并發(fā)數(shù)不一定等于所開的線程數(shù):所開多少條線程是由系統(tǒng)決定的。
4.[queue addOperations:@[op4,op5] waitUntilFinished:YES];這是另一種添加操作到隊列的方式,后面參數(shù):YES時,必須執(zhí)行完隊列中的操作,才能執(zhí)行之后的程序;NO時,可以不用執(zhí)行完隊列中的操作就可以執(zhí)行程序。
- 隊列的取消、暫停、恢復(fù)
- 取消隊列的所有操作
-(void)cancelAllOperations;- 提示:也可以調(diào)用
NSOperation的- (void)cancel方法取消單個操作
- 暫停和恢復(fù)隊列
-(void)setSuspended:(BOOL)b; // YES代表暫停隊列,NO代表恢復(fù)隊列
->-(BOOL)isSuspended;
- (void)start{
//01 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//02 封裝操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5000; ++i) {
NSLog(@"1--%zd--%@",i,[NSThread currentThread]);
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5000; ++i) {
NSLog(@"2--%zd--%@",i,[NSThread currentThread]);
}
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5000; ++i) {
NSLog(@"3--%zd--%@",i,[NSThread currentThread]);
}
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5000; ++i) {
NSLog(@"4--%zd--%@",i,[NSThread currentThread]);
}
}];
//設(shè)置最大并發(fā)數(shù)
queue.maxConcurrentOperationCount = 1;
//03 添加到隊列
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
self.queue = queue;
}
- (IBAction)suspendBtnClick:(id)sender {
//暫停 YES
//只能暫停當(dāng)前操作后面的操作,當(dāng)前操作不可分割必須執(zhí)行完畢
//操作是有狀態(tài)的
[self.queue setSuspended:YES];
}
- (IBAction)resumeBtnClick:(id)sender {
//恢復(fù)
[self.queue setSuspended:NO];
}
- (IBAction)cancelBtnClick:(id)sender {
//取消 取消所有的操作
//只能取消隊列中處理等待狀態(tài)的操作
[self.queue cancelAllOperations];
}
自定義操作、自定義線程
- 自定義
NSOperation的步驟很簡單- 重寫
- (void)main方法,在里面實現(xiàn)想執(zhí)行的任務(wù)
- 重寫
- (void)main方法的注意點- 自己創(chuàng)建自動釋放池(因為如果是異步操作,無法訪問主線程的自動釋放>池)
- 經(jīng)常通過
- (BOOL)isCancelled方法檢測操作是否被取消,對取消做出響應(yīng)
#import "ViewController.h"
#import "KXOperation.h"
#import "KXGThread.h"
@interface ViewController ()
@property (nonatomic, strong) NSOperationQueue *queue;
@end
@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self test2];
}
-(void)thread
{
//自定義線程
XMGThread *thread = [[XMGThread alloc]init];
[thread start];
}
- (IBAction)startBtnClick:(id)sender {
// [self test1];
[self test2];
}
- (IBAction)suspendBtnClick:(id)sender {
//暫停 YES
//只能暫停當(dāng)前操作后面的操作,當(dāng)前操作不可分割必須執(zhí)行完畢
//操作是有狀態(tài)的
[self.queue setSuspended:YES];
}
- (IBAction)resumeBtnClick:(id)sender {
//恢復(fù)
[self.queue setSuspended:NO];
}
- (IBAction)cancelBtnClick:(id)sender {
//取消 取消所有的操作
//只能取消隊列中處理等待狀態(tài)的操作
[self.queue cancelAllOperations];
}
-(void)test2
{
//01 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//02 封裝操作(執(zhí)行任務(wù))
XMGOperation *op = [[XMGOperation alloc]init];
//03 把操作添加到隊列
[queue addOperation:op]; //內(nèi)部會調(diào)用start方法 ->main
//自定義操作的好處:代碼復(fù)用
self.queue = queue;
}
@end
-------------------------------------------------------------------
#import "KXGOperation.h"
@implementation KXGOperation
//重寫內(nèi)部的main方法類告訴自定義的操作任務(wù)是什么?
-(void)main
{
// NSLog(@"main----%@",[NSThread currentThread]);
for (int i = 0; i < 10000; ++i) {
NSLog(@"1---%zd--%@",i,[NSThread currentThread]);
}
//官方建議:在自定義操作的時候每執(zhí)行完一個耗時操作就判斷一下當(dāng)前操作是否被取消,如果被取消就直接返回
if (self.isCancelled) {
return;
}
NSLog(@"++++++++++++++++");
for (int i = 0; i < 10000; ++i) {
NSLog(@"2---%zd--%@",i,[NSThread currentThread]);
}
if (self.isCancelled) {
return;
}
NSLog(@"++++++++++++++++");
for (int i = 0; i < 10000; ++i) {
NSLog(@"3d---%zd--%@",i,[NSThread currentThread]);
}
}
@end
-------------------------------------------------------------------
#import "KXGThread.h"
@implementation KXGThread
-(void)main
{
NSLog(@"MAIN----%@",[NSThread currentThread]);
}
@end
操作依賴
-
NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序
比如一定要讓操作
A執(zhí)行完后,才能執(zhí)行操作B,可以這么寫。
[operationB addDependency:operationA];// 操作B依賴于操作A。
- 可以在不同
queue的NSOperation之間創(chuàng)建依賴關(guān)系。operation.png
注意:不能相互依賴
比如:A依賴B,B依賴A
操作的監(jiān)聽
- 可以監(jiān)聽一個操作的執(zhí)行完畢
-(void (^)(void))completionBlock;
-(void)setCompletionBlock:(void (^)(void))block;
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//01 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
//02 封裝操作對象
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]);
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"5----%@",[NSThread currentThread]);
}];
//監(jiān)聽任務(wù)執(zhí)行完畢
op4.completionBlock = ^{
NSLog(@"小4任務(wù)已完成");
};
[op1 setCompletionBlock:^{
NSLog(@"老幺任務(wù)已完成");
}];
//03 設(shè)置操作依賴:4->3->2->1->5
//?? 不能設(shè)置循環(huán)依賴,結(jié)果就是兩個任務(wù)都不會執(zhí)行
[op5 addDependency:op1];
[op1 addDependency:op2];
//[op2 addDependency:op1];
[op2 addDependency:op3];
[op3 addDependency:op4];
//04 把操作添加到隊列
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
[queue2 addOperation:op5];
}
線程間的通信
01 創(chuàng)建隊列
NSOperationQueue *queue =[[NSOperationQueue alloc]init];
02封裝操作
NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
執(zhí)行任務(wù)
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
回到主線程
}];
}];
03 把操作添加到隊列
[queue addOperation:download];
- GCD和NSOperation的對比:
1)GCD是純C語言的API,而操作隊列則是Object-C的對象。
2)在GCD中,任務(wù)用塊(block)來表示,而塊是個輕量級的數(shù)據(jù)結(jié)構(gòu);
相反操作隊列中的『操作』NSOperation則是個更加重量級的Object-C對象。
3)具體該使用GCD還是使用NSOperation需要看具體的情況
NSOperation和NSOperationQueue的好處有:
1)NSOperationQueue可以方便的調(diào)用cancel方法來取消某個操作,而GCD中的任務(wù)是無法被取消的(安排好任務(wù)之后就不管了)。
2)NSOperation可以方便的指定操作間的依賴關(guān)系。
3)NSOperation可以通過KVO提供對NSOperation對象的精細控制(如監(jiān)聽當(dāng)前操作是否被取消或是否已經(jīng)完成等)
4)NSOperation可以方便的指定操作優(yōu)先級。操作優(yōu)先級表示此操作與隊列中其它操作之間的優(yōu)先關(guān)系,優(yōu)先級高的操作先執(zhí)行,優(yōu)先級低的后執(zhí)行。
5)通過自定義NSOperation的子類可以實現(xiàn)操作重用.
- 使用Crearte函數(shù)創(chuàng)建的并發(fā)隊列和全局并發(fā)隊列的主要區(qū)別:
1)全局并發(fā)隊列在整個應(yīng)用程序中本身是默認(rèn)存在的并且對應(yīng)有高優(yōu)先級、默認(rèn)優(yōu)先級、低優(yōu)先級和后臺優(yōu)先級一共四個并發(fā)隊列,我們只是選擇其中的一個直接拿來用。而Create函數(shù)是實打?qū)嵉膹念^開始去創(chuàng)建一個隊列。
2)在iOS6.0之前,在GCD中凡是使用了帶Create和retain的函數(shù)在最后都需要做一次release操作。而主隊列和全局并發(fā)隊列不需要我們手動release。當(dāng)然了,在iOS6.0之后GCD已經(jīng)被納入到了ARC的內(nèi)存管理范疇中,即便是使用retain或者create函數(shù)創(chuàng)建的對象也不再需要開發(fā)人員手動釋放,我們像對待普通OC對象一樣對待GCD就OK。
3)在使用柵欄函數(shù)的時候,蘋果官方明確規(guī)定柵欄函數(shù)只有在和使用create函數(shù)自己的創(chuàng)建的并發(fā)隊列一起使用的時候才有效(沒有給出具體原因)
4)其它區(qū)別涉及到XNU內(nèi)核的系統(tǒng)級線程編程,不一一列舉。
多線程相關(guān)知識希望給朋友們帶來一丟丟的幫助,如有問題敬請批正?。?!









