標籤:
pthread 實現多線程操作
代碼實現:
void * run(void *param){ for (NSInteger i = 0; i < 1000; i++) { NSLog(@"---buttonclick---%zd---%@", i, [NSThread currentThread]); } return NULL;}@implementation ViewController- (IBAction)clickButton:(id)sender { // 定義一個線程 pthread_t thread; // 建立一個線程 (參1)pthread_t *restrict:建立線程的指標,(參2)const pthread_attr_t *restrict:線程屬性 (參3)void *(*)(void *):線程執行的函數的指標,(參4)void *restrict:null pthread_create(&thread, NULL, run, NULL); // 何時回收線程不需要你考慮 pthread_t thread2; pthread_create(&thread2, NULL, run, NULL);}
NSThread實現多線程
一個 NSThread 對象就代表一條線程
建立線程的多種方式
第一種方式:先建立再啟動線程
// 建立線程 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"jack"]; // 線程啟動了,事情做完了才會死, 一個NSThread對象就代表一條線程 [thread start];
第二種:直接建立並啟動線程
// 直接建立並啟動線程 [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"jack"];
第三種:
// 直接建立並啟動線程 [self performSelectorInBackground:@selector(run:) withObject:@"jack"]; // 使線程進入阻塞狀態 [NSThread sleepForTimeInterval:2.0]; #pragma mark - 執行run方法 - (void)run:(NSString *)param { // 當前線程是否是主線程 for (NSInteger i = 0; i < 100; i++) { NSLog(@"---%@---%zd---%d", [NSThread currentThread], i, [NSThread isMainThread]); } }
方法2和方法3的優點:快捷 方法1的優點:可以輕鬆拿到線程
線程間通訊
1個線程傳遞資料給另1個線程
在1個線程中執行完特定任務後,轉到另1個線程繼續執行任務
線程間通訊的常用方法:小程式圖片下載
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 擷取圖片的url NSURL *url = [NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"];// 另開1條線程 object用於資料的傳遞 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downLoadWithURL:) object:url]; // 由於下面下載圖片的耗時太長,應領開啟線程來完成 [thread start];}// 下載圖片- (void)downLoadWithURL:(NSURL *)url{ NSLog(@"%@", [NSThread currentThread]); // 下載圖片 NSData *data = [NSData dataWithContentsOfURL:url]; // 產生圖片 UIImage *image = [UIImage imageWithData:data]; // 返回主線程顯示圖片 [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];}
以上兩種方式使用線程已經過時了,開發中我們操作線程大多都使用 GCD 和 NSOperation 來實現多線程操作。
下面我就給大家系統的介紹一下 GCD 是如何?多線程的
GCD 實現多線程GCD 簡介
GCD 全稱是Grand Central Dispatch,可譯為“超級厲害的中樞調度器”,GCD 是蘋果公司為多核的並行運算提出的解決方案, GCD會自動利用更多的 CPU 核心(比如雙核、四核)來開啟線程執行任務,GCD 會自動管理線程的生命週期(建立線程、調度任務、銷毀線程),不需要我們程式員手動管理記憶體。
任務和隊列
任務:在同步函數和非同步函數中執行
隊列:用來存放任務(並發 串列)
GCD會自動將隊列中的任務取出,放到對應的線程,任務的取出遵循FIFO,即先入先出隊列,First Input First Output 的縮寫。先進入的任務先完成並結束,再執行後面的任務。
同步函數和非同步函數,並發隊列和串列隊列
建立並發/串列隊列代碼:
// 建立並發隊列 // 參1:const char *label 隊列名稱 // 參2:dispatch_queue_attr_t attr 隊列類型dispatch_queue_t queueConcurrent = dispatch_queue_create("520it.com", DISPATCH_QUEUE_CONCURRENT);// 建立串列隊列 serial 串列 concurrent並發dispatch_queue_t queueSerial = dispatch_queue_create("520it.com", DISPATCH_QUEUE_SERIAL);// 擷取全域隊列 全域隊列是並發隊列 // 參1:隊列的優先順序 // 參2:0(以後可能用到的參數)dispatch_queue_t queueGlobal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);// 全域並發隊列的優先順序#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 預設(中)#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 後台// 擷取主隊列 在主隊列中的任務都會在主線程中執行。dispatch_queue_t queueMain = dispatch_get_main_queue();
同步/非同步函數代碼錶示:
// GCD同步函數串列隊列(立即執行,當前線程) // 參1: dispatch_queue_t queue 隊列 // 參2: 任務dispatch_sync(queueSerial, ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"~~~%@", [NSThread currentThread]); }});// 同步函數並行隊列(立即執行,當前線程)dispatch_sync(queueConcurrent, ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"~~~%@", [NSThread currentThread]); }});// 非同步函數串列隊列 (另開線程,多個任務按順序執行)dispatch_async(queueSerial, ^{ dispatch_async(queueSerial, ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"~~~%@", [NSThread currentThread]); } }); dispatch_async(queueSerial, ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"~~~%@", [NSThread currentThread]); } }); dispatch_async(queueSerial, ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"~~~%@", [NSThread currentThread]); } });});// 非同步函數並行隊列 (另開線程,多個任務一起執行)dispatch_async(queueConcurrent, ^{ dispatch_async(queueSerial, ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"~~~%@", [NSThread currentThread]); } }); dispatch_async(queueSerial, ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"~~~%@", [NSThread currentThread]); } }); dispatch_async(queueSerial, ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"~~~%@", [NSThread currentThread]); } });});// 主隊列:(任何一個任務只要在主隊列中,都會加入到主線程的隊列中執行)
注意:使用sync函數(同步函數)往當前串列隊列中新增工作,會卡住當前的串列隊列
解釋:使用同步函數新增工作 A 到串列隊列,說明要在當前串列隊列立即執行任務 A ,任務 A 執行完後,才會執行任務 A 後面的代碼。但當前隊列是串列隊列,也就是說任務 A 必須等到當前串列隊列中正在執行的任務 B 完成之後才能執行,因此又必須先執行任務 A 中立即執行任務,又要必須等到任務 B 執行完以後才能執行下一個任務,所以就會卡死。你等我,我等你,誰也無法執行。
GCD實現線程通訊
小項目:下載圖片
代碼如下:
// 擷取圖片的urlNSURL *url = [NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"];// 開啟線程下載圖片dispatch_queue_t queue = dispatch_queue_create("111", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{ NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; // 下載完成後返回主線程顯示圖片 dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; });});
GCD其他常用函數dispatch_barrier 柵欄
// 1.barrier : 在barrier前面的先執行,然後再執行barrier,然後再執行barrier後面的 barrier的queue不能是全域的並發隊列dispatch_queue_t queue = dispatch_queue_create("11", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{ for (int i = 0; i < 100; i++) { NSLog(@"%@--1", [NSThread currentThread]); }});dispatch_async(queue, ^{ for (int i = 0; i < 100; i++) { NSLog(@"%@--2", [NSThread currentThread]); }});dispatch_barrier_async(queue, ^{ for (int i = 0; i < 100; i++) { NSLog(@"%@--3", [NSThread currentThread]); }});dispatch_async(queue, ^{ for (int i = 0; i < 100; i++) { NSLog(@"%@--4", [NSThread currentThread]); }});
dispatch_after 順延強制
// 順延強制// 方法1[self performSelector:@selector(run:) withObject:@"參數" afterDelay:2.0];// 方法2dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ for (NSInteger i = 0; i < 100; i++) { NSLog(@"%@", [NSThread currentThread]); }});// 方法3[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
dispatch_once 整個程式運行中執行一次
// 整個程式中只執行一次static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{ // 一次性代碼});
作用:實現某個類的單粒對象
單例模式:在整個應用程式中,共用一份資源(這份資源只需要建立初始化1次)
static id _person;+ (instancetype)sharePerson{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _person = [[super alloc] init]; }); return _person;}+ (instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _person = [super allocWithZone:zone]; }); return _person;}- (id)copy{ return _person;}
開發中一般自訂成宏,比較方便,一行代碼搞定。
dispatch_apply 快速迭代
樣本小程式:將一個檔案夾中的圖片剪下到另一個檔案夾
// 將圖片剪下到另一個檔案夾裡NSString *from = @"/Users/Ammar/Pictures/壁紙";NSString *to = @"/Users/Ammar/Pictures/to";NSFileManager *manager = [NSFileManager defaultManager];NSArray *subPaths = [manager subpathsAtPath:from];// 快速迭代dispatch_apply(subPaths.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) { NSLog(@"%@ - %zd", [NSThread currentThread], index); NSString *subPath = subPaths[index]; NSString *fromPath = [from stringByAppendingPathComponent:subPath]; NSString *toPath = [to stringByAppendingPathComponent:subPath]; // 剪下 [manager moveItemAtPath:fromPath toPath:toPath error:nil]; NSLog(@"%@---%zd", [NSThread currentThread], index);});
dispatch_group 隊列組
樣本小程式:需求下載圖片1 下載圖片2 將圖片1和圖片2合成新的圖片
// 建立隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 建立組dispatch_group_t group = dispatch_group_create();// 用組隊列下載圖片1dispatch_group_async(group, queue, ^{ NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]]; self.image1 = [UIImage imageWithData:data]; NSLog(@"1%@", [NSThread currentThread]);});// 用組隊列下載圖片2dispatch_group_async(group, queue, ^{ NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]]; self.image2 = [UIImage imageWithData:data]; NSLog(@"2%@", [NSThread currentThread]);});// 將圖片1和圖片2合成一張圖片dispatch_group_notify(group, queue, ^{ CGFloat imageW = self.imageView.bounds.size.width; CGFloat imageH = self.imageView.bounds.size.height; // 開啟位元影像上下文 UIGraphicsBeginImageContext(self.imageView.bounds.size); // 畫圖 [self.image1 drawInRect:CGRectMake(0, 0, imageW * 0.5, imageH)]; [self.image2 drawInRect:CGRectMake(imageW * 0.5, 0, imageW * 0.5, imageH)]; // 將圖片取出 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 關閉圖形上下文 UIGraphicsEndImageContext(); // 在主線程上顯示圖片 dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); NSLog(@"3%@", [NSThread currentThread]);});
GCD定時器
GCD定時器不受Mode影響因此比NSTimer要準確
static int count = 0;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 這句話的意思現在很好懂了});// GCD定時器dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);// 1.建立一個定時器源// 參1:類型定時器// 參2:控制代碼 // 參3:mask傳0 // 參4:隊列 (注意:dispatch_source_t本質是OC對象,表示源)self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);// 嚴謹起見,時間間隔需要用單位int64_t,做乘法以後單位就變了// 下面這句代碼錶示回呼函數時間間隔是多少int64_t interval = (int64_t)(2.0 * NSEC_PER_SEC); // 如何設定開始時間 CGD給我們了一個設定時間的方法 // 參1:dispatch_time_t when 傳一個時間, delta是增量dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)); // 從現在起3秒後開始// 2.設定定時器的各種屬性// 參1:timer // 參2:開始時間 // 參3:時間間隔 // 參4:傳0 不需要 DISPATCH_TIME_NOW 表示現在 GCD 時間用 NS 表示dispatch_source_set_timer(self.timer, start, interval, 0);// 3.設定回調(即每次間隔要做什麼事情)dispatch_source_set_event_handler(self.timer, ^{ NSLog(@"----------------%@", [NSThread currentThread]); // 如果希望做5次就停掉 count++; if (count == 5) { dispatch_cancel(self.timer); self.timer = nil; }});// 4.啟動定時器 (恢複)dispatch_resume(self.timer);
講完 GCD 就該講講 NSOperation,它是 GCD 的物件導向的封裝,使用起來也更方便,
NSOperation實現多線程
NSOperation是個抽象類別,並不具備封裝操作的能力,必須使用它的子類
NSInvocationOperationNSBlockOperation自訂子類繼承NSOperation,實現內部相應的方法
使用 NSOperation 實現多線程的步驟:
建立任務 NSOperation 對象建立 NSOperationQueue 隊列將任務 NSOperation 對象 add 到 NSOperationQueue 隊列中去
NSInvocationOperation
代碼如下:
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];[op start];
注意:預設情況下,調用了start方法後並不會開一條新的線程去執行,而是在當前線程同步執行操作,只有將 NSOperation 放到一個 NSOperationQueue 中,才會非同步執行操作
NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ // 在主線程 NSLog(@"下載1------%@", [NSThread currentThread]);}];// 添加額外的任務(在子線程執行),封裝數大於1才會非同步執行[op addExecutionBlock:^{ NSLog(@"下載2------%@", [NSThread currentThread]);}];
自訂Operation:需要實現- (void)main方法,需要做的事情放在mian方法中
NSOperationQueue
使用NSOperationQueue建立隊列:主隊列和全域隊列
// 建立一個其他隊列(包括串列隊列和並發隊列) 放到這個隊列中的NSOperation對象會自動放到子線程中執行NSOperationQueue *queue = [[NSOperationQueue alloc] init];// 建立一個主隊列,放到這個隊列中的NSOperation對象會自動放到子線程中執行NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];// 表示並發數量:即同時執行任務的最大數。queue.maxConcurrentOperationCount = 1;
隊列的取消、暫停、恢複:
// NSOpertion的 - cancel 方法也可以停止單個操作- (void)cancelAllOperations; // YES代表暫停隊列,NO代表恢複隊列- (void)setSuspended:(BOOL)b;
添加依賴
NSOperationQueue *queue = [[NSOperationQueue alloc] init];NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download1 -------------- %@", [NSThread currentThread]);}];NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download2 -------------- %@", [NSThread currentThread]);}];NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download3 -------------- %@", [NSThread currentThread]);}];// 添加依賴: block1 和 block2執行完後 再執行 block3 block3依賴於block1和block2// 給block3添加依賴 讓block3在block1和block2之後執行[block3 addDependency:block1];[block3 addDependency:block2];[queue addOperation:block1];[queue addOperation:block2];[queue addOperation:block3];
注意:不能循環相依性,但可以跨隊列依賴,不管NSOperation對象在哪個隊列。只要是兩個NSOperation對象就可以依賴
線程間通訊
樣本:下載圖片
// 下載圖片 operation實現線程間通訊[[[NSOperationQueue alloc] init] addOperation:[NSBlockOperation blockOperationWithBlock:^{ UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]]]; // 返回主線程 [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{ self.imageView.image = image; }]];}]];
樣本:下載圖片1和圖片2 併合成圖片
NSOperationQueue *queue = [[NSOperationQueue alloc] init];// 下載圖片1__block UIImage *image1 = nil;NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{ image1 = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]]];}];// 下載圖片2__block UIImage *image2 = nil;NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{ image2 = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]]];}];CGFloat imageW = self.imageView.bounds.size.width;CGFloat imageH = self.imageView.bounds.size.height;// 合成圖片NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{ UIGraphicsBeginImageContext(CGSizeMake(imageW, imageH)); [image1 drawInRect:CGRectMake(0, 0, imageW * 0.5, imageH)]; [image2 drawInRect:CGRectMake(0.5 * imageW, 0, 0.5 * imageW, imageH)]; UIImage *image3 = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // 切換回主線程顯示圖片 [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{ self.imageView.image = image3; }]];}];// 設定依賴[block3 addDependency:block1];[block3 addDependency:block2];// 新增工作到隊列中[queue addOperation:block1];[queue addOperation:block2];[queue addOperation:block3];
應用
應用:SDWebImage 架構的底層主要功能實現就是基於多線程,使用多線程,我們可以實現小圖片的多圖片下載。這裡的邏輯其實是比較複雜的
實現小圖片的多圖片下載思路:
代碼實現見本文代碼。
本文代碼見:Multithreading
https://github.com/lizhaoLoveIT/Multithreading
iOS開發多線程--技術方案