[转]多线程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[转]多线程相关的知识,希望对你有一定的参考价值。
原文地址:http://d3caifu.com/ebook/MultiThread.html
随着计算机/移动设备不断发展,提供了越来越强大的计算能力和资源,从硬件层面来看首先是CPU的芯片集成度越来越高,提供更快的处理能力;其次是多核化多CPU发展;最后剥离一些图片动画渲染等到独立的协处理器处理,硬件的提升促进了操作系统软件OS的不断进化,更好的利用硬件能力和资源,提高系统的性能,多线程多任务处理就是最为关键的技术。
线程分为主线程和后台线程2种:一个应用只有唯一的一个主线程,其它线程都是后台线程。主线程主要负责UI界面更新和响应用户事件。
OSX系统提供了多种线程编程相关技术,我们主要讨论NSThread,NSOperationQueue,GCD,Run Loop等相关概念和编程要点。
GCD
传统的多线程编程技术,为了并行处理增强性能往往采用手段是创建更多线程去并行处理,但这带来一个问题:究竟创建多少个线程是合适的最优的?线程的创建准备需要耗费一定的系统内存资源,到一定数量后,性能的提升并不一定是跟线程的多少成线性增长的,这样对多线程编程提出了很高的要求,如何控制保持合理数量的线程保证性能最优?
GCD是内核级的多线程并发技术,不需要关注线程创建,只需要把执行的程序单元体检到GCD的分发队列即可,GCD线程技术特点:
1)创建线程是系统自动完成,简化了传统的编程模式
2)内核级,创建线程不会占用应用程序的内存空间
2)基于C语言函数级的接口编程,性能优
3)GCD的分发队列按先进先出的顺序执行线程任务
Dispatch Queue
GCD的多线程任务都是通过Dispatch Queue来管理,有多种类型的分发队列:
1.Serial串行队列
每个串行队列,顺序执行,先进先出,每次只允许执行一个任务,任务执行完成后才能执行队列中下一个任务。
可以创建多个串行队列,多个串行队列之间仍然是并行调度。
串行队列的单任务调度特点,使得它特别适合管理保护存在冲突访问的资源,比如文件读写,全局数据的访问等。
除了主线程队列,任务其它串行队列都需要代码手工去创建。
2.Concurrent并行队列
并行队列,每次可以按照顺序执行一个或多个任务单元,并发的任务数由系统根据计算资源自动的动态分配控制。
系统默认提供四种不同高低优先级的并行队列,可以按需获取。除此之外也可以通过代码创建自己的并行队列。
3.Main dispatch queue主线程队列
每个应用中自动生成,唯一的全局的串行队列,无须手工创建,用于在应用的主线程上执行任务。UI界面元素的数据更新必须在主线程队列执行。
GCD多线程编程
创建队列
1.创建串行队列
//创建私有队列
dispatch_queue_t queue = dispatch_queue_create("com.yourdomain.TestQueue", NULL);
//获取主线程队列
dispatch_queue_t queue = dispatch_get_main_queue() ;
2.获取系统默认的并行队列
系统默认有4种不同优先级队列,高优先级的队列优先得到调度机会,从高到低依次如下:
#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 queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3.代码创建并行队列
dispatchqueuet queue = dispatchqueuecreate(com.yourdomain.TestQueue
, DISPATCHQUEUECONCURRENT);
添加任务到队列
有2种方式添加任务对队列执行,一种是使用dispatchasync异步添加,一种是使用dispatchsync同步添加。
异步添加的任务不会阻塞当前的线程,程序可以继续执行添加任务完之后的代码功能,添加到队列的任务由GCD后续动态分配调用。
dispatch_async(queue, ^{
NSLog(@"Do some tasks!");
});
NSLog(@"The tasks may or may not have run");
上述代码的执行结果:
The tasks may or may not have run
Do some tasks!
而同步添加的任务会阻塞当前的程序流程,直到添加的任务执行完成后,才能执行后续的代码功能。
dispatch_sync(queue, ^{
NSLog(@"Do some work 1 here");
});
dispatch_sync(queue, ^{
NSLog(@"Do some work 2 here");
});
NSLog(@"All works have completed");
上述代码的执行结果:
Do some work 1 here
Do some work 2 here
All works have completed
任务执行完的回调
某些特定的队列任务,执行完成后需要将处理结果或状态通知到主线程或其它队列做进一步的处理。
可以对任务定义完成的回调处理任务。
下面的代码摘自SDWebImage库,定义了一个回调块,在_ioQueue中执行文件是否存在的判断,然后将是否存在的结果通过回调块completionBlock通知到主线程队列。
-(void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
dispatch_async(_ioQueue, ^{
BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}
下面的代码演示异步任务读取数据库,通过回调块在主线程执行TableView的UI更新。
dispatch_async(self.dbQueue , ^
{
self.datas = [self queryDataFromDB];
dispatch_async(dispatch_get_main_queue(),^
{
[self.tableView reloadData];
}
);
});
线程组Groups
将多个任务加入到一个组,当组内所有任务都执行完毕后收到通知。
1.组+队列控制任务执行
下面的代码创建了任务组和串行的队列,同时加入2个任务到组,组内左右任务执行完成后收到通知。
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, ^{
NSLog(@"Do some work 1 here");
});
dispatch_group_async(group, queue, ^{
NSLog(@"Do some work 2 here");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"group complete");
});
上面代码执行完的结果:
Do some work 1 here
Do some work 2 here
group complete
组内的任务都是异步执行的,如果需要组内的任务执行完成才能进行后续的流程,可以增加组的等待函数阻塞当前的流程
dispatch_group_async(group, queue, ^{
NSLog(@"Do some work 1 here");
});
dispatch_group_async(group, queue, ^{
NSLog(@"Do some work 2 here");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"group complete");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"Do some work 3 here!");
这时代码执行结果如下,可以看出只有work1和work2都执行完成,work3才会得到执行。
Do some work 1 here
Do some work 2 here
Do some work 3 here!
group complete
2.动态控制组任务
使用dispatch_group_enter和dispatch_group_leave做为任务开始和结束的标记, 可用脱离dispatch_group_async API的使用而方便的在代码其它流程加入组的控制。
比如我们需要在多个网络请求完成后做一个统一的UI更新操作,可用使用动态控制组的方式去方便的实现。
在每个网络请求发起前调用dispatch_group_enter(group),在接收数据完成后调用dispatch_group_leave(group)。
dispatch_group_enter(group);
[webAPI sendWithURL:@"http://xxx.path1" block:^( ){
dispatch_group_leave(group)
}
];
dispatch_group_enter(group);
[webAPI sendWithURL:@"http://xxx.path2" block:^( ){
dispatch_group_leave(group)
}
];
dispatch_group_notify(group, queue, ^{
NSLog(@"group complete");
});
优化循环性能
当循环处理的任务之间没有关联时,即执行顺序可以任意时,可以使用GCD提供dispatch_apply方法将循环串行的任务并行化处理。
int count = 10;
for (i = 0; i < count; i++) {
printf("%u\n",i);
}
int count = 10 ;
dispatch_apply(count, queue, ^(size_t i) {
printf("%u\n",i);
});
使用信号量控制受限的资源
当允许有限的资源可以并发使用时,可以通过信号量控制访问,当信号量有效时允许访问(即信号量大于1),否则等待其它线程释放资源,信号量变成有效时在执行任务。
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(10);
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
int fd = open("/Users/test/file.data", O_RDONLY);
close(fd);
dispatch_semaphore_signal(fd_sema);
延时执行任务
使用dispatch_after延时任务执行。
下面的代码演示了延时0.5秒执行任务
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), queue, ^{
NSLog(@"after 0.5s timeout , Do some other works!");
});
使用dispatch_barrier_async控制并发任务
在并发任务队列中,可以使用dispatch_barrier_async来对任务做分隔保护,dispatch_barrier_async之前加入到队列的任务执行完成后,才能执行后续加入的任务。barrier英文意思为障碍物,因此我们可以理解为执行完前面的任务才能越过障碍物执行后续的任务。
dispatch_queue_t queue = dispatch_queue_create("com.yourdomain.TestQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"Do some work 1 here");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"Do some work 2 here");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"Do some work 3 here");
});
dispatch_barrier_async(queue, ^{
NSLog(@"Do some work 4 here");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"Do some work 5 here");
});
上面代码的执行结果为:
Do some work 2 here
Do some work 1 here
Do some work 3 here
Do some work 4 here
Do some work 5 here
任务暂停和唤醒
通过dispatch_suspend函数暂停队列执行,dispatch_resume恢复队列执行。
-(void)suspendQueue {
if (queue) {
dispatch_suspend(queue);
}
}
-(void)resumeQueue {
if (queue) {
dispatch_resume(queue);
}
}
GCD 实际使用的例子
1.使用dispatch_once实现单例
```
+ (instancetype)sharedInstance {
static HTTPConfig *instance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
instance = [[self alloc] init];
});
return instance;
}
```
2.变量访问控制
读取方法是用同步调用
写方法是异步保证性能
HTTPServer.h
@interface HTTPServer : NSObject {
NSString *interface;
}
-(NSString *)interface;
-(void)setInterface:(NSString *)value;
HTTPServer.m
-(NSString *)interface {
//定义临时变量 增加引用计数 防止对象被释放
__block NSString *result;
dispatch_sync(serverQueue, ^{
result = interface;
});
return result;
}
-(void)setInterface:(NSString *)value {
NSString *valueCopy = [value copy];
dispatch_async(serverQueue, ^{
interface = valueCopy;
});
}
上面的读取方案如果存在队列嵌套调用的话会产生死锁,interface的get方法可以优化如下:
#import "HTTPConfig.h"
static const void * const kHTTPQueueSpecificKey = &kHTTPQueueSpecificKey;
@interface HTTPConfig ()
{
NSString *interface;
dispatch_queue_t serverQueue;
}
@end
@implementation HTTPConfig
-(instancetype)init {
self = [super init];
if(self) {
serverQueue = dispatch_queue_create("com.uu.queue", NULL);
dispatch_queue_set_specific(serverQueue, kHTTPQueueSpecificKey, (__bridge void *)self, NULL);
}
return self;
}
-(NSString *)interface {
id currentObj = (__bridge id)dispatch_get_specific(kHTTPQueueSpecificKey);
if(currentObj){
return interface;
}
//定义临时变量 增加引用计数 防止对象被释放
__block NSString *result;
dispatch_sync(serverQueue, ^{
result = interface;
});
return result;
}
-(void)setInterface:(NSString *)value {
NSString *valueCopy = [value copy];
dispatch_async(serverQueue, ^{
interface = valueCopy;
});
}
@end
3.FMDB数据库中使用
开源的FMDB中提供了FMDatabaseQueue类,采用了GCD的Queue来保证线程安全。
FMDatabaseQueue类的实现代码
-(instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName {
self = [super init];
if (self != nil) {
_db = [[[self class] databaseClass] databaseWithPath:aPath];
FMDBRetain(_db);
#if SQLITE_VERSION_NUMBER >= 3005000
BOOL success = [_db openWithFlags:openFlags vfs:vfsName];
#else
BOOL success = [_db open];
#endif
if (!success) {
NSLog(@"Could not create database queue for path %@", aPath);
FMDBRelease(self);
return 0x00;
}
_path = FMDBReturnRetained(aPath);
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
_openFlags = openFlags;
}
return self;
}
-(void)inDatabase:(void (^)(FMDatabase *db))block {
/* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
* and then check it against self to make sure we‘re not about to deadlock. */
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
FMDBRetain(self);
dispatch_sync(_queue, ^() {
FMDatabaseQueue *currentSyncQueue2 = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
FMDatabase *db = [self database];
block(db);
if ([db hasOpenResultSets]) {
NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
}
});
FMDBRelease(self);
}
FMDB数据库具体使用:所有的数据库操作都在一个串行队列中顺序执行,防止了冲突,保证了数据读写的一致性。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
[queue inDatabase:^(FMDatabase *db) {
FMResultSet *rs = [db executeQuery:@"select * from Track"];
while ([rs next]) {
NSLog(@"%@",[rs resultDictionary]);
}
}];
Dispatch Sources
NSOperationQueue操作队列
NSOperationQueue是基于Objective-C封装的异步对象操作队列,提供了更为灵活强大的功能:
1) 任务定义可以基于NSOperation,NSInvocationOperation,NSBlockOperation不同的方式;
2)队列可是可管理的相对于GCD,可以取消队列中的任务;
3)并发的任务数可以通过maxConcurrentOperationCount控制。
所有的任务操作首先要封装成NSOperation的子类,加入队列执行。
下面列出NSOperationQueue头文件中定义的属性和方法,通过这些方法我们可以对NSOperationQueue,对它提供的特性/功能/使用有一个基本的了解。
@interface NSOperationQueue : NSObject
//增加操作任务到队列
-(void)addOperation:(NSOperation *)op;
//增加多个操作任务到队列
-(void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
//增加基于Block块定义的操作任务到队列
-(void)addOperationWithBlock:(void (^)(void))block ;
//所有的操作任务
@property (readonly, copy) NSArray *operations;
//操作任务个数
@property (readonly) NSUInteger operationCount ;
//最大允许的并发数,设置为1时等价于GCD的串行队列,大于1相当为并发队列
@property NSInteger maxConcurrentOperationCount;
//队列挂起状态控制
@property (getter=isSuspended) BOOL suspended;
//队列名称
@property (nullable, copy) NSString *name ;
//对列服务质量
@property NSQualityOfService qualityOfService;
//队列对应的底层GCD的队列,从这里可以印证NSOperationQueue在GCD的基础上做了封装
@property ( assign ) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
//取消所有的任务
-(void)cancelAllOperations;
//等待所有任务完成
-(void)waitUntilAllOperationsAreFinished;
//获取当前正在执行任务的队列
+(NSOperationQueue *)currentQueue ;
//获取当前的主线程队列
+(NSOperationQueue *)mainQueue ;
@end
NSInvocationOperation
NSInvocationOperation将目标对象,执行的方法和相关参数封装成Operation对象,加入队列并发执行。
-(void)invocationOperationTest {
NSOperationQueue *aQueue = [[NSOperationQueue alloc] init];
NSString *data = @"invocation paras";
NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork:) object:data];
[aQueue addOperation:theOp];
}
-(void)doWork:(NSString*)data {
NSLog(@"doWork show data %@ ",data );
}
NSBlockOperation
NSBlockOperation可以将一个或多个代码片段封装成Block对象加入到队列,异步的并发执行。当有多个多个代码片段加入到NSBlockOperation中时,它相当于GCD的Groups分组控制。
NSBlockOperation * opt = [[NSBlockOperation alloc] init];
[opt addExecutionBlock:^{
NSLog(@"Run in block 1 ");
}];
[opt addExecutionBlock:^{
NSLog(@"Run in block 2 " );
}];
[opt addExecutionBlock:^{
NSLog(@"Run in block 3 " );
}];
[opt addExecutionBlock:^{
NSLog(@"Run in block 4 " );
}];
[[NSOperationQueue currentQueue] addOperation:opt];
上面代码的执行如下,可以看出这些Block块之间是异步并发执行的。
Run in block 2
Run in block 1
Run in block 3
Run in block 4
NSOperation
NSOperation是一个抽象类,不行直接使用,必须之类化使用。前面介绍的NSInvocationOperation,NSBlockOperation都是NSOperation的子类。当它们不能满足我们需要时可以定义NSOperation的子类来实现多线程任务。
下面是NSOperation子类实现线程操作任务时的几个关键方法:
//表示是否允许并发执行
-(BOOL)isAsynchronous;
//KVO属性方法,表示任务执行状态
-(BOOL)isExecuting;
//KVO属性方法,表示任务是否执行完成
-(BOOL)isFinished;
//任务启动前的setup方法,满足执行条件时,启动线程执行main方法
-(void)start;
//实现具体的任务逻辑
-(void)main;
下面例子定义了HTTPImageOperation类,用来异步下载网络图片。
HTTPImageOperation.h的定义
@interface HTTPImageOperation : NSOperation
-(instancetype)initWithImageURL:(NSURL*)url;
@property (nonatomic, copy) void (^downCompletionBlock)(NSImage *image);
@end
HTTPImageOperation.m的实现
#import "HTTPImageOperation.h"
@interface HTTPImageOperation () {
BOOL executing;
BOOL finished;
}
@property(nonatomic,strong)NSURL *url;
@end
@implementation HTTPImageOperation
-(instancetype)initWithImageURL:(NSURL*)url {
self = [super init];
if(self) {
_url = url;
}
return self;
}
//表示是否允许并发执行
-(BOOL)isAsynchronous {
return YES;
}
-(BOOL)isExecuting {
return executing;
}
-(BOOL)isFinished {
return finished;
}
-(void)start {
if([self isCancelled]){
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
-(void)main {
@try {
NSImage *image = [[NSImage alloc]initWithContentsOfURL:self.url];
if(self.downCompletionBlock){
self.downCompletionBlock(image);
}
[self completeOperation];
}
@catch(...) {
[self completeOperation];
}
}
-(void)completeOperation {
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
@end
HTTPImageOperation的使用:下载2个网络图片,并且显示在2个ImageView中。
NSURL *url1 = [NSURL URLWithString:@"http://www.jobbole.com/wp-content/uploads/2016/01/75c6c64ae288896908d2c0dcd16f8d65.jpg"];
HTTPImageOperation *op1 = [[HTTPImageOperation alloc]initWithImageURL:url1];
op1.downCompletionBlock = ^(NSImage *image){
if(image){
self.leftImageView.image = image;
}
};
NSURL *url2 = [NSURL URLWithString:@"http://ww1.sinaimg.cn/mw690/bfdcef89gw1exedm1rzkpj20j602d757.jpg"];
HTTPImageOperation *op2 = [[HTTPImageOperation alloc]initWithImageURL:url2];
op2.downCompletionBlock = ^(NSImage *image){
if(image){
self.rightImageView.image = image;
}
};
[[NSOperationQueue mainQueue] addOperation:op1];
[[NSOperationQueue mainQueue] addOperation:op2];
设置任务间的依赖
可以使用addDependency:方法在2个或多个任务间设置依赖关系,当操作任务的依赖的任务全部执行完成后,任务才能得到执行。
下面的例子中只有opt1任务执行后,opt2才能得到执行。
NSOperationQueue *aQueue = [[NSOperationQueue alloc] init];
NSBlockOperation * opt1 = [[NSBlockOperation alloc] init];
[opt1 addExecutionBlock:^{
NSLog(@"Run in block 1 ");
}];
NSBlockOperation * opt2 = [[NSBlockOperation alloc] init];
[opt2 addExecutionBlock:^{
NSLog(@"Run in block 2 ");
}];
[opt2 addDependency:opt1];
[aQueue addOperation:opt1];
[aQueue addOperation:opt2];
设置NSOperation执行完的回调
NSOperation的completionBlock属性,用来做为任务执行完成后的回调定义,前面介绍的NSBlockOperation例子中,可以设置completionBlock做为所有任务快执行完成的回调通知。
opt.completionBlock = ^{
NSLog(@"Run completion " );
};
取消任务
可以取消单个任务,也可以取消队列中全部未执行的任务。
// 取消单个操作
[operation cancel];
// 取消queue中所有的操作
[queue cancelAllOperations];
暂停或恢复队列执行
修改队列的suspended属性来控制队列的暂停或恢复执行。
//暂停执行
[queue setSuspended:YES];
//恢复执行
[queue setSuspended:NO];
任务执行的优先级
NSOperation可以通过queuePriority属性设置5个不同等级的优先级,在同一个队列中优先级越高的任务最先得到执行。要注意的是有依赖关系的任务还是按依赖关系执行任务,不受优先级的影响。
下面例子中opt3任务优先得到执行。
NSOperationQueue *aQueue = [[NSOperationQueue alloc] init];
NSBlockOperation * opt1 = [[NSBlockOperation alloc] init];
[opt1 addExecutionBlock:^{
NSLog(@"Run in block 1 ");
}];
NSBlockOperation * opt2 = [[NSBlockOperation alloc] init];
[opt2 addExecutionBlock:^{
NSLog(@"Run in block 2 ");
}];
NSBlockOperation * opt3 = [[NSBlockOperation alloc] init];
opt3.queuePriority = NSOperationQueuePriorityVeryHigh;
[opt3 addExecutionBlock:^{
NSLog(@"Run in block 3 ");
}];
[aQueue addOperations:@[opt1,opt2,opt3] waitUntilFinished:NO];
NSThread
NSThread是传统意义上底层pthread线程的OC封装,提供更多的灵活性
1)设置线程的服务质量Qos
2)可以设置线程堆栈大小
2)线程提供local数据字典:可以存储key/value数据
相对于NSOperation ,GCD更高级的多线程技术,NSThread也有自己独特的优势:
1)实时性更高
2)与RunLoop结合,提供了更为灵活高效的线程管理方式
NSThread的缺点:创建线程代价较大,需要同时占用应用和内核的内存空间(GCD的线程只占用内核的内存空间);编写线程相关的代码相对繁杂。
线程创建方式
1.通过target-selector方式:是比较简单方便的线程创建方法,直接将某个对象的方法和需要的参数包装为线程的执行方法
下面是代码示例,创建新的独立线程,执行当前类的threadExecuteMethod方法。
[NSThread detachNewThreadSelector:@selector(threadExecuteMethod:) toTarget:self
withObject:nil];
2.直接使用NSThread创建线程
这个方式比较灵活,可以在线程启动前设置一些参数,比如线程名称,优先级,堆栈大小等。
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadExecuteMethod:) object:nil];
thread.name = @"Thread1";
[thread start];
3.对NSThread子类化
对一些处理加工性耗时的操作,可以独立出来,封装成NSThread的子类进行独立处理。处理完成的结果可以以通知或代理的方法通知到主线程。
通过重载main方法实现子类化,注意在main中要加上@autoreleasepool 自动释放池确保线程处理过程中及时释放内存资源。
//
// WorkThread.h
// NSQueueDemo
//
// Created by zhaojw on 1/21/16.
// Copyright © 2016 MacDev.io. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface WorkThread : NSThread
--((instancetype)initWithImageURL:(NSURL*)url;
@end
#import "WorkThread.h"
@interface WorkThread ()
@property(nonatomic,strong)NSURL *url;
@end
@implementation WorkThread
-(instancetype)initWithImageURL:(NSURL*)url {
self = [super init];
if(self){
_url = url;
}
return self;
}
-(void)main {
NSLog(@"WorkThread main");
@autoreleasepool {
NSImage *image = [[NSImage alloc]initWithContentsOfURL:_url];
//Do some other image process work
}
}
@end
NSThread类中关键方法和属性
获取当前线程和主线程相关方法:
//获取当前运行的线程
+(NSThread *)currentThread;
//是否支持多线程
+(BOOL)isMultiThreaded;
//是否是主线程的属性
@property (readonly) BOOL isMainThread ;
//是否是主线程
+(BOOL)isMainThread;
//获取主线程
+(NSThread *)mainThread ;
线程的配置参数
//线程的local数据字典
@property (readonly, retain) NSMutableDictionary *threadDictionary;
//线程优先级
+(double)threadPriority;
//修改优先级
+(BOOL)setThreadPriority:(double)p;
//线程服务质量Qos
@property NSQualityOfService qualityOfService;
//线程名称
@property (copy) NSString *name ;
//堆栈大小
@property NSUInteger stackSize ;
线程的调试接口 获取当前线程调用链堆栈
//堆栈返回地址
+(NSArray<NSNumber *> *)callStackReturnAddresses ;
//堆栈调用链
+(NSArray<NSString *> *)callStackSymbols ;
线程的执行取消完成状态:
//是否正在执行
@property (readonly, getter=isExecuting) BOOL executing ;
//是否完成
@property (readonly, getter=isFinished) BOOL finished ;
//是否取消
@property (readonly, getter=isCancelled) BOOL cancelled;
线程的控制方法:
//取消执行
-(void)cancel;
//启动执行
-(void)start;
//线程执行的主方法,子类化线程实现这个方法即可
-(void)main;
//休眠到指定日期
+(void)sleepUntilDate:(NSDate *)date;
//定期休眠
+(void)sleepForTimeInterval:(NSTimeInterval)ti;
//退出线程
+(void)exit;
NSObject线程扩展方法
在主线程执行的方法
aSelector:方法
thr:线程
arg:方法的参数
wait:是否等待执行完成
modes:runloop的模式
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:( NSArray *)array;
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
在指定的线程上执行方法
-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
后台线程执行的方法
-(void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;
线程中的共享资源保护
1.OSAtomic原子操作
对基本的简单数据类型提供原子操作的函数,包括数学加减运算和逻辑操作。相比较加锁保护资源的方式,原子操作更轻量级,性能更高。
```
int32_t theValue1 = 0;
int32_t theValue2 = 0;
OSAtomicIncrement32(&theValue1);
OSAtomicDecrement32(&theValue2);
```
2.加锁
1)NSLock:互斥锁
下面的代码展示了最简单的锁的使用。
tryLock方法尝试获取锁,如果没有可用的锁,函数返回NO,不会阻塞当前任务执行。
NSLock *theLock = [[NSLock alloc] init];
if ([theLock tryLock]) {
//Do some work
[theLock unlock];
}
2)NSRecursiveLock:递归锁主要用在循环或递归操作中,保证了同一个线程执行多次加锁操作不会产生死锁;只要保证加锁解锁次数相同即可释放资源使其它线程得到资源使用。
使用场景:同一个类中多个方法,递归操作,循环处理中对受保护的资源的访问
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
for(int i=0;i<10;i++){
[theLock lock];
//Do some work
[theLock unlock];
}
3)NSConditionLock:条件锁
condition为一个整数参数,满足condition条件时获得锁,解锁时可以设置condition条件。
lock,lockWhenCondition与unlock,unlockWithCondition可以任意组合使用。unlockWithCondition表示解锁并且设置condition值;lockWhenCondition表示condition为参数值时获得锁。
#import "NSConditionLockTest.h"
@interface NSConditionLockTest ()
@property(nonatomic,strong)NSMutableArray *queue;
@property(nonatomic,strong)NSConditionLock *condition;
@end
@implementation NSConditionLockTest
-(void)doWork1 {
NSLog(@"doWork1 Begin");
while(true)
{
sleep(1);
[self.condition lock];
NSLog(@"doWork1 ");
[self.queue addObject:@"A1"];
[self.queue addObject:@"A2"];
[self.condition unlockWithCondition:2];
}
NSLog(@"doWork1 End");
}
-(void)doWork2 {
NSLog(@"doWork2 Begin");
while(true)
{
sleep(1);
[self.condition lockWhenCondition:2];
NSLog(@"doWork2 ");
[self.queue removeAllObjects];
[self.condition unlock];
}
NSLog(@"doWork2 End");
}
-(void)doWork {
[self performSelectorInBackground:@selector(doWork1) withObject:nil ];
[self performSelector:@selector(doWork2) withObject:nil afterDelay:0.1];
}
-(NSMutableArray *)queue {
if(!_queue){
_queue = [[NSMutableArray alloc]init];
}
return _queue;
}
-(NSConditionLock*)condition {
if(!_condition) {
_condition = [[NSConditionLock alloc]init];
}
return _condition;
}
@end
3.NSCondition
Condition通过一些条件控制来多个线程协作完成任务。当条件不满足时线程等待;条件满足时通过发送signal信号来通知等待的线程继续处理。
#import "NSConditionTest.h"
@interface NSConditionTest ()
@property(nonatomic,assign)BOOL completed;
@property(nonatomic,strong)NSCondition *condition;
@end
@implementation NSConditionTest
-(void)clearCondition{
self.completed = NO;
}
-(void)doWork1{
NSLog(@"doWork1 Begin");
[self.condition lock];
while (!self.completed) {
[self.condition wait];
}
NSLog(@"doWork1 End");
[self.condition unlock];
}
-(void)doWork2{
NSLog(@"doWork2 Begin");
//do some work
[self.condition lock];
self.completed = YES;
[self.condition signal];
[self.condition unlock];
NSLog(@"doWork2 End");
}
-(void)doWork {
[self performSelectorInBackground:@selector(doWork1) withObject:nil ];
[self performSelector:@selector(doWork2) withObject:nil afterDelay:0.1];
}
-(NSCondition*)condition {
if(!_condition){
_condition = [[NSCondition alloc]init];
}
return _condition;
}
@end
执行doWork方法后输出如下:
doWork1 Begin
doWork2 Begin
doWork2 End
doWork1 End
[email protected]同步指令
synchronized是自动实现的加锁技术,同时增加了异常处理。通俗的讲就是编译器自动插入加锁和解锁的代码,同时捕获异常,避免异常时不及时释放锁导致死锁。
下面是摘自 Countly 中使用synchronized指令的句子。
-(void)recordEvent:(NSString *)key count:(int)count
{
@synchronized (self)
{
NSArray* events = [[[CountlyDB sharedInstance] getEvents] copy];
for (NSManagedObject* obj in events)
{
CountlyEvent *event = [CountlyEvent objectWithManagedObject:obj];
if ([event.key isEqualToString:key])
{
event.count += count;
event.timestamp = (event.timestamp + time(NULL)) / 2;
[obj setValue:@(event.count) forKey:@"count"];
[obj setValue:@(event.timestamp) forKey:@"timestamp"];
[[CountlyDB sharedInstance] saveContext];
return;
}
}
CountlyEvent *event = [CountlyEvent new];
event.key = key;
event.count = count;
event.timestamp = time(NULL);
[[CountlyDB sharedInstance] createEvent:event.key count:event.count sum:event.sum segmentation:event.segmentation timestamp:event.timestamp];
}
}
RunLoop
线程是任务分解成不同的工作单元分配给线程去执行,解决了多任务并发执行的问题,提高了系统性能。在现代交互式系统中,还存在大量的未知不确定的异步事件,这时候线程是一直是出于等待状态的,直到有事件发生才会唤醒线程去执行,执行完成后系统又恢复到以前的等待状态。如何控制线程在等待和执行任务状态间无缝切换,就引入了RunLoop的概念。
RunLoop称为事件循环,可以理解为系统中对各种事件源不间断的循环的处理。应用在运行过程中会产生大量的系统和用户事件,包括定时器事件,用户交互事件(鼠标键盘触控板操作),模态窗口事件,各种系统Source事件,应用自定义的Source事件等等,每种事件都会存储到不同的FIFO先进先去的队列,等待事件循环依次处理。
被RunLoop管理的线程在挂起时,不会占用系统的CPU资源,可以说RunLoop是非常高效的线程管理技术。
线程和RunLoop是一一对应,系统会将线程和它的RunLoop对象实例以key/value字典形式存储到全局字典中统一管理和访问。获取线程的RunLoop时,如果字典中不存在或新建一个并将这个RunLoop存储到字典。
每个应用启动后系统默认生成一个RunLoop对象,也可以称为主线程的RunLoop,由它完成主线程运行期间各种事件调度控制。
RunLoop中包括3大核心组件,定时器,输入源Input Sources和观察者Observer,后面会逐一介绍。
以上是关于[转]多线程的主要内容,如果未能解决你的问题,请参考以下文章