多線程原理:
- 同一時間CPU只能處理一條線程, 只有一條線程在工作
- 多線程并發執行,起始是CPU在各個線程之間快速調度的結果
- 由于CPU調度線程速度非常快,所以就造成了多線程并發的假象
一般情況下耗時操作放在子線程里面,多線程也正是解決耗時操作,防止卡住主線程產生的。
主線程
- 程序已啟動就自動創建的線程就是主線程
- 作用一般就是相應用戶點擊事件,刷新UI等等
多線程的實現方案
Paste_Image.png
- NSThread
@Parmark 第一中創建方法
//創建線程
//在內存中開辟空間
NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"thread"];
thread.name = @"my-thread";
//啟動線程 并且系統會把這個線程放到可調度程序池里面 為了方便CPU來回調用
[thread start];
//任務執行完畢后 會自動銷毀線程
- (void)run:(NSString *)param
{
//處理耗時操作
}
@Parmark 第二中創建方法
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"thread"];
- (void)run:(NSString *)param
{
//處理耗時操作
}
@Parmark 第三中創建方法
[self performSelectorInBackground:@selector(run:) withObject:@"thread"];
- (void)run:(NSString *)param
{
//處理耗時操作
}
//卡住線程睡兩秒 控制線程進入阻塞狀態
[NSThread sleepForTimeInterval:2.0];
//退出線程(強制性的)
[NSThread exit];
線程安全
- 資源共享:一塊資源 被多個線程共享 也就是多個線程訪問同一塊資源
隱患:
Paste_Image.png
解決引號 -- > 加把互斥鎖
Paste_Image.png
代碼:
###沒加互斥鎖的代碼
self.ticketCount = 100;//100張票
self.thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread1.name = @"售票員1";
self.thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread2.name = @"售票員2";
self.thread3 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread3.name = @"售票員3";
- (void)saleTicket
{
while (1) {
//先取出總數
NSInteger count = self.ticketCount;
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@賣了一張票 -- 還剩下%ld張票",[NSThread currentThread].name,_ticketCount);
}else{
NSLog(@"票已經賣完了");
break;
}
}
}
###加互斥鎖的代碼
- (void)saleTicket
{
//用同一把鎖 可以記錄線程的狀態
while (1) {
@synchronized (self) {//加鎖
//先取出總數
NSInteger count = self.ticketCount;
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@賣了一張票 -- 還剩下%ld張票",[NSThread currentThread].name,_ticketCount);
}else{
NSLog(@"票已經賣完了");
break;
}
}
}
}
線程間的通訊
//開辟一個子線程
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelectorInBackground:@selector(downLoad) withObject:nil];
}
//子線程要做的事
- (void)downLoad
{
NSURL * url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/5ab5c9ea15ce36d358d27ee43ef33a87e850b114.jpg"];
//下載圖片
NSData * data = [NSData dataWithContentsOfURL:url];
//生成圖片
UIImage * image = [UIImage imageWithData:data];
//回到主線程刷新UI
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
// [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
}
GCD的基本使用
- 任務: 執行操作
- 隊列: 存放任務
- 隊列的類型
- 并發隊列 (只有在)dispatch_async下才有效
- 串行隊列
同步 - 異步:主要影響是能不能開新線程
- 同步只能在當前線程執行任務,不能開啟新線程
- 異步可以在新的線程中執行任務,能開啟新線程
串行 - 并發:主要影響任務的執行方式
- 串行:一個任務執行完畢 執行下一個任務
- 并發:多個任務同時執行
使用步驟
- 定制任務:確定想要做的事情
- 將任務添加到隊列中
- GCD會自動將隊列中的任務取出來,放到對應的線程中執行
- 任務的去除遵循:先進先出 后進后出 的原則
代碼:
//異步線程
dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
//同步線程
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
###異步函數+并發隊列 (可以開啟多條線程 并且可以同時執行)
//創建一個并發隊列 DISPATCH_QUEUE_CONCURRENT:隊列類型
dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", DISPATCH_QUEUE_CONCURRENT);
//將任務加入隊列
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
#@pargam 或者這種寫法
//獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//將任務加入隊列
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
###同步函數+并發隊列 (不會開啟新的線程)
//獲得全局的并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//同步函數
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
###異步函數+串行對列 (可以開線程 但是不能同時執行)
//串行隊列 沒有全局的 只能手動創建
dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
###同步函數+串行對列 (不可以開線程 )
//串行隊列 沒有全局的 只能手動創建
dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
主隊列
- GCD自帶的一種特殊的串行隊列
- 放到主隊列的任務都會,在主線程中執行
- 使用dispatch_get_main_queue()獲取主隊列
###主隊列+異步函數(只會在主線程中執行任務)
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
###主隊列+同步函數(線程沖突 不會執行任何操作)
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
各種隊列的執行效果
Paste_Image.png
GCD的線程之間通訊
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL * url = [NSURL URLWithString:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1479864304&di=99dcf40127f2dc4273536f73d0951638&src=http://d.hiphotos.baidu.com/image/pic/item/2e2eb9389b504fc2065e2bd2e1dde71191ef6de0.jpg"];
NSData * data = [NSData dataWithContentsOfURL:url];
UIImage * image = [UIImage imageWithData:data];
//回到主線程
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
GCD中還有另外一個執行任務的函數
//在它前面的函數執行完畢才執行它的任務,在它后面的函數在它執行完畢后才開始執行
dispatch_barrier_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
iOS延時執行的函數
//延時兩秒執行run方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//執行的操作
});
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//一次性函數
//此函數只執行一次
});
GCD隊列組
- 用隊列組下載多張圖片并且合成一張圖片
//創建一個隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創建一個隊列組
dispatch_group_t group = dispatch_group_create();
//1.下載圖片1
dispatch_group_async(group, queue, ^{
NSURL * url = [NSURL URLWithString:@"http://d.hiphotos.baidu.com/image/h%3D200/sign=4241e02c86025aafcc3279cbcbecab8d/562c11dfa9ec8a13f075f10cf303918fa1ecc0eb.jpg"];
NSData * data = [NSData dataWithContentsOfURL:url];
UIImage * image = [UIImage imageWithData:data];
self.image1 = image;
});
//2.下載圖片2
dispatch_group_async(group, queue, ^{
NSURL * url = [NSURL URLWithString:@"http://e.hiphotos.baidu.com/image/pic/item/8cb1cb134954092359d94e479758d109b3de4952.jpg"];
NSData * data = [NSData dataWithContentsOfURL:url];
UIImage * image = [UIImage imageWithData:data];
self.image2 = image;
});
//3.將圖片1和圖片2合成一張新的圖片
dispatch_group_notify(group, queue, ^{
//能保證組里面的任務都完成了
//能來到這里說明前兩張圖片一定下載完了
//開啟圖形上下文
UIGraphicsBeginImageContext(CGSizeMake(336, 440));
//繪制圖片
[self.image1 drawInRect:CGRectMake(0, 0, 168, 220)];
[self.image2 drawInRect:CGRectMake(168, 0, 168, 220)];
//獲取上下文的圖片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
//結束上下文
UIGraphicsEndImageContext();
//回到主線程顯示圖片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
//4.將合成后的圖片顯示出來
GCD實現單利
###第一種方式
static Person * _person;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone: zone];
});
return _instance;
}
+ (instancetype)defaultManger
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_person = [[self alloc]init];
});
return _person;
}
//記得遵守NSCopying協議
//實現此方法為了保證copy的時候 訪問的是同一個對象
- (id)copyWithZone:(NSZone *)zone
{
return _person;
}
###第二種方式
static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
@synchronized (self) {//加鎖 防止多線程訪問出問題
if (_instance == nil)
{
_instance = [self allocWithZone:zone];
}
}
return _instance;
}
+ (instancetype)sharedInstance
{
@synchronized (self) {//加鎖 防止多線程訪問出問題
if (_instance == nil)
{
_instance = [[self alloc]init];
}
}
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}