這周就學(xué)了多線程,現(xiàn)在還在云里霧里....
線程的概念
- 執(zhí)行調(diào)度單位
- 并發(fā)資源訪問控制
- 原子操作 Atomic(任務(wù)不可分隔,保證數(shù)據(jù)一致性)
- 用鎖同步,鎖的類型
- Semaphore 信號量:系統(tǒng)多個資源
- Mutex 互斥量:單個資源
- Critical Section 臨界區(qū):反復(fù)訪問的情況
- Read-Write Lock 讀寫鎖 只有1個寫,其它是讀,不受鎖的影響
- Condition Variable 條件變量 符合條件后激活
iOS中多任務(wù)API
iOS有三種多線程編程的技術(shù):
- NSThread
- Cocoa NSOperation
- GCD 全稱:Grand Central Dispatch
抽象度層次是從低到高的,抽象度越高的使用越簡單
三種方式的優(yōu)缺點(diǎn)介紹:
NSThread:
優(yōu)點(diǎn):NSThread 比其他兩個輕量級
缺點(diǎn):需要自己管理線程的生命周期,線程同步。線程同步對數(shù)據(jù)的加鎖會有一定的系統(tǒng)開銷
NSThread實(shí)現(xiàn)的技術(shù)有下面三種:
- Technology
- Description
- Cocoa threads
一般使用cocoa thread 技術(shù)。
Cocoa operation
優(yōu)點(diǎn):不需要關(guān)心線程管理,數(shù)據(jù)同步的事情,可以把精力放在自己需要執(zhí)行的操作上。
Cocoa operation 相關(guān)的類是 NSOperation ,NSOperationQueue。
NSOperation是個抽象類,使用它必須用它的子類,可以實(shí)現(xiàn)它或者使用它定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation。
創(chuàng)建NSOperation子類的對象,把對象添加到NSOperationQueue隊(duì)列里執(zhí)行。
GCD
Grand Central Dispatch (GCD)是Apple開發(fā)的一個多核編程的解決方法。在iOS4.0開始之后才能使用。GCD是一個替代諸如NSThread, NSOperationQueue, NSInvocationOperation等技術(shù)的很高效和強(qiáng)大的技術(shù)。
參考:http://blog.csdn.net/totogo2010/article/details/8010231
Main Thread (主線程)
主線程管理界面,也叫 UI 線程
長時間操作線程的應(yīng)該另外開一個線程進(jìn)行
NSThread
//新開線程執(zhí)行
//import
@import Foundation;
//方法一:直接創(chuàng)建線程并且開始運(yùn)行線程
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
//方法二:
NSThread* myThread = [[NSThread alloc] initWithTarget:self
selector:@selector(doSomething:)
object:nil];
[myThread start];
參數(shù)說明:
selector :線程執(zhí)行的方法,這個selector只能有一個參數(shù),而且不能有返回值。
target :selector消息發(fā)送的對象
-
argument:傳輸給target的唯一參數(shù),也可以是nil
//通知主線程更新界面 NSObject *Obj = [[NSObject alloc]init]; [Obj performSelectorOnMainThread:@selector(doSomething:) withObject:self waitUntilDone:NO]; //查詢是否是主線程 NSLog(@"%@",[NSThread isMainThread]?@"YES":@"NO");
Demo
-
為了訪問 HTTP 協(xié)議,在Info.plist中添加
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict> 在 View 中添加imageView
-
.m文件
#import "ViewController.h"
#define kURL @"http://upload.jianshu.io/daily_images/images/myb4DiWJtEcFVgHwgdhh.jpg"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end@implementation ViewController -(void)loadImage:(NSString *) url{ NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]]; UIImage *image = [UIImage imageWithData:data scale:2.0]; [self performSelectorOnMainThread:@selector(dispalyImage:) withObject:image waitUntilDone:NO]; } -(void)dispalyImage:(UIImage*) image{ self.imageView.image = image; } - (void)viewDidLoad{ [super viewDidLoad]; NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:kURL]; [thread start]; } - (void)didReceiveMemoryWarning{ [super didReceiveMemoryWarning]; } @end
創(chuàng)建一個線程的開銷
- 內(nèi)核中占據(jù)1KB 內(nèi)存保存線程資料
- stack space堆中占據(jù) 512KB 內(nèi)存( osx 主線程8MB\ios 主線程1MB)
- 需要耗費(fèi)90毫秒,費(fèi)電
因此線程越少越好
線程屬性設(shè)定
thread.name = @"image";
thread.stackSize = 1024 * 4 * 5 ;//20K
thread.threadPriority=1.0;//0.0-1.0 ,1.0為高優(yōu)先級
//?thread.threadDictionary;//沒弄清楚怎么用
線程的控制
[thread start];
[thread cancel];
NSLog(@"1--%@",[NSThread currentThread]);//當(dāng)前線程
[NSThread sleepForTimeInterval:2];
NSDate * sleepTime =[[NSDate alloc]initWithTimeIntervalSinceNow:60];
[NSThread sleepUntilDate:sleepTime];
[NSThread exit];//直接取消,慎用
Runloop (循環(huán)執(zhí)行)
線程和 RunLoop 之間是一一對應(yīng)的,其關(guān)系是保存在一個全局的 Dictionary 里
線程剛創(chuàng)建時并沒有 RunLoop
RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時,RunLoop 的銷毀是發(fā)生在線程結(jié)束時。
只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外)
一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer
Timer :CFRunLoopTimerRef/NSTimer
Observer:CFRunLoopObserverRef:觀察 runloop 狀態(tài)變化
每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode。
如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進(jìn)入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。
兩種 source :
-
source0:
- 只包含一個回調(diào)
- 不能主動觸發(fā)事件
- 需要手工標(biāo)記和喚醒
CFRunLoopSourceSignal(<#CFRunLoopSourceRef source#>)
CFRunLoopWakeUp(<#CFRunLoopRef rl#>)
-
source1:
- 一個 mach_port 和一個回調(diào)
- mach消息到達(dá)時,主動喚醒 Runloop 線程
實(shí)際上 RunLoop 內(nèi)部是一個 do-while 循環(huán)。
當(dāng)調(diào)用 CFRunLoopRun() 時,線程就會一直停留在這個循環(huán)里;直到超時或被手動停止,該函數(shù)才會返回。
Source/Timer/Observer 被統(tǒng)稱為 mode item,一個 item 可以被同時加入多個 mode。但一個 item 被重復(fù)加入同一個 mode 時是不會有效果的。如果一個 mode 中一個 item 都沒有,則 RunLoop 會直接退出,不進(jìn)入循環(huán)。
主線程的 RunLoop 里有兩個預(yù)置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode
這兩個 Mode 都已經(jīng)被標(biāo)記為"Common"屬性
DefaultMode 是 App 平時所處的狀態(tài),TrackingRunLoopMode 是追蹤 ScrollView 滑動時的狀態(tài)
當(dāng)創(chuàng)建一個 Timer 并加到 DefaultMode 時,Timer 會得到重復(fù)回調(diào),但此時滑動一個TableView時,RunLoop 會將 mode 切換為 TrackingRunLoopMode,這時 Timer 就不會被回調(diào),并且也不會影響到滑動操作。
有時需要一個 Timer,在兩個 Mode 中都能得到回調(diào),一種辦法就是將這個 Timer 分別加入這兩個 Mode。還有一種方式,就是將 Timer 加入到頂層的 RunLoop 的 "commonModeItems" 中。
"commonModeItems" 被 RunLoop 自動更新到所有具有"Common"屬性的 Mode 里去。
NSRunloop 生命周期
待補(bǔ)充
NSOperation
NSThread中,依賴\同步\調(diào)度需要寫的東西比較多
參考:http://blog.csdn.net/totogo2010/article/details/8013316
- NSOperation:封裝任務(wù)部分
- 用 selector(選擇器)編寫任務(wù): NSInvocationOperation
- 用 block 編寫任務(wù):NSBlockOperation
- 任務(wù)可以用依賴關(guān)系串起來
- NSOperationQueue: 封裝線程管理部分
demo
-
為了訪問 HTTP 協(xié)議,在Info.plist中添加
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict> 在 View 中添加imageView
.m文件
@interface NSOperationTemplateVC ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation NSOperationTemplateVC
-(void)loadImage:(NSString *) url{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
self.image = [UIImage imageWithData:data scale:2.0];
}
-(void)dispalyImage{
self.imageView.image = self.image;
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSInvocationOperation *opLoadImage = [[NSInvocationOperation alloc]initWithTarget:self
selector:@selector(loadImage:)
object:kURL];
NSInvocationOperation *opDispalyImage = [[NSInvocationOperation alloc]initWithTarget:self
selector:@selector(dispalyImage)
object:nil];
[opDispalyImage addDependency:opLoadImage];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:opLoadImage];
[[NSOperationQueue mainQueue] addOperation:opDispalyImage];
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
@end
NSOperation 的直接使用
是個抽象基類,一般不直接創(chuàng)造實(shí)例
NSOperation *op = [[NSOperation alloc]init];
[op start];
[op waitUntilFinished];
[op cancel];
[op main];
//默認(rèn)不做
//在 autoreleasepool 里運(yùn)行
//定義子類時覆蓋
//可以通過繼承重寫NSOperation 的main 使用
NSInvocationOperation
創(chuàng)建
//方法一
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self
selector:@selector(loadImage:)
object:kURL];
//方法二
NSInvocation * invocation =[[NSInvocation alloc]init];
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithInvocation:invocation];
NSBlockOperation
//創(chuàng)建
[NSBlockOperation blockOperationWithBlock:<#^(void)block#>);
//追加
NSBlockOperation *blockOperation = [[NSBlockOperation alloc]init];
[blockOperation addExecutionBlock:<#^(void)block#>];
NSOperation 依賴關(guān)系
[op addDependency:<#(nonnull NSOperation *)#>];
依賴完成后本Operation才會執(zhí)行
移除(必須手工移除):
[op removeDependency:<#(nonnull NSOperation *)#>];
NSOperation 狀態(tài)
op.name = @"image";
NSLog(@"%@",op.ready?@"YES":@"NO");
NSLog(@"%@",op.finished?@"YES":@"NO");
NSLog(@"%@",op.cancelled?@"YES":@"NO");
NSLog(@"%@",op.asynchronous?@"YES":@"NO");
[op setCompletionBlock:<#(void (^ _Nullable)(void)()completionBlock#>];
NSOperationQueue
//add 操作
[queue addOperation:operation];
[queue addOperations:<#(nonnull NSArray<NSOperation *> *)#> waitUntilFinished:NO];
[queue addOperationWithBlock:<#^(void)block#>];
[queue setMaxConcurrentOperationCount:5];//可跨隊(duì)列,但必須保證沒有阻塞和掛起
[queue cancelAllOperations];
[queue setSuspended:YES];//暫停
[queue waitUntilAllOperationsAreFinished];
mainQueue
NSOperationQueue *operationQueue = [NSOperationQueue mainQueue];
GCD
(作業(yè)相關(guān)內(nèi)容,必須好好學(xué))
用函數(shù)的方法調(diào)用進(jìn)程,是一個 C語言 API
Grand Central Dispatch 簡稱(GCD)是蘋果公司開發(fā)的技術(shù),以優(yōu)化的應(yīng)用程序支持多核心處理器和其他的對稱多處理系統(tǒng)的系統(tǒng)
參考:http://blog.csdn.net/totogo2010/article/details/8016129
dispatch queue分為下面三種:
Serial
又稱為private dispatch queues,同時只執(zhí)行一個任務(wù)。Serial queue通常用于同步訪問特定的資源或數(shù)據(jù)。當(dāng)你創(chuàng)建多個Serial queue時,雖然它們各自是同步執(zhí)行的,但Serial queue與Serial queue之間是并發(fā)執(zhí)行的。Concurrent
又稱為global dispatch queue,可以并發(fā)地執(zhí)行多個任務(wù),但是執(zhí)行完成的順序是隨機(jī)的。Main dispatch queue
它是全局可用的serial queue,它是在應(yīng)用程序主線程上執(zhí)行任務(wù)的。
我們看看dispatch queue如何使用
用法:
- 拿到一個 queue
- 把 block 放到 queue 中去
用dispatch_async處理后臺任務(wù)
dispatch_sync:等執(zhí)行結(jié)束再返回
dispatch_async:不等結(jié)束就返回
如果要獲知任務(wù)結(jié)束信息,只要自己寫就好了
現(xiàn)在在執(zhí)行的 queue
dispatch_get_current_queue()
全局隊(duì)列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{...}
//1
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
//2
QOS_CLASS_USER_INTERACTIVE//響應(yīng)用戶
QOS_CLASS_USER_INITIATED//用戶發(fā)起
QOS_CLASS_DEFAULT//
QOS_CLASS_UTILITY//程序內(nèi)部維護(hù)
QOS_CLASS_BACKGROUND//后臺
QOS_CLASS_UNSPECIFIED//
主隊(duì)列(串行)
dispatch_get_main_queue(), ^{...}
//dispatch_main(); 在 OSX 服務(wù)程序中使用,iOS 不使用
Demo
- (void)viewDidLoad
{
[super viewDidLoad];
//新建線程,然后通知主線程更新界面
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL * url = [NSURL URLWithString:@"http://upload.jianshu.io/daily_images/images/myb4DiWJtEcFVgHwgdhh.jpg"];
NSData * data = [[NSData alloc]initWithContentsOfURL:url];
UIImage *image = [[UIImage alloc]initWithData:data];
if (data != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
}
});
}
dispatch_queue_create(串行)
dispatch_queue_create("com.my.q",NULL);//name=com.my.q
操作
dispatch_suspend()/dispatch_resume()
dispatch_set_finalizer_f(func)//clean
傳遞參數(shù)
dispatch_set_context(q,voidPtr)
dispatch_get_context(q,voidPtr)
dispatch_once讓單例線程安全
dispatch_once(<#dispatch_once_t *predicate#>, <#^(void)block#>)
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); // 1
用 dispatch_after 延后工作
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // 2
});
dispatch_apply(5, globalQ, ^(size_t index) {
// 執(zhí)行5次
});
dispatch_group_async
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:3];
dispatch_group_wait(group,10);
NSLog(@"group3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"updateUi");
});
用 dispatch_barrie\dispatch_semaphore 處理讀者與寫者的沖突(臨界區(qū))
- dispatch_barrier_async
- dispatch_barrier_sync

用 Dispatch Sources 來監(jiān)視事件(進(jìn)度條)
用dispatch_semaphore(信號量)來處理多個資源
在GCD中有三個函數(shù)是semaphore的操作,分別是:
dispatch_semaphore_create 創(chuàng)建一個semaphore
dispatch_semaphore_signal 發(fā)送一個信號
dispatch_semaphore_wait 等待信號
第一個函數(shù)有一個整形的參數(shù),可以理解為信號的總量
dispatch_semaphore_signal是發(fā)送一個信號,自然會讓信號總量加1
dispatch_semaphore_wait等待信號,當(dāng)信號總量少于0的時候就會一直等待,否則就可以正常的執(zhí)行,并讓信號總量-1
int data = 3;
__block int mainData = 0;
__block dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("StudyBlocks", NULL);
dispatch_async(queue, ^(void) {
int sum = 0;
for(int i = 0; i < 5; i++)
{
sum += data;
NSLog(@" >> Sum: %d", sum);
}
dispatch_semaphore_signal(sem);
});
//dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//可以看看取消注釋前后的區(qū)別
for(int j=0;j<5;j++)
{
mainData++;
NSLog(@">> Main Data: %d",mainData);
}
用 Dispatch Sources 來監(jiān)視事件(進(jìn)度條)
//1
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(source, ^{
[progressIndicator incrementBy:dispatch_source_get_data(source)];
});
dispatch_resume(source);
dispatch_apply([array count], globalQueue, ^(size_t index) {
// do some work on data at index
dispatch_source_merge_data(source, 1);
});
//2
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t stdinSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
STDIN_FILENO,
0,
globalQueue);
dispatch_source_set_event_handler(stdinSource, ^{
char buf[1024];
int len = read(STDIN_FILENO, buf, sizeof(buf));
if(len > 0)
NSLog(@"Got data from stdin: %.*s", len, buf);
});
dispatch_resume(stdinSource);
//創(chuàng)建
dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
STDIN_FILENO,
0,
globalQueue);
DISPATCH_SOURCE_TYPE_TIMER 定時器
DISPATCH_SOURCE_TYPE_MACH_SEND/RECV
DISPATCH_SOURCE_TYPE_PROC 監(jiān)控進(jìn)程
DISPATCH_SOURCE_TYPE_VNODE 監(jiān)控文件系統(tǒng)對象
DISPATCH_SOURCE_TYPE_SIGNAL
DISPATCH_SOURCE_TYPE_READ/WRITE 從描述符中讀取數(shù)據(jù) 向描述符中寫入字符
//配置
dispatch_source_set_event_handler(stdinSource, ^{...}]
dispatch_source_set_event_get_data/handle/mask(stdinSource)
dispatch_source_set_cancel_handler(stdinSource, ^{...})
//啟動
dispatch_resume(stdinSource, ^{...})
//撤銷
dispatch_source_cancel(stdinSource)
計(jì)時器Demo
__block int timeout=300; //倒計(jì)時時間
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒執(zhí)行
dispatch_source_set_event_handler(_timer, ^{
if(timeout<=0){ //倒計(jì)時結(jié)束,關(guān)閉
dispatch_source_cancel(_timer);
dispatch_async(dispatch_get_main_queue(), ^{
});
}else{
int minutes = timeout / 60;
int seconds = timeout % 60;
NSString *strTime = [NSString stringWithFormat:@"%d分%.2d秒后重新獲取驗(yàn)證碼",minutes, seconds];
dispatch_async(dispatch_get_main_queue(), ^{
self.lbTimer.text = strTime;
});
timeout--;
}
});
dispatch_resume(_timer);