简介

在软件开发中,多线程编程技术被广泛应用,相信多线程任务对我们来说已经不再陌生了。有了多线程技术,我们可以同做多个事情,而不是一个一个任务地进行。比如:前端和后台作交互、大任务(需要耗费一定的时间和资源)等等。也就是说,我们可以使用线程把占据时间长的任务放到后台中处理,而不影响到用户的使用。

线程间通讯

有一个非常重要的队列,就是主队列。在这个队列中处理多点触控及所有与UI相关操作等等。它非常特殊,原因有两点。一是我们绝对不想它阻塞,我们不会将需要执行很长时间的任务放在主队列上执行。二是我们将其用于所有与UI相关的同步,也就是线程间通讯需要注意的地方。所有有可能会使屏幕UI发生变化的,都应放在主队列上执行。

线程的定义:

每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。

转自百度百科:多线程

如果熟悉多线程编程技术这一块的朋友们,可以去看关于多线程安全的文章,是我写的另一篇文章”iOS开发-多线程开发之线程安全篇“;

IOS支持的多线程技术:

一、Thread:

1)显式创建线程:NSThreed

2)隐式创建线程:NSObject

二、Cocoa operations:

NSOperation类是一个抽象类,因为我们必须使用它的两个子类。

  1)NSInvocationOperation

2)NSBlockOperation

————————————————————————————

3)NSOperationQueue(继承于NSObject)

三、Grand Central Dispatch (GCD):

1)GCD的创建

2)重复执行线程及一次性执行:dispatch_apply & dispatch_once

3)操作(串行)队列:dispatch_queue_create

4)GCD群组通知:dispatch_group_t

5)GCD实现计时器

6)后台运行

7)延迟执行

四、比较多线程技术

一、Thread

我们可以使用NSTherad或NSObject类去调用:

1)显式创建线程:NSThread

创建NSThread有两个办法

1.1)创建之后需要使用start方法,才会执行方法:

NSThread *threadAlloc = [[NSThread alloc] initWithTarget:self selector:@selector(threadAlloc) object:nil];
[threadAlloc start];

1.2)创建并马上执行方法:

[NSThread detachNewThreadSelector:@selector(threadAlloc:) toTarget:self withObject:nil];

2)隐式创建线程:NSObject

我们也可以使用NSObject类的方法直接调用方法

[self performSelectorInBackground:@selector(threadAlloc) withObject:nil];

取消线程的方法:

实际上并没有真正提供取消线程的API。苹果提供了一个cancel的api,但它不能作用于取消线程,它只能改变线程的运行状态。我们可以使用它来进行条件判断。

- (void)threadCancel
{
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadCancelNow) object:nil];
[thread start];
} - (void)threadCancelNow
{
int a = ;
while (![[NSThread currentThread] isCancelled]) {
NSLog(@"a - %d", a);
a++;
if (a == ) {
NSLog(@"终止循环");
[[NSThread currentThread] cancel];
break;
}
}
}

程序效果:循环输出5000次,线程就会被终止。

NSThread线程间通讯-调用主线程修改UI:

只需要传递一个selector和它的参数,withObject参数可以为nil,waitUntilDone代表是否要等待调用它的这个线程执行之后再将它从主队列调出,并在主队列上运行,通常设为NO,不需要等待。

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; 

NSThread相关属性及方法:

// 获取/设置线程的名字
@property (copy) NSString *name NS_AVAILABLE(10_5, 2_0); /**
* 获取当前线程的线程对象
*
* 通过这个属性可以查看当前线程是第几条线程,主线程为1。
* 可以看到当前线程的序号及名字,主线程的序号为1,依次叠加。
*/
+ (NSThread *)currentThread; // 线程休眠(秒)
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; // 线程休眠,指定具体什么时间休眠
+ (void)sleepUntilDate:(NSDate *)date; // 退出线程
// 注意:这里会把线程对象销毁!销毁后就不能再次启动线程,否则程序会崩溃。
+ (void)exit;

二、Cocoa operations

1)NSInvocationOperation

创建NSInvocationOperation线程,附带一个NSString参数:

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"abc"];
// 需要启动线程,默认是不启动的。
[operation start];

