多線程(英語:multithreading),是指從軟件或者硬件上實現多個線程併發執行的技術。具備多線程能力的計算機因有硬件支持而可以在同一時間執行多於一個線程,進而提高總體處理性能。具備這種能力的系統包括對稱多處理機、多核心處理器以及芯片級多處理(Chip-level multithreading)或同時多線程(Simultaneous multithreading)處理器.在一個程序中,這些獨立運行的程序片斷叫作線程(Thread).利用它編程的概念就叫作多線程.具備多線程能力的計算機因有硬件支持而可以在同一時間執行多個一個線程,提高總體處理性能.html
一.什麼叫進程? git
進程是指在系統中正在運行的一個應用程序. 每一個進程之間是獨立的.每一個進程均運行在其賺傭且受保護的內存空間內.程序員
二.什麼是線程? github
1.用來執行進程的任務的叫線程.編程
2.每一個進程必須至少要有一個線程.網絡
3.線程是進程的基本執行單元.多線程
4.一個進程的全部任務都在線程中執行.併發
5.一個程序只有一個進程,一個進程可能會有一個或者多個線程.進程包含線程.app
6.主線程是系統開闢的,其餘任何線程都是手動開闢的.異步
三.線程的串行和並行分別是什麼?
1.串行.
每個線程同一時間內只能執行一個任務.就像Boss指揮一我的作完A事情,再作B事情,最後作C事情.
2.並行.
可是由於時間緣由,Boss嫌一我的來作事件A,B,C時間太長.想要同時指揮多我的一塊兒來作這三件事件.
四.多線程.
三我的一塊兒完成這三件事用專業術語將就叫多線程.
1.多線程的原理.
同一時間,CPU只能處理一條線程,只有一個線程在工做.可是CPU若是快速的在多個線程之間切換的話,就能讓多條線程同時執行.若是CPU切換的比較快的話,能夠當作多個線程併發執行.可是CPU的工做能力畢竟有限,同時執行不少個線程,每條線程被切換到的頻率就會下降,時間就會變長.因此要合理使用多線程.
2.多線程的優缺點
優勢 |
缺點 |
1.能適當的提升傳程序的執行效率 2. 能適當的提升資源的利用效率 |
1. 開啓多線程須要佔用必定的內存空間,主線程佔用1M,子線程佔用512KB. 2. 線程越多,CPU在調度線程上的開銷就越大. 3. 程序設計更復雜.好比:線程之間的通信,多線程的數據共享等
|
3. 多線程在開發中的應用
主線程:一個iOS程序運行後,默認會開啓1條線程,稱爲"主線程"或者"UI線程". 在iOS中除了主線程,其餘子線程都是獨立於Cocoa Touch的,因此只有主線程能夠更新UI界面.
主線程的做用: 顯示/刷新UI界面,處理UI事件(點擊事件,滾動事件,拖拽事件等)
注意點: 不要將刷新比較耗時的放到主線程中.耗時操做會卡住主線程,嚴重影響UI的流暢度,給用戶一種「卡」的壞體驗.
4. 多線程的使用.
若是我有一個個按鈕A和一個UITextView. A按鈕在當前線程(UI線程)下作一個for循環(循環10萬次輸出)點擊完A按鈕,當即拖拽UITextView,許久後纔有反應.
5.任務的概念.
有兩種執行方式:同步執行和異步執行.
(1) 同步執行(sync): 會阻塞當前線程並等待Block執行完畢,而後在當前線程纔會繼續往下執行. 會盡量的在當前線程派發任務,但若是在其餘隊列往主隊列中同步派發,任務會在主線程中執行.
(2) 異步執行(async): 當前線程繼續執行,不會阻塞當前線程. 不必定會新建一個線程,例如在主線程異步派發到主線程,派發依舊是異步線程的,任務也會在主線程中執行.
同步異步的區別,不在因而否會開闢一個線程,在於派發方法是否須要等待Block完成後才返回.
6.隊列的概念.
用於存聽任務.分爲串行隊列和並行隊列.
(1)串行隊列:放到串行隊列中的任務,GCD會FIFO(先進先出)的取出一個,執行一個,而後取出下一個,這樣一個一個的執行.
(2)並行隊列:放到並行隊列中任務,GCD也會FIFO的取出來,但不一樣的是,取出來一個任務就會放到別的線程中去,燃火取出來另外一個又放到另外一個線程中.因爲取的動做很快,能夠忽略不計,看起來全部的任務都是一塊兒執行的.不過須要注意,GCD會根據系統資源控制並行的數量,因此任務不少也不會把全部的任務都執行.
不管串行仍是並行隊列,任務啓動順序都是按照FIFO的,只是併發隊列容許同一時間有多個任務都在執行.
FIFO是 First Input First Output的縮寫,先入先出隊列,這是一種傳統的按序執行方法,先進入的指令先完成並引退,跟着才執行第二條指令。
7.組合方式:
(1)串行隊列同步執行:
(2)串行隊列異步執行:
(3)並行隊列同步執行:
(4)並行隊列異步執行:
四.目前有四種多線程的實現方法.
1.Pthreads.
基於C的,適合作跨平臺的SDK.
2.NSThread.
NSThread是輕量級的多線程開發,使用起來也並不複雜,可是使用NSThread須要本身管理線程生命週期.
目前我就用到
[NSThread currentThread]獲取當前線程.主要用於調試.
[NSThread sleepForTimeInterval:<#(NSTimeInterval)#>]; 延時多少秒
3.NSOperation & NSOperationQueue
待整理
4.GCD.
queue--線程 thread--隊列 diapatch--派遣 serial--連續的 concurrent--併發的
(1)這四總方式是隨着iOS的發展逐漸引進的,因此後者比前者使用更加簡單,而且GCD也是目前蘋果官方比較推薦的(充分利用了多核運算性能).
(2)GCD的全拼 --> Grand Central Dispatch --> 集中調度,是iOS開發的一個多核編程解決方案.會本身管理線程的生命週期(建立線程,調度任務,銷燬線程),不須要本身手動管理,只要要求它作就好了.使用靈活方便.
(3)在GCD中一個操做是多線程仍是單線程執行取決於當前隊列類型和執行方法,只有隊列類型爲並行隊列而且使用異步方法執行才能在多個線程中執行.
(4)串行隊列是能夠按照順序執行的,並行隊列的異步方法是沒法按照順序執行的.
(5)UI界面的更新最好採用同步方法,其餘採用異步方法.
(6)GCD中多線程操做方法不須要使用@autoreleasepool,GCD會管理內存.
(7)GCD是完成面向過程的.
(8)GCD的工做原理:讓程序平行排隊的特定任務,根據可用的處理資源,安排他們在任何可用的處理器核心上執行任務.
(9)一個任務能夠是一個函數(function)或者是一個Block,GCD的底層依然是用線程實現的,不過這樣可讓程序員不用關注實現的細節.
(10) dispatch queue 分爲三種
serial(連續的) | 又稱爲private dispatch queues,同時只執行一個任務.Serial queue 一般用於同步訪問特定的資源或者數據.當你建立多個Serial queue時候,雖然他們各自是同步的,可是Serial queue之間是併發的. |
Concurrent(併發的) | 又稱爲Global dispatch queue,能夠併發的執行多個任務,可是執行任務的順序是隨機的. |
Main dispatch queue (主隊列) |
它是全局可用的serial queue,它是在應用程序主線程上執行任務的. |
So GCD is the leading role of today.
五.代碼示例
1.獲取主線程
/** 獲取主線程 1. 全部的刷新UI界面的任務都要在主線程執行. 2. 將消耗時間的任務放在別的線程中出來,儘可能不要在主線程中處理. */ dispatch_queue_t main_queue = dispatch_get_main_queue(); NSLog(@"main_queue:\n %@",main_queue);
/*! * @function dispatch_get_main_queue * * @abstract * Returns the default queue that is bound to the main thread. * * @discussion * In order to invoke blocks submitted to the main queue, the application must * call dispatch_main(), NSApplicationMain(), or use a CFRunLoop on the main * thread. * * @result * Returns the main queue. This queue is created automatically on behalf of * the main thread before main() is called. */ DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW dispatch_queue_t dispatch_get_main_queue(void) { return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q); }
翻譯一波
dispatch_get_main_queue的功能
1. 摘要
返回綁定到主線程的默認隊列
2. 討論
爲了請求blocks提交到主線程,主隊列的申請必須呼叫到dispatch_main,NSApplicationMain(),或者用一個CFRunLoop.
3.結果
返回主線程.爲了主隊列能再main()以前被呼叫,這個隊列應該被自動的建立.
dispatch_get_main_queue 也是一種dispatch_queue_t
2.本身建立隊列
/** 本身建立的隊列 dispatch_queue_create 參數1: 第一個參數是標識符.用於DEBUG的時候標誌惟一的隊列,能夠爲空. 參數2: 第二個參數用來表示建立的隊列是串行的仍是並行的.傳入DISPATCH_QUEUE_SERIAL或者NULL表示建立的是串行隊列.傳入DISPATCH_QUEUE_CONCURRENT表示建立的並行隊列. (SERIAL--> serial連續的/CONCURRENT--> concurrent,併發的,一致的) */
// 建立串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create(nil, NULL); NSLog(@"serialQueue:\n %@",serialQueue); // 建立並行隊列: 這應該是惟一一個並行隊列,只要是並行任務通常都加入到這個隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT); NSLog(@"concurrentQueue:\n %@",concurrentQueue);
3.建立任務
dispatch_queue_t serialQueue = dispatch_queue_create(nil, NULL);
// 建立任務
/** 同步任務 (sync) 1. 會阻塞當前線程. */ dispatch_sync( serialQueue, ^{ for (int i = 0; i < 10000; i ++) { NSLog(@"同步任務: \n%@",[NSThread currentThread]); } }); /** 異步任務 (async) 1. 不會阻塞當前線程. */ dispatch_async(serialQueue, ^{ NSLog(@"異步任務: %@",[NSThread currentThread]); });
4.建立隊列組
dispatch_group_async能夠實現監聽一組任務是否完成,完成後獲得通知執行其餘的操做。這個方法頗有用,好比你執行三個下載任務,當三個任務都下載完成後你才通知界面說完成的了。下面是一段例子代碼
//1. 建立隊列組
dispatch_group_t group = dispatch_group_create(); /**2. 建立隊列 dispatch_get_global_queue 會獲取一個全局隊列,咱們姑且理解爲系統爲咱們開啓的一些全局線程。咱們用priority指定隊列的優先級,而flag做爲保留字段備用(通常爲0)。並行隊列的執行順序與其加入隊列的順序相同. #define DISPATCH_QUEUE_PRIORITY_HIGH 2 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) */ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //3. 屢次使用隊列中的方法執行任務,只有異步任務 //3.1 執行三次循環
dispatch_group_async(group, queue, ^{ for (int i = 0; i < 3; i ++) { NSLog(@"group-01 - %@",[NSThread currentThread]); } }); //3.2 主隊列執行8次循環
dispatch_group_async(group, dispatch_get_main_queue(), ^{ for (int i = 0; i < 8; i ++) { NSLog(@"group-02 - %@",[NSThread currentThread]); } }); //3.3 執行5次循環
dispatch_group_async(group, queue, ^{ for (int i = 0; i < 5; i ++) { NSLog(@"group-03 - %@",[NSThread currentThread]); } }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"完成 - %@",[NSThread currentThread]); });
5. 死鎖現象
(1)現象1
NSLog(@"以前==> %@",[NSThread currentThread]); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"sync==> %@",[NSThread currentThread]); }); NSLog(@"以後==> %@",[NSThread currentThread]); /** 解釋 1. 只會打印第一句:以前==> <NSThread: 0x7fe66b700610>{number = 1, name = main} ,而後主線程就卡死了,你能夠在界面上放一個按鈕,你就會發現點不了了。 2. 打印完第一句,dispatch_sync(由於是一個同步任務,會阻塞當前的線程)會阻塞當前的主線程,而後把Block中的任務放到main_queue中,main_queue中的任務會被取出來放到主線程中執行,但主線程種鴿時候已經被阻塞了,因此Block種鴿的任務就不能完成,它不完成,dispatch_sync就會一直阻塞主線程.致使主線程一直卡死.這就是死鎖現象. */
(2)現象2
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL); NSLog(@"輸出1.以前==> %@",[NSThread currentThread]); dispatch_async(queue, ^{ NSLog(@"輸出2.sync以前==> %@",[NSThread currentThread]); dispatch_sync(queue, ^{ NSLog(@"輸出3.sync==> %@",[NSThread currentThread]); }); NSLog(@"輸出4.sync以後==> %@",[NSThread currentThread]); }); NSLog(@"輸出5.以後==> %@",[NSThread currentThread]); /** 解釋 1. 當前線程爲默認的主線程 2. 輸出結果爲,輸出1,輸出5和輸出2 執行了輸出.輸出3和輸出4沒有被執行. 3. 按照執行順序分析. (1)咱們建立的隊列queue是一個串行隊列(DISPATCH_QUEUE_SERIAL).串行隊列的特色是,所持有的任務會取出一個執行一個.當前任務沒有執行完,下一個任務不會被執行. (2)打印出輸出1. (3)在queue隊列中開啓了一個異步任務(async).異步任務的特色是,當前的線程不會被阻塞.因此有了兩條線程,一條是主線程中執行輸出5.另外一條是在新開闢的queue線程中執行輸出2. (4)在開闢的queue線程中,又執行了一個同步的任務(sync),同步任務的特色是執行一個任務會阻塞當前的線程.當前的線程是queue,已經被阻塞了.又要求它去執行下一個任務.就形成了死鎖現象.因此 sync 所在的線程被卡死了,輸出3和輸出4天然就不會打印了. */
六.GCD經常使用方法
1.延遲
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ <#code to be executed after a specified delay#> });
2.從其餘線程回到主線程的方法
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主線程以後,須要執行的代碼
});
3. 一次性執行
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // code to be executed once
});
&onceToken的意思: &表示取地址符,這個是定義一個靜態變量,而後再dispatch_once函數第一次運行時,寫入數據,以後就不會再次寫入,能夠保證後面的block函數內部的代碼只被執行一次.
4.自定義dispatch_queue_t
dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL); dispatch_async(urls_queue, ^{ // your code
});
5.程序在後臺較長時間運行.
// AppDelegate.h文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask; // AppDelegate.m文件
- (void)applicationDidEnterBackground:(UIApplication *)application { [self beingBackgroundUpdateTask]; // 在這裏加上你須要長久運行的代碼
[self endBackgroundUpdateTask]; } - (void)beingBackgroundUpdateTask { self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endBackgroundUpdateTask]; }]; } - (void)endBackgroundUpdateTask { [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask]; self.backgroundUpdateTask = UIBackgroundTaskInvalid; }
6.並行隊列異步任務的具體使用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 執行一些耗時間的操做
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主線程,刷新UI,或者點擊觸發UI事件
}); }); // 實際使用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL * url = [NSURL URLWithString:@"https://gd2.alicdn.com/imgextra/i1/0/TB1p6QnOFXXXXbFXFXXXXXXXXXX_!!0-item_pic.jpg"]; NSData * data = [[NSData alloc]initWithContentsOfURL:url]; UIImage *image = [[UIImage alloc]initWithData:data]; if (data != nil) { dispatch_async(dispatch_get_main_queue(), ^{ self.imageView_one.image = image; }); } });
7. dispatch_barrier_sync
barrier 障礙物
dispatch_barrier_async是在前面的任務執行結束後它才執行,並且它後面的任務等它執行完成以後纔會執行
dispatch_queue_t queue = dispatch_queue_create("customIdenrifier", NULL); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:2]; NSLog(@"dispatch_async1"); }); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:4]; NSLog(@"dispatch_async2"); }); dispatch_barrier_sync(queue, ^{ NSLog(@"dispatch_barrier_async"); [NSThread sleepForTimeInterval:4]; }); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"dispatch_async3"); });
8. dispatch_apply
屢次執行裏面的代碼塊
參數1: 執行的次數
參數2: 在那個隊列中執行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(5, queue, ^(size_t index) { NSLog(@"第%zu次執行",index); });
七.代碼實現串行隊列
使用串行隊列時首先要建立一個串行隊列,而後調用異步調用方法,在此方法中傳入串行隊列和線程操做便可自動執行。下面使用線程隊列演示圖片的加載過程,你會發現多張圖片會按順序加載,由於當前隊列中只有一個線程。
#import "Level_four_itemFiveViewController.h"
#import "Masonry.h"
#import "Header.h"
#define ItemWH (ScreenW - MARGIN*4) / 3
#define MARGIN 20
#define MYQUEUE "myThreadQueue1"
@interface Level_four_itemFiveViewController () @property (nonatomic, strong) UIButton * clickedButton; @property (nonatomic, strong) NSMutableArray * imageViewsArrayM; @property (nonatomic, strong) NSMutableArray * imageNamesArrayM; @end
@implementation Level_four_itemFiveViewController #pragma mark - 生命週期
#pragma mark viewDidLoad
- (void)viewDidLoad { [super viewDidLoad]; [self basicSetting]; [self sendNetWorking]; [self initUI]; } #pragma mark - 點擊事件
- (void)clickedButtonClickd { // 多線程下載圖片
NSInteger count = self.imageNamesArrayM.count; //建立一個串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create(MYQUEUE, DISPATCH_QUEUE_SERIAL); //建立多個線程用於填充圖片
for (int i = 0; i < count; ++i) { //異步執行隊列任務
dispatch_async(serialQueue, ^{ [self loadImage:i]; }); } } #pragma mark 加載圖片
-(void)loadImage:(NSUInteger )index{ //請求數據
NSString * urlStr = [self.imageNamesArrayM objectAtIndex:index]; NSURL *url=[NSURL URLWithString:urlStr]; NSData *data=[NSData dataWithContentsOfURL:url]; //更新UI界面,此處調用了GCD主線程隊列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ [self updateImageWithData:data andIndex:index]; }); } #pragma mark 將圖片顯示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(NSInteger)index{ UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= self.imageViewsArrayM[index]; imageView.image=image; } #pragma mark - 網絡請求
- (void)sendNetWorking { //建立圖片連接
NSArray * array = @[ @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175036054-1229067335.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175046054-1410401557.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175055570-783207556.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175103820-2144487664.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175109523-327441423.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175115961-859836922.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175131195-2009565896.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175136617-306726060.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175036054-1229067335.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175046054-1410401557.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175055570-783207556.png", ]; self.imageNamesArrayM = [NSMutableArray arrayWithArray:array]; } #pragma mark - 實現方法
#pragma mark 基本設置
- (void)basicSetting { self.title = @"串行隊列"; self.view.backgroundColor = [UIColor whiteColor]; } #pragma mark - UI佈局
- (void)initUI { NSInteger total = self.imageNamesArrayM.count; for (int i = 0; i < total; i ++) { NSInteger column = i % 3; // 列數
NSInteger row = i / 3; // 行數
UIImageView * imageView = [[UIImageView alloc] init]; imageView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:imageView]; [imageView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view).with.offset((MARGIN + ItemWH) * column + MARGIN); make.size.mas_equalTo(CGSizeMake(ItemWH, ItemWH)); make.top.mas_equalTo(self.view).with.offset(74 + (MARGIN + ItemWH) * row); }]; [self.imageViewsArrayM addObject:imageView]; } [self.view addSubview:self.clickedButton]; [self.clickedButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view); make.right.mas_equalTo(self.view); make.bottom.mas_equalTo(self.view); make.height.mas_equalTo(40); }]; } #pragma mark - setter & getter
- (UIButton *)clickedButton { if (_clickedButton == nil) { self.clickedButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.clickedButton.backgroundColor = [UIColor orangeColor]; [self.clickedButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; self.clickedButton.titleLabel.font = [UIFont systemFontOfSize:15]; [self.clickedButton setTitle:@"加載圖片" forState:UIControlStateNormal]; [self.clickedButton addTarget:self action:@selector(clickedButtonClickd) forControlEvents:UIControlEventTouchUpInside]; } return _clickedButton; } - (NSMutableArray *)imageViewsArrayM { if (_imageViewsArrayM == nil) { self.imageViewsArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageViewsArrayM; } - (NSMutableArray *)imageNamesArrayM { if (_imageNamesArrayM == nil) { self.imageNamesArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageNamesArrayM; } @end
八.代碼實現並行隊列
併發隊列一樣是使用dispatch_queue_create()方法建立,只是最後一個參數指定爲DISPATCH_QUEUE_CONCURRENT進行建立,可是在實際開發中咱們一般不會從新建立一個併發隊列而是使用dispatch_get_global_queue()方法取得一個全局的併發隊列(固然若是有多個併發隊列可使用前者建立)。下面經過並行隊列演示一下多個圖片的加載. 其餘代碼和串行隊列的實現相同,只是點擊事件有區別
- (void)clickedButtonClickd { // 多線程下載圖片
NSInteger count = self.imageNamesArrayM.count; //建立一個並行隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //建立多個線程用於填充圖片
for (int i = 0; i < count; ++i) { //異步執行隊列任務
dispatch_async(globalQueue, ^{ [self loadImage:i]; }); //同步執行隊列任務
dispatch_sync(globalQueue, ^{ // 全部的圖片都在主線程中加載.主線程被阻塞.致使全部照片一次性被顯示.
}); /** 在GCD中一個操做是多線程執行仍是單線程執行取決於當前隊列類型和執行方法,只有隊列類型爲並行隊列而且使用異步方法執行時才能在多個線程中執行。 串行隊列能夠按順序執行,並行隊列的異步方法沒法肯定執行順序。 UI界面的更新最好採用同步方法,其餘操做採用異步方法。 */ } }
九.線程同步(NSLock,@synchronized代碼塊,GCD信號機制)
說到多線程就不得不提多線程中的鎖機制,多線程操做過程當中每每多個線程是併發執行的,同一個資源可能被多個線程同時訪問,形成資源搶奪,這個過程當中若是沒有鎖機制每每會形成重大問題。舉例來講,每一年春節都是一票難求,在12306買票的過程當中,成百上千的票瞬間就消失了。不妨假設某輛車有1千張票,同時有幾萬人在搶這列車的車票,順利的話前面的人都能買到票。可是若是如今只剩下一張票了,而同時還有幾千人在購買這張票,雖然在進入購票環節的時候會判斷當前票數,可是當前已經有100個線程進入購票的環節,每一個線程處理完票數都會減1,100個線程執行完當前票數爲-99,遇到這種狀況很明顯是不容許的。
這裏不妨還拿圖片加載來舉例,假設如今有8張圖片,可是有15個線程都準備加載這8張圖片,約定不能重複加載同一張圖片,這樣就造成了一個資源搶奪的狀況。在下面的程序中將建立9張圖片,每次讀取照片連接時首先判斷當前連接數是否大於1,用完一個則當即移除,最多隻有8個。
#import "Level_four_itemSevenViewController.h"
#import "Masonry.h"
#import "Header.h"
#define ItemWH (ScreenW - MARGIN*4) / 3
#define MARGIN 20
#define MYQUEUE "myThreadQueue1"
@interface Level_four_itemSevenViewController () { // 使用GCD解決資源搶佔問題
dispatch_semaphore_t _semaphore;//定義一個信號量
} @property (nonatomic, strong) UIButton * clickedButton; @property (nonatomic, strong) NSMutableArray * imageViewsArrayM; @property (nonatomic, strong) NSMutableArray * imageNamesArrayM; @property (nonatomic, strong) NSLock * lock; @end
@implementation Level_four_itemSevenViewController /** 說明 拿圖片加載來舉例,假設如今有8張圖片,可是有15個線程都準備加載這8張圖片,約定不能重複加載同一張圖片,這樣就造成了一個資源搶奪的狀況。在下面的程序中將建立8張圖片,每次讀取照片連接時首先判斷當前連接數是否大於1,用完一個則當即移除,最多隻有8個. */
#pragma mark - 生命週期
#pragma mark viewDidLoad
- (void)viewDidLoad { [super viewDidLoad]; [self basicSetting]; [self sendNetWorking]; [self initUI]; } #pragma mark - 系統代理
#pragma mark - 點擊事件
- (void)clickedButtonClickd { // 多線程如今圖片
NSInteger count = 15; //建立一個串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create(MYQUEUE, DISPATCH_QUEUE_SERIAL); //建立多個線程用於填充圖片
for (int i = 0; i < count; ++i) { //異步執行隊列任務
dispatch_async(serialQueue, ^{ [self loadImage:i]; }); } } #pragma mark 加載圖片
-(void)loadImage:(NSUInteger )index{ /** 三種方式 一個線程A已經開始獲取圖片連接,獲取完以後尚未來得及從self.imageNamesArrayM中刪除,另外一個線程B已經進入相應代碼中,因爲每次讀取的都是self.imageNamesArrayM的最後一個元素,所以後面的線程其實和前面線程取得的是同一個圖片連接這樣就形成圖中看到的狀況。要解決這個問題,只要保證線程A進入相應代碼以後B沒法進入,只有等待A完成相關操做以後B才能進入便可。這樣纔不會出錯. */
// 1
[self ThreadSynchronization_wayOneWithIndex:index]; // 2 //[self ThreadSynchronization_wayTwoWithIndex:index]; // 3 //[self ThreadSynchronization_wayThreeWithIndex:index];
} - (void)ThreadSynchronization_wayOneWithIndex:(NSInteger)index { NSString * urlStr; NSData *data; /** 線程同步方法1: NSLock 1.線程使用前加鎖,線程使用後解鎖 2.iOS中對於資源搶佔的問題可使用同步鎖NSLock來解決,使用時把須要加鎖的代碼(之後暫時稱這段代碼爲」加鎖代碼「)放到NSLock的lock和unlock之間,一個線程A進入加鎖代碼以後因爲已經加鎖,另外一個線程B就沒法訪問,只有等待前一個線程A執行完加鎖代碼後解鎖,B線程才能訪問加鎖代碼。須要注意的是lock和unlock之間的」加鎖代碼「應該是搶佔資源的讀取和修改代碼,不要將過多的其餘操做代碼放到裏面,不然一個線程執行的時候另外一個線程就一直在等待,就沒法發揮多線程的做用了 */ [self.lock lock]; if (self.imageNamesArrayM.count > 0) { urlStr = [self.imageNamesArrayM lastObject]; [self.imageNamesArrayM removeObject:urlStr]; } [self.lock unlock]; if (urlStr) { NSURL * url = [NSURL URLWithString:urlStr]; data = [NSData dataWithContentsOfURL:url]; } //更新UI界面,此處調用了GCD主線程隊列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ [self updateImageWithData:data andIndex:index]; }); } - (void)ThreadSynchronization_wayTwoWithIndex:(NSInteger)index { /** 線程同步方法2: @synchronized代碼塊 使用@synchronized解決線程同步問題相比較NSLock要簡單一些,平常開發中也更推薦使用此方法。首先選擇一個對象做爲同步對象(通常使用self),而後將」加鎖代碼」(爭奪資源的讀取、修改代碼)放到代碼塊中。@synchronized中的代碼執行時先檢查同步對象是否被另外一個線程佔用,若是佔用該線程就會處於等待狀態,直到同步對象被釋放 */ NSString * urlStr; NSData *data; @synchronized (self) { if (self.imageNamesArrayM.count > 0) { urlStr = [self.imageNamesArrayM lastObject]; [self.imageNamesArrayM removeObject:urlStr]; } } if (urlStr) { NSURL * url = [NSURL URLWithString:urlStr]; data = [NSData dataWithContentsOfURL:url]; } //更新UI界面,此處調用了GCD主線程隊列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ [self updateImageWithData:data andIndex:index]; }); } - (void)ThreadSynchronization_wayThreeWithIndex:(NSInteger)index { /** 線程同步方法三: GCD信號機制 初始化信號量 參數是信號量初始值 在GCD中提供了一種信號機制,也能夠解決資源搶佔問題(和同步鎖的機制並不同)。GCD中信號量是dispatch_semaphore_t類型,支持信號通知和信號等待。每當發送一個信號通知,則信號量+1;每當發送一個等待信號時信號量-1,;若是信號量爲0則信號會處於等待狀態,直到信號量大於0開始執行。根據這個原理咱們能夠初始化一個信號量變量,默認信號量設置爲1,每當有線程進入「加鎖代碼」以後就調用信號等待命令(此時信號量爲0)開始等待,此時其餘線程沒法進入,執行完後發送信號通知(此時信號量爲1),其餘線程開始進入執行,如此一來就達到了線程同步目的。 */ NSString * urlStr; NSData *data; _semaphore = dispatch_semaphore_create(1); dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); if (self.imageNamesArrayM.count > 0) { urlStr = [self.imageNamesArrayM lastObject]; [self.imageNamesArrayM removeObject:urlStr]; } // 信號通知
dispatch_semaphore_signal(_semaphore); if (urlStr) { NSURL * url = [NSURL URLWithString:urlStr]; data = [NSData dataWithContentsOfURL:url]; } //更新UI界面,此處調用了GCD主線程隊列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ [self updateImageWithData:data andIndex:index]; }); } #pragma mark 將圖片顯示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(NSInteger)index{ UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= self.imageViewsArrayM[index]; imageView.image=image; } #pragma mark - 網絡請求
- (void)sendNetWorking { //建立圖片連接
NSArray * array = @[ @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175036054-1229067335.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175046054-1410401557.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175055570-783207556.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175103820-2144487664.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175109523-327441423.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175115961-859836922.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175131195-2009565896.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175136617-306726060.png", ]; self.imageNamesArrayM = [NSMutableArray arrayWithArray:array]; } #pragma mark - 實現方法
#pragma mark 基本設置
- (void)basicSetting { self.title = @"線程同步(NSLock,@synchronized代碼塊,GCD信號機制"; self.view.backgroundColor = [UIColor whiteColor]; } #pragma mark - UI佈局
- (void)initUI { NSInteger total = 15; for (int i = 0; i < total; i ++) { NSInteger column = i % 3; // 列數
NSInteger row = i / 3; // 行數
UIImageView * imageView = [[UIImageView alloc] init]; imageView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:imageView]; [imageView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view).with.offset((MARGIN + ItemWH) * column + MARGIN); make.size.mas_equalTo(CGSizeMake(ItemWH, ItemWH)); make.top.mas_equalTo(self.view).with.offset(74 + (MARGIN + ItemWH) * row); }]; [self.imageViewsArrayM addObject:imageView]; } [self.view addSubview:self.clickedButton]; [self.clickedButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view); make.right.mas_equalTo(self.view); make.bottom.mas_equalTo(self.view); make.height.mas_equalTo(40); }]; } #pragma mark - setter & getter
- (UIButton *)clickedButton { if (_clickedButton == nil) { self.clickedButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.clickedButton.backgroundColor = [UIColor orangeColor]; [self.clickedButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; self.clickedButton.titleLabel.font = [UIFont systemFontOfSize:15]; [self.clickedButton setTitle:@"加載圖片" forState:UIControlStateNormal]; [self.clickedButton addTarget:self action:@selector(clickedButtonClickd) forControlEvents:UIControlEventTouchUpInside]; } return _clickedButton; } - (NSMutableArray *)imageViewsArrayM { if (_imageViewsArrayM == nil) { self.imageViewsArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageViewsArrayM; } - (NSMutableArray *)imageNamesArrayM { if (_imageNamesArrayM == nil) { self.imageNamesArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageNamesArrayM; } - (NSLock *)lock { if (_lock == nil) { self.lock = [[NSLock alloc] init]; } return _lock; } @end
十.GCD控制線程通信
因爲線程的調度是透明的,程序有時候很難對它進行有效的控制,爲了解決這個問題iOS提供了NSCondition來控制線程通訊(同前面GCD的信號機制相似)。NSCondition實現了NSLocking協議,因此它自己也有lock和unlock方法,所以也能夠將它做爲NSLock解決線程同步問題,此時使用方法跟NSLock沒有區別,只要在線程開始時加鎖,取得資源後釋放鎖便可,這部份內容比較簡單在此再也不演示。固然,單純解決線程同步問題不是NSCondition設計的主要目的,NSCondition更重要的是解決線程之間的調度關係(固然,這個過程當中也必須先加鎖、解鎖)。NSCondition能夠調用wati方法控制某個線程處於等待狀態,直到其餘線程調用signal(此方法喚醒一個線程,若是有多個線程在等待則任意喚醒一個)或者broadcast(此方法會喚醒全部等待線程)方法喚醒該線程才能繼續。
#import "Level_four_itemEightViewController.h"
#import "Masonry.h"
#import "Header.h"
#define ItemWH (ScreenW - MARGIN*4) / 3
#define MARGIN 20
#define MYQUEUE "myThreadQueue1"
#define ImageCount 8
@interface Level_four_itemEightViewController () { NSMutableArray *_imagesArrayM; } @property (nonatomic, strong) UIButton * createButton; @property (nonatomic, strong) UIButton * loadButton; @property (nonatomic, strong) NSMutableArray * imageViewsArrayM; @property (nonatomic, strong) NSMutableArray * imageNamesArrayM; #pragma mark 當前加載的圖片索引(圖片連接地址連續) @property (atomic,assign) int currentIndex; @property (nonatomic, strong) NSCondition * condition; @end
@implementation Level_four_itemEightViewController /** 線程的調度是透明的,程序有時候很難對它進行有效的控制.iOS提供了NSCondition來控制線程通訊.NSCondition也遵照NSLocking協議, 因此它自己就有lock和unlock方法.NSCondation能夠解決線程同步的問題,可是更重要的是能解決線程之間的調度問題,固然這個過程也須要先加鎖和解鎖. wait方法控制某個線程處於等待狀態. signal方法喚起一個線程,若是有多個線程,則任意喚起一個. broadcast方法喚起全部等待的線程. */
#pragma mark - 生命週期
#pragma mark viewDidLoad
- (void)viewDidLoad { [super viewDidLoad]; [self basicSetting]; [self sendNetWorking]; [self initUI]; } #pragma mark - 系統代理
#pragma mark - 點擊事件
#pragma mark 產生圖片
- (void)createButtonClickd { // 異步建立一張圖片
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 建立圖片連接
dispatch_async(globalQueue, ^{ [self createImageName]; }); } - (void)createImageName { [self.condition lock]; // 若是當前有圖片則不建立,線程處於等待狀態
if (self.imageNamesArrayM.count > 0) { NSLog(@"createImageName wait, current:%i",_currentIndex); [self.condition wait]; } else { NSLog(@"createImageName work, current:%i",_currentIndex); //生產者,每次生產1張圖片
NSString * str = [_imagesArrayM objectAtIndex:_currentIndex]; [self.imageNamesArrayM addObject:str]; _currentIndex ++; //建立完圖片則發出信號喚醒其餘等待線程
[self.condition signal]; } [self.condition unlock]; } #pragma mark 加載圖片
- (void)loadButtonClickd { if (_currentIndex > 8) { UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"圖片張數超過8張,返回重試!" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { [self.navigationController popViewControllerAnimated:YES]; }]; [alert addAction:cancel]; [self presentViewController:alert animated:YES completion:nil]; } else { // 多線程下載圖片 //建立一個串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create(MYQUEUE, DISPATCH_QUEUE_SERIAL); //建立多個線程用於填充圖片 //異步執行隊列任務
dispatch_async(serialQueue, ^{ [self loadImage:_currentIndex - 1]; }); } } #pragma mark 加載圖片
-(void)loadImage:(NSUInteger)index{ // 加鎖
[self.condition lock]; // 若是當前有圖片資源則加載,不然等待
if (self.imageNamesArrayM.count > 0) { NSLog(@"loadImage work,index is %lu",(unsigned long)index); [self loadAnUpdateImageWithIndex:index]; [_condition broadcast]; }else{ NSLog(@"loadImage wait,index is %lu",(unsigned long)index); //線程等待
[_condition wait]; NSLog(@"loadImage resore,index is %lu",(unsigned long)index); //一旦建立完圖片當即加載
[self loadAnUpdateImageWithIndex:index]; } [self.condition unlock]; } -(void)loadAnUpdateImageWithIndex:(NSUInteger)index{ //請求數據
NSData *data= [self requestData:index]; //更新UI界面,此處調用了GCD主線程隊列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= self.imageViewsArrayM[index]; imageView.image=image; }); } -(NSData *)requestData:(NSUInteger)index{ NSData *data; NSString *name; name = [self.imageNamesArrayM lastObject]; [self.imageNamesArrayM removeObject:name]; if(name){ NSURL *url=[NSURL URLWithString:name]; data=[NSData dataWithContentsOfURL:url]; } return data; } #pragma mark - 網絡請求
- (void)sendNetWorking { //建立圖片連接
NSArray * array = @[ @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175036054-1229067335.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175046054-1410401557.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175055570-783207556.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175103820-2144487664.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175109523-327441423.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175115961-859836922.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175131195-2009565896.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175136617-306726060.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175036054-1229067335.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175046054-1410401557.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175055570-783207556.png", ]; _imagesArrayM = [NSMutableArray arrayWithArray:array]; } #pragma mark - 實現方法
#pragma mark 基本設置
- (void)basicSetting { self.title = @"串行隊列"; self.view.backgroundColor = [UIColor whiteColor]; _currentIndex = 0; } #pragma mark - UI佈局
- (void)initUI { NSInteger total = 8; for (int i = 0; i < total; i ++) { NSInteger column = i % 3; // 列數
NSInteger row = i / 3; // 行數
UIImageView * imageView = [[UIImageView alloc] init]; imageView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:imageView]; [imageView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view).with.offset((MARGIN + ItemWH) * column + MARGIN); make.size.mas_equalTo(CGSizeMake(ItemWH, ItemWH)); make.top.mas_equalTo(self.view).with.offset(74 + (MARGIN + ItemWH) * row); }]; [self.imageViewsArrayM addObject:imageView]; } [self.view addSubview:self.createButton]; [self.createButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view); make.width.mas_equalTo(ScreenW / 2); make.bottom.mas_equalTo(self.view); make.height.mas_equalTo(40); }]; [self.view addSubview:self.loadButton]; [self.loadButton mas_makeConstraints:^(MASConstraintMaker *make) { make.right.mas_equalTo(self.view); make.width.mas_equalTo(ScreenW / 2); make.bottom.mas_equalTo(self.view); make.height.mas_equalTo(40); }]; } #pragma mark - setter & getter
- (UIButton *)createButton { if (_createButton == nil) { self.createButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.createButton.backgroundColor = [UIColor orangeColor]; [self.createButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; self.createButton.titleLabel.font = [UIFont systemFontOfSize:15]; [self.createButton setTitle:@"產生圖片" forState:UIControlStateNormal]; [self.createButton addTarget:self action:@selector(createButtonClickd) forControlEvents:UIControlEventTouchUpInside]; } return _createButton; } - (UIButton *)loadButton { if (_loadButton == nil) { self.loadButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.loadButton.backgroundColor = [UIColor greenColor]; [self.loadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; self.loadButton.titleLabel.font = [UIFont systemFontOfSize:15]; [self.loadButton setTitle:@"加載圖片" forState:UIControlStateNormal]; [self.loadButton addTarget:self action:@selector(loadButtonClickd) forControlEvents:UIControlEventTouchUpInside]; } return _loadButton; } - (NSMutableArray *)imageViewsArrayM { if (_imageViewsArrayM == nil) { self.imageViewsArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageViewsArrayM; } - (NSMutableArray *)imageNamesArrayM { if (_imageNamesArrayM == nil) { self.imageNamesArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageNamesArrayM; } - (NSCondition *)condition { if (_condition == nil) { self.condition = [[NSCondition alloc] init]; } return _condition; } @end
本篇博客所用的Demo地址: https://github.com/mancongiOS/multithreading
技術支持:
簡書.做者:伯恩的遺產. 地址:http://www.jianshu.com/p/0b0d9b1f1f19
博客園.做者:文頂頂. 地址:http://www.cnblogs.com/wendingding/p/3805088.html
博客園.做者:KenshinCui 地址:http://www.cnblogs.com/kenshincui/p/3983982.html