转载

iOS GCD NSOperation NSThread等多线程各种举例详解

2年多的iOS之路匆匆而过,期间也拜读来不少大神的博客,近来突然为自己一直做伸手党感到羞耻,是时候回馈社会。回想当年自己还是小白的时候,照着一些iOS多线程教程学,也只是照抄,只知其然、不知其所以然。现写一篇详细教程奉献给广大读者。废话就不多说了,直接上干货。如下图列举了很多多线程的知识点,每个知识点都写有对应的详细例子,并对运行结果进行分析,绝对拿实践结果来说话。如果各位道友发现错误之处还请指正。附上 demo下载地址

iOS中几种多线程的比较

GCD:是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理。

NSOperation: 是苹果公司对GCD的封装,以面向对象的方式封装了需要执行的操作,不必关心线程管理、同步等问题。NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。

NSThread: 是三种方法里面相对轻量级的,但需要管理线程的生命周期、同步、加锁问题,这会导致一定的性能开销。

一、iOS多线程之GCD篇

1.串行队列异步执行( 不阻塞当前线程,且都是按顺序执行,前一个完成,后一个才开始, 异步操作会另开辟线程

举个例子

//串行队列异步执行 - (IBAction)serialAsync:(id)sender {  // 创建一个串行队列  //其中第一个参数是标识符。第二个参数传DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列,传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。  dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);  //通过一个for循环将10个很耗时的异步任务加到串行队列  for (NSInteger n = 0; n < 3; n++) {   dispatch_async(myQueue, ^{    //模拟一个很耗时的操作    for (NSInteger i = 0; i < 500000000; i++) {     if (i == 0) {      NSLog(@"串行异步任务%ld -> 开始%@",n,[NSThread currentThread]);     }     if (i == 499999999) {      NSLog(@"串行异步任务%ld -> 完成",(long)n);     }    }   });  }  NSLog(@"阻塞我没有?当前线程%@",[NSThread currentThread]); } 

打印结果:

分析结论:“阻塞我没?”这句在第一行就打印了,说明串行异步操作不阻塞 当前 线程,且都是按顺序执行,前一个完成,后一个才开始。异步操作会另开辟线程。

2.串行队列同步执行(会阻塞当前线程, 且都是按顺序执行,前一个完成,后一个才开始,同步操作不会开辟线程

举个例子

//串行队列同步执行 - (IBAction)serialSync:(id)sender {  // 创建一个串行队列  //其中第一个参数是标识符。第二个参数传DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列,传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。  dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);  //通过一个for循环将10个很耗时的同步任务加到串行队列  for (NSInteger n = 0; n < 3; n++) {   dispatch_sync(myQueue, ^{    //模拟一个很耗时的操作    for (NSInteger i = 0; i < 500000000; i++) {     if (i == 0) {      NSLog(@"串行同步任务%ld -> 开始%@",n,[NSThread currentThread]);     }     if (i == 499999999) {      NSLog(@"串行同步任务%ld -> 完成",(long)n);     }    }   });  }  NSLog(@"阻塞我没有?当前线程%@",[NSThread currentThread]); } 

打印结果:

分析结论:“阻塞我没?”这句到最后才执行,说明 串行队列同步 任务会阻塞 当前 线程, 且都是按顺序执行,前一个完成,后一个才开始。从打印结果看,全在主线程执行的,所以 同步操作不会开辟线程。

3.并行队列异步执行(不阻塞当前线程,多个任务同时开始, 异步操作会另开辟线程

举个例子

//并行队列异步执行 - (IBAction)concurrentAsync:(id)sender {  // 创建一个并行队列  //其中第一个参数是标识符。第二个参数传DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列,传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。  dispatch_queue_t myQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);  //通过一个for循环将10个很耗时的异步任务加到并行队列  for (NSInteger n = 0; n < 3; n++) {   dispatch_async(myQueue, ^{    //模拟一个很耗时的操作    for (NSInteger i = 0; i < 500000000; i++) {     if (i == 0) {      NSLog(@"并行异步任务%ld -> 开始%@",n,[NSThread currentThread]);     }     if (i == 499999999) {      NSLog(@"并行异步任务%ld -> 完成",(long)n);     }    }   });  }  NSLog(@"阻塞我没有?当前线程%@",[NSThread currentThread]); } 

打印结果:

分析结论:不阻塞 当前 线程,红圈的时间显示,并行队列异步多个任务同时开始。 异步操作会另开辟线程。

4.并行队列同步执行 (会阻塞当前线程, 且都是按顺序执行,前一个完成,后一个才开始。)

举个例子

//并行队列同步执行 - (IBAction)concurrentSync:(id)sender {  // 创建一个并行队列  //其中第一个参数是标识符。第二个参数传DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列,传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。  dispatch_queue_t myQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);  //通过一个for循环将10个很耗时的同步任务加到并行队列  for (NSInteger n = 0; n < 3; n++) {   dispatch_sync(myQueue, ^{    //模拟一个很耗时的操作    for (NSInteger i = 0; i < 500000000; i++) {     if (i == 0) {      NSLog(@"并行同步任务%ld -> 开始%@",(long)n,[NSThread currentThread]);     }     if (i == 499999999) {      NSLog(@"并行同步任务%ld -> 完成",(long)n);     }    }   });  }  NSLog(@"阻塞我没有?当前线程%@",[NSThread currentThread]); } 

打印结果:

分析结论:并行队列同步执行会阻塞 当前 线程。 且都是按顺序执行,前一个完成,后一个才开始。(注意:此处的按顺序一个一个执行和串行队列不同,此处是因为同步操作会阻塞当前线程,从打印结果看这三个操作都在主线程,当第一个操作完成,线程才解除阻塞,然后执行下一个,于是一个一个执行) 从打印结果看,全在主线程执行的,所以同步任务不会开辟线程。

5.关于同步异步、串行并行的小结

从前4个例子小一下结论:a.串行队列不管是异步还是同步,都是按顺序一个一个执行的; b.同步操作不管是并行队列还是串行队列,都是按顺序一个一个执行的(但是同步操作并行和串行按顺序一个一个执行的原因是不同的,见第GCD4个例子的分析)。 c.同步操作会开辟线程,异步操作不开辟线程;

6.队列组(当组里所有任务都执行完了,队列组会通过一个方法通知我们,这些任务都是异步并行执行的,不阻塞当前线程)

举个例子

//队列组 - (IBAction)queueGroup:(id)sender {  //创建队列组  dispatch_group_t group = dispatch_group_create();  //创建全局并行队列  dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);   //创建自定义并行队列  dispatch_queue_t myQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);   //创建主队列  dispatch_queue_t mainQueue = dispatch_get_main_queue();  //队列组执行全局队列  dispatch_group_async(group, globalQueue, ^{   for (NSInteger i = 0; i < 3; i++) {    NSLog(@"全局并行队列%ld",(long)i);   }  });  //队列组执行自定义并行队列  dispatch_group_async(group, myQueue, ^{   for (NSInteger i = 0; i < 4; i++) {    NSLog(@"自定义并行队列%ld",(long)i);   }  });  //队列组执行主队列  dispatch_group_async(group, mainQueue, ^{   for (NSInteger i = 0; i < 5; i++) {    NSLog(@"主队列%ld",(long)i);   }  });  dispatch_group_notify(group, dispatch_get_main_queue(), ^{   NSLog(@"完成 - %@", [NSThread currentThread]);  });  NSLog(@"阻塞我没有?当前线程%@",[NSThread currentThread]); } 

打印结果:

分析结论: 当组里所有任务都执行完了,队列组会通过一个方法通知我们,这些任务都是异步并行执行的,不阻塞当前线程。

7.GCD延时

举个例子

//GCD延时 - (IBAction)dispatchDelay:(id)sender {  //非阻塞的执行方式  double delayInSeconds = 2.0;  dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);  dispatch_after(popTime, dispatch_get_main_queue(), ^(void){   NSLog(@"延时了2秒");  });  //既然谈到了延时延时,那就和其他几种延时比较下  //1.此方式要求必须在主线程中执行,否则无效。是一种非阻塞的执行方式,暂时未找到取消执行的方法。 // [self performSelector:@selector(delayMethod) withObject:nil afterDelay:1.0f];  //2.此方式要求必须在主线程中执行,否则无效。是一种非阻塞的执行方式,可以通过NSTimer类的- (void)invalidate;取消执行。 // [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];  //3. 此方式在主线程和子线程中均可执行。是一种阻塞的执行方式,建方放到子线程中,以免卡住界面,没有找到取消执行的方法。 // [NSThread sleepForTimeInterval:4.0f]; // [self delayMethod];    NSLog(@"阻塞我没有?当前线程%@",[NSThread currentThread]); } 

打印结果:

分析结论:GCD延时不会阻塞当前线程,延时操作在子线程执行

8.dispatch_barrier_async(前面任务完成再执行后面任务)

举个例子

- (IBAction)wait:(id)sender {  dispatch_queue_t myQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);  //前面加2个任务  for (NSInteger n = 0; n < 2; n++) {   dispatch_async(myQueue, ^{    //模拟一个很耗时的操作    for (NSInteger i = 0; i < 500000000; i++) {     if (i == 0) {      NSLog(@"前面任务%ld -> 开始",(long)n);     }     if (i == 499999999) {      NSLog(@"前面任务%ld -> 完成",(long)n);     }    }   });  }  dispatch_barrier_async(myQueue, ^(){   NSLog(@"dispatch-barrier");  });  //后面加2个任务  for (NSInteger n = 0; n < 2; n++) {   dispatch_async(myQueue, ^{    //模拟一个很耗时的操作    for (NSInteger i = 0; i < 500000000; i++) {     if (i == 0) {      NSLog(@"后面任务%ld -> 开始",(long)n);     }     if (i == 499999999) {      NSLog(@"后面面任务%ld -> 完成",(long)n);     }    }   });  } } 

打印结果:

分析结论: dispatch_barrier_async 方法会阻塞这个 queue(注意是阻塞 queue ,而不是阻塞当前线程),一直等到这个 queue 中排在它前面的任务都并行执行完成后才会开始执行自己,自己执行完毕后,再会取消阻塞,使这个 queue 中排在它后面的任务继续并行执行。(注意:如果你传入的是其他的 queue, 那么它就和 dispatch_async 一样了。)

9.死锁主线程

举个例子

//死锁主线程 - (IBAction)deadlock1:(id)sender {     NSLog(@"之前线程 - %@", [NSThread currentThread]);     dispatch_sync(dispatch_get_main_queue(), ^{         NSLog(@"同步操作的线程 - %@", [NSThread currentThread]);     });     NSLog(@"之后线程 - %@", [NSThread currentThread]); }

打印结果:

分析结论:只打印了一句,发现后面两个没打印出来,说明已经死锁了。我们来一步一步分析,打印完第一句后,同步操作dispatch_sync 立即阻塞当前的主线程,然后把 Block 中的任务放到 main_queue 中,然后 main_queue 中的任务会被取出来放到主线程中执行,但主线程这个时候已经被阻塞了,所以 Block 中的任务就不能完成,它不完成,dispatch_sync 就会一直阻塞主线程,这就是死锁现象。导致主线程一直卡死。

10.死锁子线程

举个例子

//死锁子线程 - (IBAction)deadlock2:(id)sender {  dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);  NSLog(@"之前线程 - %@", [NSThread currentThread]);  dispatch_async(myQueue, ^{   NSLog(@"同步操作之前线程 - %@", [NSThread currentThread]);   dispatch_sync(myQueue, ^{    NSLog(@"同步操作时线程 - %@", [NSThread currentThread]);   });   NSLog(@"同步操作之后线程 - %@", [NSThread currentThread]);  });  NSLog(@"之后线程 - %@", [NSThread currentThread]); } 

打印结果:

分析结论:打印第一句之后就是一个异步操作,异步操作会另外开辟线程,2个线程分别打印了最后一句和第二句,当执行到同步操作 dispatch_sync 时立马阻塞当前线程,一直等到 sync 里的任务执行完才会继续往下。于是 sync 就把自己 Block 中的任务放到 queue 中,可 queue 是一个串行队列,一次执行一个任务,所以 sync 的 Block 必须等到前一个任务执行完毕,可是 queue 正在执行的任务就是被 sync 阻塞了的那个。于是又发生了死锁。所以 sync 所在的线程被卡死了。剩下的两句代码自然不会打印。

二、多线程之NSOperation篇

见我的另外一个博客 iOS 多线程之NSOperation篇举例详解

三、多线程之NSThread篇

见我的另外一个博客 ios 多线程之NSThread篇举例详解

正文到此结束
Loading...