如在创建时定义了参数,那么接收的时候,可以对sender进行转换,如字符串、数组等:

- (void)invocationAction:(NSInvocationOperation *)sender
{
NSLog(@"sender - %@", sender); // 输出params
NSString *str = (NSString *)sender;
NSLog(@"str - %@e", str); // params
}

附带一提,线程的普通创建一般为并发执行的,因为串行队列是需要显式创建的,如没看见此类代码,那么即是并发队列线程,因此,上述代码也就是并发线程。关于并发和串行队列(线程),我将会在下面详细说明,我们继续往下看。

你也可以使用NSOperationQueue来创建一个线程队列,用来添加子线程:

NSOperationQueue *invocationQueue = [[NSOperationQueue alloc] init];

// 线程A
NSInvocationOperation *invocationQ1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"invocationQ1"]; // 线程B
NSInvocationOperation *invocationQ2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"invocationQ2"]; // 往invocationQueue添加子线程
[invocationQueue addOperations:@[invocationQ1, invocationQ2] waitUntilFinished:YES];

必须使用addOperations:方法把线程添加至队列,不然线程不会执行,队列是并行执行。或者,你也可以使用addOperation:方法添加单个线程。

2)NSBlockOperation

创建NSBlockOperation

// 创建线程任务
NSBlockOperation *blockOperation = [NSBlockOperation
blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:];
NSLog(@"one - %@", [NSThread currentThread]);
}];;// 执行线程任务
[blockOperation start];

注意:这会在当前的线程中执行,因为它是根据调用的线程所决定的。

比方说你在主线程中运行它,那么它就是在主线程中执行任务。如果你是在子线程中运行它,那么它就是在子线程中执行任务。

做个简单的实验,我们新建一条子线程,然后在子线程里调用NSBlockOperation

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
NSBlockOperation *blockOperation = [NSBlockOperation
blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:];
NSLog(@"one - %@", [NSThread currentThread]);
// print: one - <NSThread: 0x7f8ac2e1d0b0>{number = 2, name = (null)}
}];; [blockOperation start];
});

它将打印:one - <NSThread: 0x7f8ac2e1d0b0>{number = 2, name = (null)},因此这个理论是正确的

我们也可以使它并发执行,通过使用addExecutionBlock方法添加多个Block,这样就能使它在主线程和其它子线程中工作。

NSBlockOperation *blockOperation = [NSBlockOperation
blockOperationWithBlock:^{
NSLog(@"one - %@", [NSThread currentThread]);
}];; [blockOperation addExecutionBlock:^{
NSLog(@"two - %@", [NSThread currentThread]);
}]; [blockOperation addExecutionBlock:^{
NSLog(@"three - %@", [NSThread currentThread]);
}]; [blockOperation addExecutionBlock:^{
NSLog(@"four - %@", [NSThread currentThread]);
}]; [blockOperation start];

它将打印:

two - <NSThread: 0x7fea8a70b000>{number = , name = (null)}
one - <NSThread: 0x7fea8a558a40>{number = , name = (null)}
four - <NSThread: 0x7fea8a406b90>{number = , name = main}
three - <NSThread: 0x7fea8a436e40>{number = , name = (null)}

大家都看到,即使我们通过使用addExecutionBlock方法使它并发执行任务,但是它也依旧会在主线程执行,因此我们就需要使用NSOperationQueue了。

3)NSOperationQueue

这里介绍一下NSOperation的依赖关系,依赖关系会影响线程的执行顺序:

// 创建操作队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 线程A
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1");
[NSThread sleepForTimeInterval:];
}]; // 线程B
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op2");
}]; // 线程C
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op3");
[NSThread sleepForTimeInterval:];
}]; // 线程B依赖线程C,也就是等线程C执行完之后才会执行线程B
[op2 addDependency:op3];
// 线程C依赖线程A,同上,只不过依赖对象改成了线程A
[op3 addDependency:op1]; // 为队列添加线程
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];

当你没添加依赖时,队列是并行执行的。

注意:依赖关系可以多重依赖,但不要建立循环依赖。

Cocoa operations线程间通信-调用主线程修改UI:

