今天没什么事做就对iOS的多线程做一次总结,纯属个人看法,初学者可以参考下。对于多线程我想无论是面试还是实际开发大家都不会陌生;
严格意义来讲iOS多线程算是4种:
- PThreads
- NSThread
- GCD
- NSOPeration/NSOperationQueue;
PThreads
这个是基于C语音定义的POSIX Threads(简称PThreads)标准线程,但是对于iOS开发者使用率很低,原因是有二:
- 调用的都是C函数和语法,对于习惯OC或者Swift开发的我们来说,很蛋疼;
- 线程的生命周期需要我们手动管理。
这个一般用在实现操作系统和PHP里(Unix、Linux、Mac OS X等)
- (void)Pthreads {
pthread_t thread;
//创建一个线程并执行 start是一个函数指针
pthread_create(&thread, NULL, start, NULL);
}
void *start(void *data) {
//这里执行线程操作
NSLog(@"%@",[NSThread currentThread]);
return NULL;
}
有兴趣的同学可以看看。
NSThread
这个是苹果自己开发,NSThread是轻量级多线程生命周期需要手动管理因为扩展性不高,有很多局限性只能偶尔用用 。
上代码:
- (void)NSThread {
//先创建后执行
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil];
[thread start];
//创建并执行
[NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil];
//perform里唯一一个多线程方法 如果线程方法要刷新UI可以使用
//- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
[self performSelectorInBackground:@selector(threadRun) withObject:nil];
}
- (void)threadRun {
NSLog(@"threadRun:%@",[NSThread currentThread]);
}
GCD
下面是重点,Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。GCD能充分利用手机性能,让运行效率更快,也是苹果主推的多线程开发方式。
在GCD中,有两个概念很重要就是任务和队列,任务分同步执行和异步执行两种方式,队列也分串行队列和并行队列,下面我们来看它们的创建和使用:
//1.初始化/获取系统队列
//获取主队列 ()一般只负责刷新UI,耗时任务都交给其他线程
dispatch_queue_t queue = dispatch_get_main_queue();
//创建自己的队列
//串行队列
dispatch_queue_t queue1 = dispatch_queue_create("队列标识符", nil);
dispatch_queue_t queue2 = dispatch_queue_create("队列标识符", DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_t queue3 = dispatch_queue_create("队列标识符", DISPATCH_QUEUE_CONCURRENT);
//获取全局并行队列 只要是并行任务一般都加入到这个队列。这是系统提供的一个并发队列。
dispatch_queue_t queue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.任务的创建和执行方法(同步和异步)
//同步任务
dispatch_sync(queue3, ^{
NSLog(@"GCD-同步%@",[NSThread currentThread]);
sleep(5);
NSLog(@"GCD-同步—后%@",[NSThread currentThread]);
});
//异步任务
dispatch_async(queue3, ^{
NSLog(@"GCD-异步%@",[NSThread currentThread]);
});
从上面的代码可以看出队列还主队列(系统队列)还有自定义队列(多线程),自定义队列创建的时候两个参数第一个是标识符可以为空;第二个为空或者DISPATCH_QUEUE_SERIAL为串行队列,DISPATCH_QUEUE_CONCURRENT为并行队列。还有一个全局并行队列一般的并发任务都会加入这个队列,其实无论是并行队列还是串行队列都是以先进先出原则,区别在于并行队列是取一个开一个线程执行,在取下一个,中间取的时间很快;串行队列是取出一个执行完成以后再取出下一个,一个一个的执行。需要注意的是GCD会根据系统资源限制并行数量。
//3.队列组 队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。
//3.1创建队列组
dispatch_group_t group = dispatch_group_create();
//3.2创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.3多次使用队列组的方法执行任务,只有异步方法
//执行3此循环
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"group-01:%@",[NSThread currentThread]);
}
});
//主队列执行8此循环
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (int i = 0; i < 8; i++) {
NSLog(@"group-02:%@",[NSThread currentThread]);
}
});
//执行5次循环
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"group-03:%@",[NSThread currentThread]);
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成-04:%@",[NSThread currentThread]);
});
NSOperation/NSOperationQueue
NSOperation 是苹果公司对 GCD的封装,完全面向对象,所以使用起来更好理解。大家可以看到 NSOperation和 NSOperationQueue分别对应 GCD的任务和队列
NSOperation只是一个抽象类,所以不能封装任务。但它有 2个子类用于封装任务。
分别是:NSInvocationOperation和 NSBlockOperation。
创建一个 Operation后,需要调用 start方法来启动任务,它会默认在当前队列同步执行。
当然你也可以在中途取消一个任务,只需要调用其 cancel方法即可
//1.创建
//NSInvocationOperation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadRun) object:nil];
//开始执行
[operation start];
//NSBlockOperation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation:%@",[NSThread currentThread]);
}];
/*
NSBlockOperation 还有一个方法:addExecutionBlock: ,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务会并发执行,它会在主线程和其它的多个线程 执行这些任务
*/
for (int i = 0; i < 5; i++) {
[blockOperation addExecutionBlock:^{
NSLog(@"blockOperation-第%d次:%@",i,[NSThread currentThread]);
}];
}
[blockOperation start];
/*
看过上面的内容就知道,我们可以调用一个 NSOperation 对象的 start() 方法来启动这个任务,但是这样做他们默认是 同步执行 的。就算是 addExecutionBlock 方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。这是就要用到队列 NSOperationQueue 了。而且,按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法
*/
//NSOperationQueue 获取主队列(串行)
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//创建其他队列
NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
//这里可以添加多个operation
[queue1 addOperation:operation1];
NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3个任务:A:从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:
//1.任务一:下载图片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载图片:%@",[NSThread currentThread]);
}];
//2.任务二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"打水印:%@",[NSThread currentThread]);
}];
//3.任务三:上传图片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"上传图片:%@",[NSThread currentThread]);
}];
//4.设置依赖 注意:不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。
[operation2 addDependency:operation1]; //任务二依赖任务一
[operation3 addDependency:operation2]; //任务三依赖任务二
//5.创建队列并加入
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:[NSArray arrayWithObjects:operation1,operation2,operation3, nil] waitUntilFinished:NO];
以上只是一些基本使用方法,想了解更多可以去网上找或者去看官方文档;
使用多线程我们就需要考虑数据同步的问题
其他
互斥锁:给需要同步的代码块加一个互斥锁,就可以保证每次只有一个线程访问此代码块。
@synchronized(self) {
//需要执行的代码块
}
我们也可以在队列的结尾加一个串行队列,来达到数据同步的效果方法有很多。