基本概念
- 進程:系統中正在運行的一個應用程序。
- 線程:進程想要執(zhí)行任務图贸,必須要有線程(每1個進程至少要有個1條線程)蹂季,線程是進程的基本單元,一個進程的所有任務都是在線程中執(zhí)行疏日。
- 線程的串行:1個線程中任務的執(zhí)行時串行的偿洁,若1個線程中執(zhí)行多個任務,只能一個個的按順序執(zhí)行沟优。同一時間內涕滋,1個線程只能執(zhí)行1個任務。
- 多線程
- 一個進程中開啟多條線程挠阁,每條線程可以并行(同時)執(zhí)行不同的任務
- 什么叫線程的并行
- 并行即同時執(zhí)行宾肺。若同時開啟3條線程分別下載3個文件(文件A,文件B侵俗,文件C)
- 多線程并發(fā)執(zhí)行的原理
- 同一時間CPU只能處理1條線程锨用。多線程并發(fā)執(zhí)行,只是CPU在多條線程之間進行切換隘谣,如果CPU調度線程的時間足夠快增拥,就造成了多線程并發(fā)執(zhí)行的假象。
5.多線程的優(yōu)缺點
- 優(yōu)點
- 能適當提高程序的執(zhí)行效率
- 能適當提高資源利用率(CPU,內存利用率)
- 缺點
- 開啟線程需要占用一定的內存空間寻歧,若開啟大量的線程掌栅,則會占用大量的內存空間,降低程序的性能
- 線程越多码泛,CPU在調度線程上的開銷就越大
- 程序設計更加復雜(線程之間的通信猾封,多線程的數據共享)
6.iOS中多線程的實現方案
- pthread
- NSThread
- GCD
- NSOperation
NSThread
-
創(chuàng)建方式
- [alloc init];
- 需要手動開啟線程;
- 可以拿到線程對象
- [alloc init];
分離一條子線程;
- 不可拿到線程對象;-
后臺創(chuàng)建一條線程;
- 不可拿到線程對象;
線程狀態(tài)
新建
就緒
運行
阻塞
死亡(線程死亡之后,不能再重新啟用)
-
線程安全
- 多個線程訪問同一塊區(qū)域
- 增加互斥鎖
- 相關代碼:@synchoronized(self){}(一般都是使用self)
- 這叫線程同步
原子屬性和非原子屬性
用@porperty生命一個屬性的時候弟晚,內部會進行三步操作
1.生成一個有下劃線的成員變量
2.生成一個setter方法
3.生成一個getter方法原子屬性 : atomic(為setter方法加鎖忘衍,默認就是atomic)
非原子屬性:nonatomic(不會為setter方法加鎖)
-
為什么開發(fā)中經常使用nonatomic而不是atomic
-
atomic
代表線程安全,需要消耗大量資源 -
nonatomic
代表非線程安全卿城,適合內存小的移動設備 - 在開發(fā)中盡量使用
nonatomic
- 盡量避免多線程訪問同一資源
- 盡量將加鎖、資源搶奪的業(yè)務邏輯交給服務器铅搓,從而減少客戶端的壓力
-
線程間通信
把程序中的耗時操作盡量放到子線程當中進行
必須要回到主線程中進行UI刷新
GCD
-
兩個核心概念
- 任務 : 執(zhí)行什么操作
- 隊列 : 用來儲存任務的
同步函數/異步函數
同步函數:只能在當前線程中進行任務瑟押,不具備開啟子線程的能力,立刻馬上執(zhí)行任務
異步函數:可以在新線程中執(zhí)行任務星掰,具備開啟子線程的能力
并發(fā)隊列/串行隊列
并發(fā)隊列 : 多個任務可以同時執(zhí)行多望,前提是在異步函數的情況下
可以創(chuàng)建并發(fā)隊列嫩舟,也可以獲取并發(fā)隊列。GCD里本身存在一個并發(fā)隊列
串行隊列 : 任務不能同時執(zhí)行怀偷,需一個接一個的執(zhí)行
主隊列 : 放在主隊列里面的任務只能在主線程中執(zhí)行家厌,并且也是一個接一個的執(zhí)行任務
GCD的使用
-
異步函數 + 并發(fā)隊列 : 開啟子線程,并發(fā)執(zhí)行任務
//1.創(chuàng)建隊列 dispatch_queue_t queue = dispatch_queue_creat("one",DISPATHC_QUEUE_CONCURRENT); //2.封裝任務椎工,將任務添加到隊列中 dispathc_async(queue,^{ NSLog(@"xxxx"); });
異步函數 + 串行隊列 : 開啟一條線程饭于,串行執(zhí)行任務
//1.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_queue_creat("two",DISPATCH_QUEUE_SERIAL);
dispatch_async(queue,^{
NSLog(@"xxxx");
});
- 同步函數 + 并發(fā)隊列 : 不開線程,串行執(zhí)行任務
//1.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_queue_creat("three",DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue,^{
NSLog(@"xxx");
});
- 同步函數 + 串行隊列 : 不開線程维蒙,串行執(zhí)行任務
//1.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_queue_creat("four",DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue,^{
NSLog(@"xxx");
});
- 異步函數 + 主隊列 : 不開線程掰吕,在主線程中串行執(zhí)行任務
//1.獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
//
dispatch_async(queue,^{
NSLog(@"xxx");
});
-
同步函數 + 主隊列 : 造成死鎖狀況
//獲得主隊列 dispatch_queue_t queue = dispatch_get_main_queue();
//
dispatch_sync("six",^{
NSLog(@"xxx");
});
- GCD中的線程通信
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSURL * url = [NSURL URLWithString:@"http://static.jstv.com/img/2016/5/18/20165181463528366178_0.jpg"];
NSData * imagedate = [NSData dataWithContentsOfURL:url];
UIImage * image = [UIImage imageWithData:imagedate];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
}
- GCD的常用函數
- 延遲
![GCD中的延遲](http://upload-images.jianshu.io/upload_images/1404354-5a589a7e984c4150.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![調用延遲方法](http://upload-images.jianshu.io/upload_images/1404354-b691033dde114ed0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![NSTimer](http://upload-images.jianshu.io/upload_images/1404354-63a320ee97f5eea8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 一次代碼
![一次代碼](http://upload-images.jianshu.io/upload_images/1404354-18b974230d99a0ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 柵欄函數
- 用于異步函數
- 用于控制多線程執(zhí)行任務順序
- 在使用柵欄函數的時候,*不能使用全局并發(fā)隊列颅痊,只能進行手動創(chuàng)建*殖熟。
- 柵欄函數之前的線程執(zhí)行順序,柵欄函數是沒有辦法進行控制的
![柵欄函數](http://upload-images.jianshu.io/upload_images/1404354-7b9aa4054951553b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 快速迭代
- 開啟多個線程斑响,完成快速迭代操作
- 類似于for循環(huán)
- GCD里面的快速迭代是并發(fā)隊列
- for循環(huán)里面是串行隊列
小案例:圖片的移動
思路:(使用了GCD里面的快速迭代)
1.獲得最初文件夾的路徑
2.獲得目的文件夾的為路徑
3.移動文件需要全路徑,需要對最初文件夾下的文件進行路徑拼接
4.文件名不變菱属,所以目的文件夾的文件路徑也需要進行拼接
5.然后用文件管理者進行文件移動
- 案例代碼
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index) {
- 第一個參數:遍歷的次數
- 第二個參數: 隊列,必須使用并發(fā)隊列
3.第三個參數:設置索引
};
![GCD快速迭代 圖片移動](http://upload-images.jianshu.io/upload_images/1404354-b1633fd217ea64d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 隊列組
- 用來控制隊列任務的完成情況
// 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
// 獲取全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//將隊列添加到隊列組中舰罚,執(zhí)行任務(下載圖1)
dispatch_group_async(group, queue, ^{
//確定圖片地址
NSURL * url = [NSURL URLWithString:@"http://img.2258.com/d/file/yule/mingxing/neidi/2016-04-20/6b6d95c044b5282cf5b8c78f73c23c4c.jpg"];
//根據圖片地址下載二進制數據
NSData * imageData = [NSData dataWithContentsOfURL:url];
//轉換二進制數據
self.image1 = [UIImage imageWithData:imageData];
});
//將隊列添加到隊列組中照皆,執(zhí)行任務(下載圖2)
dispatch_group_async(group, queue, ^{
//確定圖片地址
NSURL * url = [NSURL URLWithString:@"http://pic.yesky.com/uploadImages/2016/126/00/7HLRG65LQ5FJ.jpg"];
//根據圖片地址下載二進制數據
NSData * imageData = [NSData dataWithContentsOfURL:url];
//轉換二進制數據
self.image2 = [UIImage imageWithData:imageData];
});
//當隊列組中的任務完成之后會進入這個方法
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//開啟圖片上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//繪制圖片到上下文中
[self.image1 drawInRect:CGRectMake(0, 0, 100, 200)];
[self.image2 drawInRect:CGRectMake(100, 0, 100, 200)];
//獲得新圖片
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
//關閉圖片上下文
UIGraphicsEndImageContext();
//回到主線程,刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 設置圖片
self.image.image = newImage;
});
});
- 這個方法內部并不是阻塞沸停,內部本身是異步的
![方法內部](http://upload-images.jianshu.io/upload_images/1404354-100b4d4670a1fc69.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 這個方法是阻塞的膜毁,會等之前的任務執(zhí)行完成之后才能執(zhí)行
![等待](http://upload-images.jianshu.io/upload_images/1404354-c561530c74eb514e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 關于隊列組的另一種寫法
//獲得全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//在該方法后面的異步任務會被納入監(jiān)聽范圍,進入隊列組
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
//任務執(zhí)行完成之后離開隊列組
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
- 兩個異步函數的方法的區(qū)別
1. 一個用block塊封裝任務
2.一個用函數來進行任務的封裝
![兩個方法的區(qū)別](http://upload-images.jianshu.io/upload_images/1404354-dcf9193766c33fbf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 全局并發(fā)隊列和手動創(chuàng)建的并發(fā)隊列的區(qū)別
- 全局并發(fā)隊列在GCD中本身就存在的愤钾,而手動創(chuàng)建的并發(fā)隊列是重新創(chuàng)建的
- 在使用柵欄函數的時候瘟滨,必須要使用手動創(chuàng)建的并發(fā)隊列,這樣才能有效果
- 在iOS6以前能颁,GCD中只要帶有了Creat和retain函數杂瘸,在最后都要進行一次release操作。但是現在GCD已經被納入ARC管理范圍伙菊,已經不需要我們再進行手動release操作了败玉。