// 创建线程对象(并发)
NSBlockOperation *blockOperation = [[NSBlockOperation alloc] init]; // 添加新的操作
[blockOperation addExecutionBlock:^{
NSLog(@"two - %@", [NSThread currentThread]);
}]; // 添加新的操作
[blockOperation addExecutionBlock:^{
NSLog(@"three - %@", [NSThread currentThread]);
// 在主线程修改UI
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperationWithBlock:^{
[self editUINow];
}];
}]; [blockOperation start];

NSOperation方法及属性:

// 设置线程的最大并发数
@property NSInteger maxConcurrentOperationCount; // 线程完成后调用的Block
@property (copy) void (^completionBlock)(void); // 取消线程
- (void)cancel;

只列举上面那些,其它的方法就不全列出来了。

注意:在NSOperationQueue类中,我们可以使用cancelAllOperations方法取消所有的线程。这里需要说明一下,不是执行cancelAllOperations方法时就会马上取消,是等当前队列执行完,下面的队列不会再执行。

三、Grand Central Dispatch (GCD)

1)GCD异步线程的创建:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
NSLog(@"线程 - %@", [NSThread currentThread]);
});

GCD也可以创建同步的线程,只需要把async改成sync即可。

2)重复执行线程:dispatch_apply

以下代码会执行4次:

dispatch_apply(, DISPATCH_QUEUE_PRIORITY_DEFAULT, ^(size_t index) {
// index则为执行的次数 0开始递增
NSLog(@"one - %ld", index);
});

index参数为执行的次数,从0开始递增。

其中需要注意的是,每次执行都会新开辟一条子线程,因为是异步的原因,它们不会是顺序的。

[:] one - , thread - <NSThread: 0x100110b50>{number = , name = main}
[:] one - , thread - <NSThread: 0x103800000>{number = , name = (null)}
[:] one - , thread - <NSThread: 0x100112b90>{number = , name = (null)}
[:] one - , thread - <NSThread: 0x100501180>{number = , name = (null)}

然而,GCD还有一次性执行的方法:

dispatch_once_t once;
dispatch_once(&once, ^{
NSLog(@"once - %@", [NSThread currentThread]); // 主线程
});

它通常用于创建单例。

3)操作队列:dispatch_queue_create

使用GCD也能创建串行队列,具体代码如下:

/**
* GCD创建串行队列
*
* @param "com.GarveyCalvin.queue" 队列字符串标识
* @param DISPATCH_QUEUE_CONCURRENT 可选的,可以是NULL
*
* @return dispatch_queue_t
*/
dispatch_queue_t queue = dispatch_queue_create("com.GarveyCalvin.queue", DISPATCH_QUEUE_CONCURRENT); // 线程A
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:];
NSLog(@"sleep async - %@", [NSThread currentThread]);
}); // 线程B
dispatch_barrier_async(queue, ^{
[NSThread sleepForTimeInterval:];
NSLog(@"sleep barrier2 - %@", [NSThread currentThread]);
}); // 线程C
dispatch_async(queue, ^{
NSLog(@"async");
});

运行效果:以上会先执行 线程A-》线程B-》线程C,它是一个串行队列。

dispatch_queue_create的第二个参数:

1)DISPATCH_QUEUE_SERIAL(串行)

2)DISPATCH_QUEUE_CONCURRENT(并发)

4)GCD群组通知:dispatch_group_t

GCD的高级用法,等所有线程都完成工作后,再作通知。

// 创建群组
dispatch_group_t group = dispatch_group_create(); // 线程A
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
NSLog(@"group1");
[NSThread sleepForTimeInterval:];
}); // 线程B
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
NSLog(@"group2");
}); // 待群组里的线程都完成之后调用的通知
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
NSLog(@"group success");
});

群组里的线程也是并行队列。线程A和线程B都执行完之后,会调用通知打印group success。

5)GCD实现计时器

__block int time = ;
CGFloat reSecond = 1.0;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, , , queue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, reSecond * NSEC_PER_SEC, );
dispatch_source_set_event_handler(timer, ^{
time--;
NSLog(@"%d", time);
if (time == ) {
dispatch_source_cancel(timer);
}
});
dispatch_resume(timer);

