ios多线程编程:
本文的大部分整合参考博客http://blog.csdn.net/q199109106q/article/details/8566300的多线程篇章,加上自己的理解
1.多线程编程之NSOperationQueue(一)
[iOS]后台工作队列:NSOperationQueue、NSOperation、NSInvocationOperation
NSOperationQueue里面可以添加多个Operation,我们可以设置这个队里每次被处理的操作数量。
NSOperationQueue *aQ = [[NSOperationQueue alloc] init];
[aQ setMaxConcurrentOperationCount:10];
这里的setMaxConcurrentOperationCount就是同时被处理的“操作数”,参数为整数int或NSInteger
NSInvocationOperation *aOpt = [[NSInvocationOperation alloc]
initWithTarget:self selector:@selector(doSomeThing) object:nil];
- (void)doSomeThing
{
//读取大量大延迟数据等等
//可以使用performSelectorOnMainThread来将得来的数据返回到主线程
}
在doSomeThing函数里面,我可以从网上读取一些东西,但是读取是需要占用时间,而堵塞主线程的。而使用NSOperation这样使用就不会了。
2.多线程编程之NSOperationQueue(二)
多线程编程是防止主线程堵塞,增加运行效率等等的最佳方法。而原始的多线程方法存在很多的毛病,包括线程锁死等。在Cocoa中,Apple提供了NSOperation这个类,提供了一个优秀的多线程编程方法。
具体步骤:
想要讲一个线程里的公众单独成类,可以继承NSOperation
@interface ImageLoadingOperation : NSOperation {
NSURL *imageURL; //这个例子里面需要传入一个图片地址,所以定义一个NSURL变量
id target; //由于需要返回一些值,所以需要一个对象参数返回要被返回的对象(运行此线程的类对象)
SEL action; //返回值要激发的方法函数
}
初始化函数,传入设置参数
(id)initWithImageURL:(NSURL *)theImageURL target:(id)theTarget action:(SEL)theAction
{
self = [super init]; //在老帖里面解释过为什么需要这么做了
if (self) {
imageURL = [theImageURL retain]; // 拷贝进对象,并retain(为什么?请查老帖)
target = theTarget;
action = theAction;
}
return self;
}
实例化Operation,加入到Queue中
// 这些是需要对其初始化的类中的代码
ImageLoadingOperation *operation = [[ImageLoadingOperation alloc] initWithImageURL:url target:self action:@selector(didFinishLoadingImageWithResult:)]; //初始化
[operationQueue addOperation:operation]; //添加到运行队列
[operation release]; //由于队列对其retain,所以我们需要release它
一个NSOperationQueue 操作队列,就相当于一个线程管理器,而非一个线程。因为你可以设置这个线程管理器内可以并行运行的的线程数量等等。
一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步执行的。
3.多线程编程之NSOperationQueue总结
关于添加NSOperation到NSOperationQueue中
1)添加一个operation
[queue addOperation:operation]; 2)添加一组operation
[queue addOperations:operations waitUntilFinished:NO]; 3)添加一个block形式的operation
[queue addOperationWithBlock:^() {
NSLog(@"执行一个新的操作,线程:%@", [NSThread currentThread]); }];
NSOperation添加到queue之后,通常短时间内就会得到运行。但是如果存在依赖,或者整个queue被暂停等原因,也可能需要等待。
注意:NSOperation添加到queue之后,绝对不要再修改NSOperation对象的状态。因为NSOperation对象可能会在任何时候运行,因此改变NSOperation对象的依赖或数据会产生不利的影响。你只能查看NSOperation对象的状态, 比如是否正在运行、等待运行、已经完成等
添加NSOperation的依赖对象
1.当某个NSOperation对象依赖于其它NSOperation 对象的完成时,就可以通过addDependency方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对 象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。[operation2 addDependency:operation1]; 依赖关系不局限于相同queue中的NSOperation对象,NSOperation对象会管理自己的依赖, 因此完全可以在不同的queue之间的NSOperation对象创建依赖关系
注意不能建议环形依赖,不然会造成死锁。
2.依赖关系会影响到NSOperation对象在queue中的执行顺序。
没有依赖关系时,默认是按照添加顺序执行的。
设置了依赖关系,则依赖先执行。
Operation的具体执行顺序:
对于添加到queue中的operations,它们的执行顺序取决于2点:
1.首先看看NSOperation是否已经准备好:是否准备好由对象的依赖关系确定
2.然后再根据所有NSOperation的相对优先级来确定。优 先级等级则是operation对象本身的一个属性。默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority: 方法来提升或降低operation对象的优先级。优先级只能应用于相同queue中的operations。如果应用有多个operation queue,每个queue的优先级等级是互相独立的。因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行。
注意:优先级不能替代依赖关系,优先级只是对已经准备好的 operations确定执行顺序。先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。
为了最佳的性能,你应该设计你的应用尽可能地异步操作,让应用在Operation正在执行时可以去处理其它事情。如果需要在当前线程中处理operation完成后的结果,可以使用NSOperation的waitUntilFinished方法阻塞当前线程,等待operation完成。通常我们应该避免编写这样的代码,阻塞当前线程可能是一种简便的解决方案,但是它引入了更多的串行代码,限制了整个应用的并发性,同时也降低了用户体验。绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。
[operation waitUntilFinished]; [queue waitUntilAllOperationsAreFinished];
注意:在等待一个 queue时,应用的其它线程仍然可以往queue中添加Operation,因此可能会加长线程的等待时间。
如果你想临时暂停Operations的执行,可以使用queue的 setSuspended:方法暂停queue。不过暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新 Operation执行。你可以在响应用户请求时,暂停一个queue来暂停等待中的任务。稍后根据用户的请求,可以再次调用setSuspended: 方法继续queue中operation的执行
// 暂停queue
[queue setSuspended:YES];
// 继续queue
[queue setSuspended:NO];
4.多线程编程之GCD
介绍:
在iOS所有实现多线程的方案中,GCD应该是最有魅力的,因为GCD本 身是苹果公司为多核的并行运算提出的解决方案。GCD在工作时会自动利用更多的处理器核心,以充分利用更强大的机器。GCD是Grand Central Dispatch的简称,它是基于C语言的。如果使用GCD,完全由系统管理线程,我们不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度你的任务,系统直接提供线程管理。
调度队列(dispath queue):
1.GCD的一个重要概念是队列,它的核心理念:将长期运行的任务拆分成多个工作单元,并将这些单元添加到dispath queue中,系统会为我们管理这些dispath queue,为我们在多个线程上执行工作单元,我们不需要直接启动和管理后台线程。
2.系统提供了许多预定义的dispath queue,包括可以保证始终在主线程上执行工作的dispath queue。也可以创建自己的dispath queue,而且可以创建任意多个。GCD的dispath queue严格遵循FIFO(先进先出)原则,添加到dispath queue的工作单元将始终按照加入dispath queue的顺序启动。
3.dispatch queue按先进先出的顺序,串行或并发地执行任务
1> serial dispatch queue一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务
2> concurrent dispatch queue则尽可能多地启动任务并发执行
创建和管理dispatch queue:
1.获得全局并发Dispatch Queue (concurrent dispatch queue)
1> 并发dispatch queue可以同时并行地执行多个任务,不过并发queue仍然按先进先出的顺序来启动任务。并发queue会在之前的任务完成之前就出列下一个任务并开 始执行。并发queue同时执行的任务数量会根据应用和系统动态变化,各种因素包括:可用核数量、其它进程正在执行的工作数量、其它串行dispatch queue中优先任务的数量等.
2> 系统给每个应用提供三个并发dispatch queue,整个应用内全局共享,三个queue的区别是优先级。你不需要显式地创建这些queue,使用dispatch_get_global_queue函数来获取这三个queue:
// 获取默认优先级的全局并发dispatch queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue;第二个参数目前未使用到,默认0即可
3> 虽然dispatch queue是引用计数的对象,但你不需要retain和release全局并发queue。因为这些queue对应用是全局的,retain和 release调用会被忽略。你也不需要存储这三个queue的引用,每次都直接调用dispatch_get_global_queue获得queue 就行了。
2.创建串行Dispatch Queue (serial dispatch queue)
1> 应用的任务需要按特定顺序执行时,就需要使用串行Dispatch Queue,串行queue每次只能执行一个任务。你可以使用串行queue来替代锁,保护共享资源 或可变的数据结构。和锁不一样的是,串行queue确保任务按可预测的顺序执行。而且只要你异步地提交任务到串行queue,就永远不会产生死锁
2> 你必须显式地创建和管理所有你使用的串行queue,应用可以创建任意数量的串行queue,但不要为了同时执行更多任务而创建更多的串行queue。如果你需要并发地执行大量任务,应该把任务提交到全局并发queue
3> 利用dispatch_queue_create函数创建串行queue,两个参数分别是queue名和一组queue属性
dispatch_queue_t queue;
queue = dispatch_queue_create("cn.itcast.queue", NULL);
3.运行时获得公共Queue
GCD提供了函数让应用访问几个公共dispatch queue:
1> 使用dispatch_get_current_queue函数作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回 block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue。
2> 使用dispatch_get_main_queue函数获得应用主线程关联的串行dispatch queue
3> 使用dispatch_get_global_queue来获得共享的并发queue
4.Dispatch Queue的内存管理
1> Dispatch Queue和其它dispatch对象(还有dispatch source)都是引用计数的数据类型。当你创建一个串行dispatch queue时,初始引用计数为 1,你可以使用dispatch_retain和dispatch_release函数来增加和减少引用计数。当引用计数到达 0 时,系统会异步地销毁这个queue
2> 对dispatch对象(如dispatch queue)retain和release 是很重要的,确保它们被使用时能够保留在内存中。和OC对象一样,通用的规则是如果使用一个传递过来的queue,你应该在使用前retain,使用完之 后release
3> 你不需要retain或release全局dispatch queue,包括全局并发dispatch queue和main dispatch queue
4> 即使你实现的是自动垃圾收集的应用,也需要retain和release创建的dispatch queue和其它dispatch对象。GCD 不支持垃圾收集模型来回收内存
添加任务到queue:
要执行一个任务,你需要将它添加到一个适当的dispatch queue,你可以单个或按组来添加,也可以同步或异步地执行一个任务,也。一旦进入到queue,queue会负责尽快地执行你的任务。一般可以用一个block来封装任务内容。
1.添加单个任务到queue
1> 异步添加任务
你可以异步或同步地添加一个任务到Queue,尽可能地使用 dispatch_async或dispatch_async_f函数异步地调度任务。因为添加任务到Queue中时,无法确定这些代码什么时候能够执 行。因此异步地添加block或函数,可以让你立即调度这些代码的执行,然后调用线程可以继续去做其它事情。特别是应用主线程一定要异步地 dispatch 任务,这样才能及时地响应用户事件
2> 同步添加任务
少数时候你可能希望同步地调度任务,以避免竞争条件或其它同步错误。 使用dispatch_sync和dispatch_sync_f函数同步地添加任务到Queue,这两个函数会阻塞当前调用线程,直到相应任务完成执行。注意:绝对不要在任务中调用 dispatch_sync或dispatch_sync_f函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做。
// 调用前,查看下当前线程
NSLog(@"当前调用线程:%@", [NSThread currentThread]);
// 创建一个串行queue
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", NULL);
dispatch_async(queue, ^{
NSLog(@"开启了一个异步任务,当前线程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"开启了一个同步任务,当前线程:%@", [NSThread currentThread]);
});
// 销毁队列
dispatch_release(queue);
执行结果:
2013-02-03 09:03:37.348 thread[6491:c07] 当前调用线程:<NSThread: 0x714fa80>{name = (null), num = 1}
2013-02-03 09:03:37.349 thread[6491:1e03] 开启了一个异步任务,当前线程:<NSThread: 0x74520a0>{name = (null), num = 3}
2013-02-03 09:03:37.350 thread[6491:c07] 开启了一个同步任务,当前线程:<NSThread: 0x714fa80>{name = (null), num = 1}
2.并发地执行循环迭代
如果你使用循环执行固定次数的迭代, 并发dispatch queue可能会提高性能。
1> 如果每次迭代执行的任务与其它迭代独立无关,而且循环迭代执行顺序也无关紧要的话,你可以调用dispatch_apply或 dispatch_apply_f函数来替换循环。这两个函数为每次循环迭代将指定的block或函数提交到queue。当dispatch到并发 queue时,就有可能同时执行多个循环迭代。用dispatch_apply或dispatch_apply_f时你可以指定串行或并发 queue。并发queue允许同时执行多个循环迭代,而串行queue就没太大必要使用了。
下面代码使用dispatch_apply替换了for循环,你传递的block必须包含一个size_t类型的参数,用来标识当前循环迭代。第一次迭代这个参数值为0,最后一次值为count - 1
// 获得全局并发queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count = 10;
dispatch_apply(count, queue, ^(size_t i) {
printf("%zd ", i);
});
// 销毁队列
dispatch_release(queue);
打印结果:
1 2 0 3 4 5 6 7 8 9
和普通for循环一样,dispatch_apply和 dispatch_apply_f函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程,主线程中调用这两个函数必须小心,可能会阻止事件 处理循环并无法响应用户事件。所以如果循环代码需要一定的时间执行,可以考虑在另一个线程中调用这两个函数。如果你传递的参数是串行queue,而且正是 执行当前代码的queue,就会产生死锁。
3.在主线程中执行任务
1> GCD提供一个特殊的dispatch queue,可以在应用的主线程中执行任务。只要应用主线程设置了run loop(由CFRunLoopRef类型或NSRunLoop对象管理),就会自动创建这个queue,并且最后会自动销毁。非Cocoa应用如果不显 式地设置run loop, 就必须显式地调用dispatch_main函数来显式地激活这个dispatch queue,否则虽然你可以添加任务到queue,但任务永远不会被执行。
2> 调用dispatch_get_main_queue函数获得应用主线程的dispatch queue,添加到这个queue的任务由主线程串行化执行
3> 代码实现,比如异步下载图片后,回到主线程显示图片
// 异步下载图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
4.任务中使用Objective-C对象
GCD支持Cocoa内存管理机制,因此可以在提交到queue的 block中自由地使用Objective-C对象。每个dispatch queue维护自己的autorelease pool确保释放autorelease对象,但是queue不保证这些对象实际释放的时间。如果应用消耗大量内存,并且创建大量autorelease 对象,你需要创建自己的autorelease pool,用来及时地释放不再使用的对象。
暂停和继续queue
我们可以使用dispatch_suspend函数暂 停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用 计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。挂起和继续是异步的,而且只在执行block之间(比 如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。
Dispatch Group的使用
例子:从网络上下载两张不同的图片,然后显示到不同的UIImageView上去,一般可以这样实现
// 根据url获取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
return [UIImage imageWithData:data];
}
- (void)downloadImages {
// 异步下载图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第一张图片
NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
UIImage *image1 = [self imageWithURLString:url1];
// 下载第二张图片
NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
UIImage *image2 = [self imageWithURLString:url2];
// 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView1.image = image1;
self.imageView2.image = image2;
});
});
}
虽然这种方案可以解决问题,但其实两张图片的下载过程并不需要按顺序执 行,并发执行它们可以提高执行速度。有个注意点就是必须等两张图片都下载完毕后才能回到主线程显示图片。Dispatch Group能够在这种情况下帮我们提升性能。下面先看看Dispatch Group的用处:
我们可以使用dispatch_group_async函数将多个任务关 联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。
dispath group优化:
// 根据url获取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
// 这里并没有自动释放UIImage对象
return [[UIImage alloc] initWithData:data];
}
- (void)downloadImages {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 异步下载图片
dispatch_async(queue, ^{
// 创建一个组
dispatch_group_t group = dispatch_group_create();
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
// 关联一个任务到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第一张图片
NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
image1 = [self imageWithURLString:url1];
});
// 关联一个任务到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第一张图片
NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
image2 = [self imageWithURLString:url2];
});
// 等待组中的任务执行完毕,回到主线程执行block回调
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
self.imageView1.image = image1;
self.imageView2.image = image2;
// 千万不要在异步线程中自动释放UIImage,因为当异步线程结束,异步线程的自动释放池也会被销毁,那么UIImage也会被销毁
// 在这里释放图片资源
[image1 release];
[image2 release];
});
// 释放group
dispatch_release(group);
});
}
dispatch_group_notify函数用来指定一个额外的block,该block将在group中所有任务完成后执行
相关推荐
iOS多线程编程指南是一份深入介绍iOS平台下多线程编程技术的文档。文档主要涵盖的技术点包括NSThread、NSOperation以及Grand Central Dispatch(GCD),这些技术是iOS开发者在开发应用程序时常用的技术。 首先,...
iOS多线程编程指南所涉及的知识点涵盖了多线程编程在iOS平台的应用,包括线程概念、线程管理、RunLoop对象、线程同步等核心概念以及具体实现技术。鉴于文档内容丰富,这里将详细解读并阐述上述知识点。 ### 1. 线程...
iOS多线程编程是iOS开发中的一个重要技能,对于提升应用程序的性能和用户体验至关重要。在iOS平台上,多线程编程可以通过多种技术实现,包括NSThread、Grand Central Dispatch(GCD)、Operation Objects等。本文档...
GCD是苹果引入的一种基于C的并发编程框架,它基于块(Block)语法,简化了多线程编程。GCD通过队列(Dispatch Queue)来管理并发,分为串行队列和并行队列。串行队列保证了操作的顺序执行,而并行队列则可以并发执行...
iOS多线程编程是iOS开发中的一个重要方面,它允许应用程序同时执行多个代码路径,以实现更高效的计算和响应。本文档是由Apple公司出品的官方多线程编程指南的翻译版本,由謝業蘭【老狼】翻译,并获得了有米移动广告...
本指南旨在为iOS开发者提供一个多线程编程的全面介绍。多线程是现代操作系统中的一个重要特性,它允许应用程序同时执行多个任务,从而提高效率并充分利用计算资源。在iOS开发中,合理利用多线程不仅能够提升应用性能...
“关于多线程编程”介绍了多线程的概念和它们在应用设计里面的角色。 “线程管理”提供了关于 Mac OS X 上面线程技术的相关信息,并且教你如果 使用它们。 “Run Loops” 提供有关如何管理在辅助线程中的...
iOS中有以下3种多线程编程方法: NSThread Grand Centeral Dispatch(GCD) NSOperation和NSOperationQueue 1.NSThread 这是最轻量级的多线程的方法,使用起来最直观的多线程编程方法。但是因为需要自己管理线程的...
本文将作为iOS多线程编程指南的第一部分,深入探讨多线程的基本概念、常用技术以及最佳实践。 1. **多线程基础** 多线程是指在一个应用程序中同时执行多个独立的执行流,每个执行流被称为一个线程。在iOS中,多...
总之,“iOS多线程Demo”是一个很好的学习资源,它涵盖了iOS多线程开发的基础和实践,包括NSOperationQueue、GCD和NSThread的使用,以及多线程下的UI更新和线程安全。通过这个Demo,开发者可以更深入地理解多线程...
本文将深入探讨iOS中的多线程编程,包括GCD(Grand Central Dispatch)、NSOperation、NSThread的使用,以及异步和同步下载、Block的运用,以及ASIHttpRequest类库的使用。 首先,GCD是Apple推出的一种多线程解决...
在iOS开发中,多线程编程是一个非常重要的概念,它允许应用程序同时执行多个操作,而不会相互干扰,从而提高程序的性能和响应能力。另外,RunLoop是iOS中一个非常重要的概念,它是事件接收循环,用于处理异步事件,...
### iOS多线程编程知识点详解 #### 一、多线程编程概述 多线程编程是一种软件技术,它允许在单个应用程序内并行执行多个代码路径。这有助于提高应用程序的响应性和整体性能,尤其是在利用现代多核处理器的能力时。...
以下是对标题“iOS并发编程指南与多线程编程指南合集”以及描述中提及知识点的详细解读: 1. **并发编程**:并发编程是指在一个时间段内,系统可以执行多个任务,而这些任务可以是同时进行的,也可以是交替进行。在...
### iOS多线程编程指南知识点概述 #### 一、多线程编程介绍 - **多线程的概念**:多线程是指在一个程序中同时运行多个线程的能力,这些线程可以独立执行不同的任务或者并行处理同一任务的不同部分。通过多线程,...