代码效果:创建了一个计时器,计时器运行30秒,每过一秒会调用一次block,我们可以在block里面写代码。因为dispatch_source_t默认是挂起状态,因此我们使用时需要使用dispatch_resume方法先恢复,不然线程不会执行。

GCD线程间通信-调用主线程修改UI:

有时候我们请求后台作数据处理,数据处理是异步的,数据处理完成后需要更新UI,这时候我们需要切换到主线程修改UI,例子如下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
NSLog(@"异步数据处理 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:];
NSLog(@"数据处理完成"); // 调用主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI - %@", [NSThread currentThread]);
[self editUINow];
});
});

因为是在主线程修改UI,所以我们最好是使用同步的GCD方法dispatch_sync。但这还不够,我们还需要使用dispatch_get_main_queue()方法来获得主线程,之后就是作UI的更新工作了。

GCD方法及属性:

// 获取主线程
dispatch_get_main_queue() // 创建队列:第一个参数是队列的名称,它会出现在调试程序等之中,是个内部名称。第二个参数代表它是串行队列还是并并发队列,NULL代表串行队列。
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr); // 创建异步调度队列
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); // 恢复队列
void dispatch_resume(dispatch_object_t object); // 暂停队列
void dispatch_suspend(dispatch_object_t object);

小结:本文主要介绍了IOS三种线程对比及其使用方法。需要特别注意的是,在修改任何有关于UI的东西,我们必须要切换至主线程,在主线程里修改UI,避免不必要的麻烦产生。苹果是推荐我们使用GCD,因为GCD是这三种里面抽象级最高的,使用起来也简单,也是消耗资源最低的,并且它执行效率比其它两种都高。因此,能够使用GCD的地方,尽量使用GCD。

6)后台运行

使用block的另一个好处是可以让程序在后台较久地运行。在以前,当应用被按Home键退出后,应用仅有最多5秒的时间做一些保存或清理资源的工作。 但是如果使用GCD,你可以让你的应用最多有10分钟的时间在后台长久运行。这个时间可以用来做各种事情,包括清理本地缓存、发送统计数据等工作。

AppDelegate.h
@interface AppDelegate () @property (assign, nonatomic) UIBackgroundTaskIdentifier backGroundUpdate; @end AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self beginBackGroundUpdate];
// 需要长久运行的代码
[self endBackGroundUpdate];
} - (void)beginBackGroundUpdate
{
self.backGroundUpdate = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackGroundUpdate];
}];
} - (void)endBackGroundUpdate
{
[[UIApplication sharedApplication] endBackgroundTask:self.backGroundUpdate];
self.backGroundUpdate = UIBackgroundTaskInvalid;
}

建议大家在真机上测试,因为笔者在模拟器测试了24分钟还有效。

7)延迟执行

如果我们想要某段代码延迟执行,那么可以使用dispatch_after ,但是有一个缺点是,当提交代码后(代码执行后),我们不能取消它,它将会运行。另外,我们可以使用 NSTimer 进行延时操作,值得一提,它是可以被取消的。

dispatch_time_t time_t = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_after(time_t, queue, ^{
NSLog(@"hahalo");
});

比较多线程技术

一、Thread: 

优点:量级较轻。

缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。

二、Cocoa operations:

优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。

三、Grand Central Dispatch (GCD):

优点:GCD基于C的API,非常底层,可以充分利用多核,能够轻松在多核系统上高效运行并发代码,也是苹果推荐使用的多线程技术。

本文参考:

iOS多线程开发

GCD的另一个用处是可以让程序在后台较长久的运行。

全面掌握iOS多线程攻略 —— PS:这个攻略较多,但是有很多重复的内容。

iOS多线程的初步研究(一)-- NSThread


博文作者:GarveyCalvin

博文出处:http://www.cnblogs.com/GarveyCalvin/

本文版权归作者和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作!

iOS开发-多线程编程技术(Thread、Cocoa operations、GCD)的更多相关文章

  1. IOS开发 多线程编程 - NSThread

    每个iOS应用程序都有个专门用来更新显示UI界面.处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验.一般的解决方案就是将 ...

  2. IOS开发 多线程编程 - NSOperationQueue

    一.简介 一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的.也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步 ...

  3. IOS开发 多线程编程 - NSOperation

    一.NSOperation 1.简介 NSOperation实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或非并发的方式执行这个操作. NSOperation本身是抽象基类,因此必须使用 ...

  4. iOS开发多线程编程2 - NSOperation

    1.简介 NSOperation实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或非并发的方式执行这个操作. NSOperation本身是抽象基类,因此必须使用它的子类,使用NSOpera ...

  5. iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD

    原文出处: 容芳志的博客 简介iOS有三种多线程编程的技术,分别是:(一)NSThread(二)Cocoa NSOperation(三)GCD(全称:Grand Central Dispatch) 这 ...

  6. iOS开发——多线程OC篇&多线程详解

    多线程详解 前面介绍了多线程的各种方式及其使用,这里补一点关于多线程的概念及相关技巧与使用,相信前面不懂的地方看了这里之后你就对多线程基本上没有什么问题了! 1——首先ios开发多线程中必须了解的概念 ...

  7. C++ Builder多线程编程技术经验谈(转)

    源:C++ Builder多线程编程技术经验谈 线程之可行性   在很多情况下,可能需要为程序创建线程.这里给出其中一些可能性: (1)如果创建的是一个多文档接口(Multiple Document ...

  8. iOS开发多线程篇—多线程简单介绍

    iOS开发多线程篇—多线程简单介绍 一.进程和线程 1.什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内 比如同时打开QQ.Xcod ...

  9. iOS开发多线程篇—线程安全

    iOS开发多线程篇—线程安全 一.多线程的安全隐患 资源共享 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源 比如多个线程访问同一个对象.同一个变量.同一个文件 当多个线程访问同一块 ...

随机推荐

  1. hdu 5232 Shaking hands 水题

    Shaking hands Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pi ...

  2. Codeforces Round #304 (Div. 2) Break the Chocolate 水题

    Break the Chocolate Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/546/ ...

  3. IDA设置函数类型

    http://www.2cto.com/shouce/ida/1361.htm Action name: SetType 该命令允许你指定当前条目类型. 如果光标处在函数内部,那么函数类型将会被编辑, ...

  4. 解决Visual Studio 2010 “无法导入以下密钥文件” 错误

    错误原文: "错误 1 无法导入以下密钥文件: SamplePlugin.pfx.该密钥文件可能受密码保护.若要更正此问题,请尝试再次导入证书,或手动将证书安装到具有以下密钥容器名称的强名称 ...

  5. 【实例图文详解】OAuth 2.0 for Web Server Applications

    原文链接:http://blog.csdn.net/hjun01/article/details/42032841        OAuth 2.0 for Web Server Applicatio ...

  6. 未能加载文件或程序集“file:///D:/Program Files (x86)/ArcGIS/DeveloperKit10.0/DotNet/ESRI.ArcGIS.3DAnalyst.dll”或它的某一个依赖项。试图加载格式不正确的程序。 行 129,位置 5。

    能加载文件或程序集“file:///C:/Program Files (x86)/ArcGIS/DeveloperKit10.0/DotNet/ESRI.ArcGIS.ADF.Local.dll”或它 ...

  7. 【redis】redis五大类 用法 【转载:https://www.cnblogs.com/yanan7890/p/6617305.html】

    转载地址:https://www.cnblogs.com/yanan7890/p/6617305.html

  8. appium+python自动化98-非select弹出选择框定位解决

    前言 遇到问题:document.getElementsByClassName(...)[0] is undefined 选择框如果是select标签的,可以直接用select专用的方法去定位点击操作 ...

  9. GO语言 -- 调用DLL函数,填平所有的坑,最详尽攻略

    编译dll文件(源代码c++):g++ -shared main.cpp -o test.dll set GOARCH=386 第一个DLL函数,第一个参数,要求传入一个指针,直接指向[]byte类型 ...

  10. ORDER BY,GROUP BY 和DI STI NCT 优化

    读<MySQL性能调优与架构设计>笔记之ORDER BY,GROUP BY 和DI STI NCT 优化 2015年01月18日 18:51:31 lihuayong 阅读数:2593 标 ...