GVKun编程网logo

多线程(NSOperation和NSOperationQueue)(多线程 segmentation fault)

7

对于多线程感兴趣的读者,本文将提供您所需要的所有信息,我们将详细讲解NSOperation和NSOperationQueue,并且为您提供关于10.2NSOperation/NSOperationQu

对于多线程感兴趣的读者,本文将提供您所需要的所有信息,我们将详细讲解NSOperation和NSOperationQueue,并且为您提供关于10.2 NSOperation/NSOperationQueue:提供了一些在GCD中不容易实现的特性,如:限制最大并发数量,操作之间的依赖关系.、Cocoa Tutorial: NSOperation and NSOperationQueue、Cocoa:NSOperation和NSOperationQueue、Cocoa深入学习:NSOperationQueue、NSRunLoop和线程安全的宝贵知识。

本文目录一览:

多线程(NSOperation和NSOperationQueue)(多线程 segmentation fault)

多线程(NSOperation和NSOperationQueue)(多线程 segmentation fault)

多线程(NSOperation和NSOperationQueue)

在网络应用程序中,经常要使用多任务处理来提高应用程序的性能,即在同一时间,有多个处理同时进行。例如,同时进行多个文件下载,同时进行多个HTTP 请求等。这一般都
是通过多线程完成的。另外,多线程编程也是为了防止主线程堵塞,增加运行效率的方法。比如,如果主线程从网上下载一个很大的图片,那么,给用户的感觉是整个应用程序死掉了。所以,可以把这个下载操作放在一个线程中,在主线程中调用这个线程让它在后台处理,主线程序可以显示一些其他信息,比如显示一些“正在装载”等文字信息。

在Cocoa中,NSOperation类提供了一个优秀的多线程编程方法。很多编程语言都支持多线程处理应用程序,但是多线程程序往往一旦出错就会很难处理。庆幸的是,苹果公司在这方面做了很多改进,例如在NSThread 上新增了很多方法,还新增了两个类NSOperation 和NSOperationQueue,从而让多线程处理变得更加容易。

在多线程中,可以同时进行多个操作。NSOperation 对象就是一个操作,比如,装载网页内容的一个操作。在Objective-C 上,一个具体的操作(比如网页装载)是一个继承NSOperation 的类。在这个类中,至少需要重写一个-(void)main 方法。线程(NSOperation)自动调用main 方法,main 方法就是线程要执行的具体操作。在下面的例子中,PageLoadOperation 继承了NSOperation,并实现了main 方法。一般而言,可以利用其初始化方法来传入所需要的参数和对象,比如PageLoadOperation的initWithURL:方法用来设置要装载的网址。

使用NSOperation 的最简单方法就是将其放入NSOperationQueue 中,NSOperationQueue是存放多个操作的队列。一旦一个NSOperation 对象被加入NSOperationQueue,该队列就会启动并开始处理它(即调用它的main方法),当操作完成后,队列就会释放它。

下面创建一个Cocoa Application例子来演示使用NSOperation和NSOperationQueue完成多线程处理。

应用代理类AppDelegate.h的代码如下:

 
 
  1. #import <Cocoa/Cocoa.h> 
  2. @interface AppDelegate : NSObject {  
  3. NSOperationQueue *queue; //线程队列  
  4. }  
  5. + (id)shared;  
  6. - (void)pageLoaded:(NSXMLDocument*)document;  
  7. @end  
  8.  
  9. AppDelegate.m的代码如下:  
  10.  
  11. #import "AppDelegate.h"  
  12. #import "PageLoadOperation.h"  
  13. @implementation AppDelegate  
  14. static AppDelegate *shared;  
  15. static NSArray *urlArray;  
  16. - (id)init  
  17. {  
  18. if (shared) {  
  19. [self autorelease];  
  20. return shared;  
  21. }  
  22. if (![super init]) return nil;  
  23. //设置要访问的网址  
  24. NSMutableArray *array = [[NSMutableArray alloc] init];  
  25. [array addobject:@"http://www.xinlaoshi.com"];  
  26. [array addobject:@"http://www.yunwenjian.com"];  
  27. [array addobject:@"http://www.108fang.com"];  
  28. [array addobject:@"http://www.baidu.com"];  
  29. urlArray = array;  
  30. //[queue setMaxConcurrentOperationCount:2];  
  31. queue = [[NSOperationQueue alloc] init];  
  32. shared = self;  
  33. return self;  
  34. }  
  35. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification  
  36. { //把各个操作添加到队列中  
  37. for (Nsstring *urlString in urlArray) {  
  38. NSURL *url = [NSURL URLWithString:urlString];  
  39. PageLoadOperation *plo = [[PageLoadOperation alloc]  
  40. initWithURL:url];  
  41. [queue addOperation:plo];  
  42. [plo release];  
  43. }  
  44. }  
  45. - (void)dealloc  
  46. {  
  47. [queue release], queue = nil;  
  48. [super dealloc];  
  49. }  
  50. + (id)shared;  
  51. {  
  52. if (!shared) {  
  53. [[AppDelegate alloc] init];  
  54. }  
  55. return shared;  
  56. }  
  57. //用于打印网页内容  
  58. - (void)pageLoaded:(NSXMLDocument*)document;  
  59. {  
  60. NSLog(@"xml 文档是:%@", document);  
  61. }  
  62. @end 

线程操作类PageLoadOperation.h的代码如下:

@interface PageLoadOperation : NSOperation {  
  
  
  • //需要使用多线程的类要继承NSOperation  
  • NSURL *targetURL;  
  • }  
  • @property(retain) NSURL *targetURL;  
  • - (id)initWithURL:(NSURL*)url;  
  • @end  
  •  
  • PageLoadOperation.m的代码如下:  
  •  
  • #import "PageLoadOperation.h"  
  • #import "AppDelegate.h"  
  • @implementation PageLoadOperation  
  • @synthesize targetURL;  
  • //获取要访问的网址  
  • - (id)initWithURL:(NSURL*)url;  
  • {  
  • if (![super init]) return nil;  
  • [self setTargetURL:url];  
  • return self;  
  • }  
  • - (void)dealloc {  
  • [targetURL release], targetURL = nil;  
  • [super dealloc];  
  • }  
  • //线程完成的操作。本例访问网址,并把该网址的内容放到一个NSXMLDocument 对象上  
  • - (void)main {  
  • //将targetURL 的值返回为webpageString 对象  
  • Nsstring *webpageString = [[[Nsstring alloc]initWithContentsOfURL:  
  • [self targetURL]] autorelease];  
  • NSError *error = nil;  
  • //访问网址,并把该网址的网页内容放到一个NSXMLDocument 对象上  
  • NSXMLDocument *document = [[NSXMLDocument alloc] initWithXMLString:  
  • webpageString options:NSXMLDocumentTidyHTML error:&error];  
  • if (!document) {  
  • //当document 为nil 的时候打印错误信息  
  • NSLog(@"错误信息:(%@): %@", [[self targetURL] absoluteString], 
  • error);  
  • return;  
  • }  
  • //拿到AppDelegate 对象并且调用主线程上的打印方法  
  • [[AppDelegate shared]  
  • performSelectorOnMainThread:@selector(pageLoaded:)  
  • withObject:document  
  • waitUntilDone:NO];  
  • }  
  • 【程序结果】

  • xml 文档是:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0  
  • Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-  
  • transitional.dtd">html xmlns="
    http://www.w3.org/1999/xhtml"
    headMeta 
  • http-equiv="Content-Type" content="
    text/html; charset=utf-8"
     />title>新老  
  • 师-学网上课程 交圈内朋友</> 
  • ....  
  • xml 文档是:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0  
  • html>云文件<htmlxmlnshtmlxmlns="
    http://www.w3.org/1999/xhtml"
    http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  • >108 方手机应用平台....  
  • xml 文档是:<!doctype htmlMeta http-equiv="Content-Type" 
  • content="text/html;charset=gb2312">百度一下,你就知道 > 
  • 下面分析一下这段程序执行过程,由于是一个Cocoa Application 应用程序,所以系统会先执行委托类AppDelegate 下的初始化方法- (id)init 进行一些初始化设置。在这个委托方法上,首先判断shared 是否已经初始化过。若初始化过,则直接返回结果;若是应用程序第一次调用初始化方法,则就初始化urlArray 数组,并将要用线程访问的各个网站地址装入其中,随后初始化shred 和NSOperationQueue 的对象queue。在应用装载结束以后,系统会调用另一个委托方法applicationDidFinishLaunching:方法,在这个方法中,我们遍历存入urlArray数组中的网站地址字符串,将其依次转换为NSURL对象。与此同时,创建同等数量的PageLoadOperation 对象,并将转换好的NSURL 对象设置为各个PageLoadOperation 对象的属性targetURL。我们还将初始化好的PageLoadOperation对象加入到queue队列上。

    在队列中每加入一个线程操作后,队列都会为其分配一个NSThread 来启动它,并运行操作的main方法。一旦操作完成,线程就会报告给队列以让队列释放该操作。

    在PageLoadOperation 类的main 方法上,根据前面设置好的targetURL 属性值,将该网址转换为字符串对象webpageString,并且加入自动释放池。利用转换好的webpageString 对象初始化NSXMLDocument对象,并访问这个网站,把内容放在NSXMLDocument对象上。如果在加载网站内容过程中发生错误,就会打印错误信息。如果没有错误,就调用主线程的pageLoaded 方法,从而打印网页内容到控制台上,然后任务结束。队列会在main 方法结束后自动释放该线程。

    这个例子展示了NSOperation 和NSOperationQueue 最基本的使用。实例中的大部分代码与NSOperation 和NSOperationQueue 的设定和使用无关,都是一些业务实现代码。NSOperation本身所需要的代码非常少,但是通过这少量的代码就可以在应用中轻松地使用多线程,从而为用户提供更好的并发性能。另外,在init方法中,有一句代码:

  • //[queue setMaxConcurrentOperationCount:2]; 
  • 这是用来设置线程的个数。如果去掉上面的注释,那么,线程队列就被限制为只能同时运行两个操作,剩余的操作就需要等待这两个操作中的任一个完成后才有可能被运行。

  • 10.2 NSOperation/NSOperationQueue:提供了一些在GCD中不容易实现的特性,如:限制最大并发数量,操作之间的依赖关系.

    10.2 NSOperation/NSOperationQueue:提供了一些在GCD中不容易实现的特性,如:限制最大并发数量,操作之间的依赖关系.


    <pre name="code">//MyOperation.h
     
    
    #import <Foundation/Foundation.h>
    
    @interface MyOperation : NSOperation
    @property(copy,atomic)Nsstring*name;
    @end
    

    #import "MyOperation.h"
    
    @implementation MyOperation
    -(void)main
    {
        
        NSLog(@" %@ 线程开始执行任务",self.name);
     NSLog(@"----------------");
        for (int i=0; i<20; i++) {
            NSLog(@"    %d    ",i);
        }
        NSLog(@"%@任务执行完毕",self.name);
    }
    @end
    


    //  ProgressOperation.h
    //  10  OperationQueue
    //
    //  Created by Tracy on 15/5/29.
    //  copyright (c) 2015年 Tracy. All rights reserved.
    //
    
    #import <Foundation/Foundation.h>
    #import "ViewController.h"
    @interface ProgressOperation : NSOperation
    @property(strong)ViewController *delegate;
    @end

    // <span>//</span><span>ViewController.h</span>
    // ProgressOperation.m
    // 10 OperationQueue
    //
    // Created by Tracy on 15/5/29.
    // copyright (c) 2015年 Tracy. All rights reserved.
    //
    
    
    #import "ProgressOperation.h"
    
    
    @implementation ProgressOperation
    -(void)main
    {
      for (int i=0; i<50; i++) {
    
    
    //  主线程中更新UI
      [self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:YES];
        }
    }
    -(void)updateUI
    {
         [NSThread sleepForTimeInterval:0.02f];
      self.delegate.progressView.progress+=0.01;
       // self.delegate.progressView.progress+=0.01;
     
    }
    @end

    <span>//</span><span>ViewController.h</span>
    #import <UIKit/UIKit.h>
    
    @interface ViewController : UIViewController
    @property (strong,nonatomic) IBOutlet UIProgressView *progressView;
    
    @end
    
     
    
    #import "ViewController.h"
    #import "MyOperation.h"
    #import "ProgressOperation.h"
    #import "backAccount.h"
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view,typically from a nib.
    }
    - (IBAction)threeOperationQueue:(UIButton *)sender
    {
        MyOperation*op1=[[MyOperation alloc]init];
        op1.name=@"1";
        MyOperation *op2=[[MyOperation alloc]init];
        op2.name=@"2";
        MyOperation *op3=[[MyOperation alloc]init];
        op3.name=@"3";
        NSOperationQueue*queue=[[NSOperationQueue alloc]init];
        
    //    queue.maxConcurrentOperationCount=1;
    //   
    ////    改变优先级需要在放入队列前完成
    //    [op3 setQueuePriority:NSOperationQueuePriorityVeryHigh];
    //    [op1 setQueuePriority:NSOperationQueuePriorityLow];
    //    
    ////    操作对象1依赖操作对象3,3完成后才执行1
    //    [op1 addDependency:op3];
        
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
    }
    - (IBAction)updateProgressvew:(UIButton *)sender
    {
        ProgressOperation *po=[[ProgressOperation alloc]init];
        po.delegate=self;
        
        NSOperationQueue *queue=[[NSOperationQueue alloc]init];
        [queue addOperation:po];
    
        
    }
    - (IBAction)fetchMoney:(UIButton *)sender
    {
        backAccount *hus=[[backAccount alloc]init];
        hus.name=@"hus";
        
        backAccount*wife=[[backAccount alloc]init];
        wife.name=@"wife";
        
        NSOperationQueue *queue=[[NSOperationQueue alloc ]init];
        //    指定操作队列最大并行操作数量完成同步,此处设为1,相当于同步。避免取钱,买票等异步引起的问题。这行不能放在addoperation之后。
        queue.maxConcurrentOperationCount=1;
        
        [queue addOperation:hus];
        [queue addOperation:wife];
        
        //NSOperation对象是一个single-shot(一次性)对象,(此处为hus,wife)当它执行完一遍后,便不能再次使用,下面代码出错!
        /*NSOperationQueue *operationQueue2 = [[NSOperationQueue alloc]init];
         [operationQueue2 addOperation:husband];
         [operationQueue2 addOperation:wife];*/
    
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // dispose of any resources that can be recreated.
    }
    
    @end
    









    // ViewController.h

    Cocoa Tutorial: NSOperation and NSOperationQueue

    Cocoa Tutorial: NSOperation and NSOperationQueue

     

    Threading is hard in any language. And what is worse,when it goes wrong,it usually goes wrong in a very bad way. Because of this,programmers either avoid threading completely (and refer to it as the spawn of the devil) or spend a LOT of time making sure that everything is perfect.

    Fortunately,Apple has made a lot of progress in OS X 10.5 Leopard. NSThread itself has received a number of very useful new methods that make threading easier. In addition,they have introduced two new objects: NSOperation and NSOperationQueue.

    In this Tutorial I will walk through a basic example that shows how to use these new Objects and how they make multi-threading your application nearly trivial.

    You can get the example project here: Async Downloader Example Project

    In this tutorial,I will demonstrate one way in which to use NSOperation and NSOperationQueue to handle tasks that are best performed on background threads. The intent of this tutorial is to demonstrate a basic use of these classes and is not intentioned to be the only way to use them.

    If you are familiar with Java,or one of its variants,the NSOperation object is very similar to the java.lang.Runnable interface. Like,in Java’s Runnable interface,the NSOperation object is designed to be extended. Also like Java’s Runnable,there is a minimum of one method to override. For NSOperation that method is -(void)main. One of the easiest ways to use an NSOperation is to load it into an NSOperationQueue. As soon as the operation is loaded into the queue,the queue will kick it off and begin processing. As soon as the operation is complete the queue will release it.

    NSOperation Example

    In this example,I have written an NSOperation that fetches a webpage as a string,parses it into an NSXMLDocument and then passes that NSXMLDocument back to the main thread in the application before completing.

    PageLoadOperation.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    #import <Cocoa/Cocoa.h>
     
     
    @interface PageLoadOperation : NSOperation {
        NSURL *targetURL;
    }
     
    @property(retain) NSURL *targetURL;
     
    - (id)initWithURL:(NSURL*)url;
     
    @end

    PageLoadOperation.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    #import "PageLoadOperation.h"
    #import "AppDelegate.h"
     
    @implementation PageLoadOperation
     
    @synthesize targetURL;
     
    - (id)initWithURL:(NSURL*)url;
    {
        if (![super init]) return nil;
        [self setTargetURL:url];
        return self;
    }
     
    - (void)dealloc {
        [targetURL release],targetURL = nil;
        [super dealloc];
    }
     
    - (void)main {
        Nsstring *webpageString = [[[Nsstring alloc] initWithContentsOfURL:[self targetURL]] autorelease];
     
        NSError *error = nil;
        NSXMLDocument *document = [[NSXMLDocument alloc] initWithXMLString:webpageString 
                                                                  options:NSXMLDocumentTidyHTML 
                                                                    error:&error];
        if (!document) {
            NSLog(@"%s Error loading document (%@): %@",_cmd,[[self targetURL] absoluteString],error);
            return;
        }	
     
        [[AppDelegate shared] performSelectorOnMainThread:@selector(pageLoaded:)
                                               withObject:document
                                            waitUntilDone:YES];
        [document release];
    }
     
    @end

    As you can see,this class is very simple. It accepts a URL in the init and stores it. When the main method is called it constructs a string from the URL and then passes that string into the init of an NSXMLDocument. If there is no error with the loading of the xml document,it is then passed back to the AppDelegate,on the main thread,and the task is complete. When the main method of the NSOperation ends the queue will automatically release the object.

    AppDelegate.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    #import <Cocoa/Cocoa.h>
     
    @interface AppDelegate : NSObject {
    	NSOperationQueue *queue;
    }
     
    + (id)shared;
    - (void)pageLoaded:(NSXMLDocument*)document;
     
    @end

    AppDelegate.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    
    #import "AppDelegate.h"
    #import "PageLoadOperation.h"
     
    @implementation AppDelegate
    static AppDelegate *shared;
    static NSArray *urlArray;
     
    - (id)init
    {
        if (shared) {
            [self autorelease];
            return shared;
        }
        if (![super init]) return nil;
     
        NSMutableArray *array = [[NSMutableArray alloc] init];
        [array addobject:@"http://www.google.com"];
        [array addobject:@"http://www.apple.com"];
        [array addobject:@"http://www.yahoo.com"];
        [array addobject:@"http://www.zarrastudios.com"];
        [array addobject:@"http://www.macosxhints.com"];
        urlArray = array;
     
        queue = [[NSOperationQueue alloc] init];
        shared = self;
        return self;
    }
     
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    {
        for (Nsstring *urlString in urlArray) {
            NSURL *url = [NSURL URLWithString:urlString];
            PageLoadOperation *plo = [[PageLoadOperation alloc] initWithURL:url];
            [queue addOperation:plo];
            [plo release];
        }
    }
     
    - (void)dealloc
    {
        [queue release],queue = nil;
        [super dealloc];
    }
     
    + (id)shared;
    {
        if (!shared) {
            [[AppDelegate alloc] init];
        }
        return shared;
    }
     
    - (void)pageLoaded:(NSXMLDocument*)document;
    {
        NSLog(@"%s Do something with the XMLDocument: %@",document);
    }
     
    @end

    In this example AppDelegate,two things are occurring. First,in the init method,the NSOperationQueue is being initialized and an array of urls is being loaded. Then when the application has completed its load,the applicationDidFinishLaunching: method is called by the NSApplication instance and the AppDelegate loops over the urls,creating a task for each one and loading those tasks into the NSOperationQueue. As soon as each item is loaded into the queue the queue will kick it off by assigning it to a NSThread and the thread will then run the main method of the operation. Once the operation is complete the thread will report back to the queue and the queue will release the operation.

    NSOperationQueue Concurrency

    In this very simple example,it is quite difficult to load up the queue with enough objects to actually see it running them in parallel. However,if you run tasks that take more time than these,you will see that the queue will run all of the tasks at the same time. Fortunately,if you want to limit how many tasks are running in parallel you can alter the init method in the App Delegate as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    - (id)init
    {
        if (shared) {
            [self autorelease];
            return shared;
        }
        if (![super init]) return nil;
     
        NSMutableArray *array = [[NSMutableArray alloc] init];
        [array addobject:@"http://www.google.com"];
        [array addobject:@"http://www.apple.com"];
        [array addobject:@"http://www.yahoo.com"];
        [array addobject:@"http://www.zarrastudios.com"];
        [array addobject:@"http://www.macosxhints.com"];
        urlArray = array;
        queue = [[NSOperationQueue alloc] init];
        [queue setMaxConcurrentOperationCount:2];
        shared = self;
        return self;
    }

    In this updated init method,the queue is throttled down to 2 operations running at the same time. The rest of the operations will wait until one of the first two is completed and then they will get an opportunity to run until the queue is empty.

    Conclusion

    That is the NSOperation and NSOperationQueue in its most basic form. You will note that most of the code in this example has nothing to do with the building up or using of the NSOperation or the NSOperationQueue. The actual code required to use the NSOperation is amazingly small. Yet with this small amount of code you can easily start using multiple threads in your application and provide a better experience for the user and be able to fine tune those complicated tasks.

    Cocoa:NSOperation和NSOperationQueue

    Cocoa:NSOperation和NSOperationQueue

     
    标签: 多线程 Cocoa NSOperation NSOperationQueue
    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。 http://www.voidcn.com/article/p-gbbimzoo-bat.html

        Cocoa: NSOperation和NSOperationQueue

        在任何语言中多线程处理都是麻烦的。更糟糕的是如果出错了往往会以很坏的方式出错。鉴于此,程序员要么完全避免使用多线程(把它当做邪恶之源),要么发很长的时间来确保每个方面都很完美。

        庆幸的是, Apple在OS X 10.5 Leopard上做了很多改进。NSThread本身就新增了很多新的方法,从而使得多线程变得更加容易。此外还新增了NSOperation和NSOperationQueue两个类。该教程通过一个简单的实例来介绍如何使用这些新增类并如何让多线程的应用变得小菜一碟。
       你可以从此获取该项目的实例代码: Async Downloader Example Project
        在该教程中,我会介绍一种使用 NSOperation和NSOperationQueue在后台线程中很好地处理任务的方法。该教程的目标是介绍这些类的使用,但并不排除使用它们的其他方法。
    如果你熟悉 Java或一个它的变种语言,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一样,NSOperation也是设计用来扩展的,并且最低仅需重写一个方法。对于NSOperation这个方法是-(void)main。一个使用NSOperation的最简单方法就是将其放入NSOperationQueue中。一旦一个操作被加入队列,该队列就会启动并开始处理它。一旦该操作完成队列就会释放它。
     
    NSOperation实例
    在该实例中,我编写了一段获取网页内容的字符串,然后解析成 NSXMLDocument对象并在结束前传回应用主线程。
    PageLoadOperation.h
     
      
      
    1. #import <Cocoa/Cocoa.h>  
    2. @interface PageLoadOperation : NSOperation {  
    3.     NSURL *targetURL;  
    4. }  
    5.  
    6. @property(retain) NSURL *targetURL;  
    7.  
    8. - (id)initWithURL:(NSURL*)url;  
    9.  
    10. @end 

    PageLoadOperation.m

      
      
    1. #import "PageLoadOperation.h" 
    2. #import "AppDelegate.h" 
    3.    
    4. @implementation PageLoadOperation  
    5.    
    6. @synthesize targetURL;  
    7.    
    8. - (id)initWithURL:(NSURL*)url;  
    9. {  
    10.     if (![super init]) return nil;  
    11.     [self setTargetURL:url];  
    12.     return self;  
    13. }  
    14.    
    15. - (void)dealloc {  
    16.     [targetURL release], targetURL = nil;  
    17.     [super dealloc];  
    18. }  
    19.    
    20. - (void)main {  
    21.     Nsstring *webpageString = [[[Nsstring alloc] initWithContentsOfURL:[self targetURL]] autorelease];  
    22.    
    23.     NSError *error = nil;  
    24.     NSXMLDocument *document = [[NSXMLDocument alloc] initWithXMLString:webpageString   
    25.                                                               options:NSXMLDocumentTidyHTML   
    26.                                                                 error:&error];  
    27.     if (!document) {  
    28.         NSLog(@"%s Error loading document (%@): %@", _cmd, [[self targetURL] absoluteString], error);  
    29.         return;  
    30.     }     
    31.    
    32.     [[AppDelegate shared] performSelectorOnMainThread:@selector(pageLoaded:)  
    33.                                            withObject:document  
    34.                                         waitUntilDone:YES];  
    35.     [document release];  
    36. }  
    37.    
    38. @end 
    我们可以看到,这个类很简单。它在 init方法中接受一个URL并保存起来。在调用main方法的时候它就从URL中构建一个字符串并传给NSXMLDocument的init方法。如果在加载xml文档过程中没有发生错误,该文档就会被传回到主线程的AppDelegate,然后任务结束。队列会在NSOperation的main方法结束后自动释放该对象。
    AppDelegate.h
     
      
      
    1. #import <Cocoa/Cocoa.h>  
    2.    
    3. @interface AppDelegate : NSObject {  
    4.     NSOperationQueue *queue;  
    5. }  
    6.    
    7. + (id)shared;  
    8. - (void)pageLoaded:(NSXMLDocument*)document;  
    9.    
    10. @end 
    AppDelegate.m
     
      
      
    1. #import "AppDelegate.h" 
    2. #import "PageLoadOperation.h" 
    3.    
    4. @implementation AppDelegate  
    5. static AppDelegate *shared;  
    6. static NSArray *urlArray;  
    7.    
    8. - (id)init  
    9. {  
    10.     if (shared) {  
    11.         [self autorelease];  
    12.         return shared;  
    13.     }  
    14.     if (![super init]) return nil;  
    15.    
    16.     NSMutableArray *array = [[NSMutableArray alloc] init];  
    17.     [array addobject:@"http://www.google.com"];  
    18.     [array addobject:@"http://www.apple.com"];  
    19.     [array addobject:@"http://www.yahoo.com"];  
    20.     [array addobject:@"http://www.zarrastudios.com"];  
    21.     [array addobject:@"http://www.macosxhints.com"];  
    22.     urlArray = array;  
    23.    
    24.     queue = [[NSOperationQueue alloc] init];  
    25.     shared = self;  
    26.     return self;  
    27. }  
    28.    
    29. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification  
    30. {  
    31.     for (Nsstring *urlString in urlArray) {  
    32.         NSURL *url = [NSURL URLWithString:urlString];  
    33.         PageLoadOperation *plo = [[PageLoadOperation alloc] initWithURL:url];  
    34.         [queue addOperation:plo];  
    35.         [plo release];  
    36.     }  
    37. }  
    38.    
    39. - (void)dealloc  
    40. {  
    41.     [queue release], queue = nil;  
    42.     [super dealloc];  
    43. }  
    44.    
    45. + (id)shared;  
    46. {  
    47.     if (!shared) {  
    48.         [[AppDelegate alloc] init];  
    49.     }  
    50.     return shared;  
    51. }  
    52.    
    53. - (void)pageLoaded:(NSXMLDocument*)document;  
    54. {  
    55.     NSLog(@"%s Do something with the XMLDocument: %@", document);  
    56. }  
    57.    
    58. @end 
    在实例中 AppDelegate做了两件事。其一是在init方法中初始化了NSOperationQueue并载入了一个urls数组。在应用结束加载时NSApplication 实例会调用applicationDidFinishLaunching:方法,AppDelegate就遍历每个url并创建相应的任务,然后将任务加入队列中。在队列中每加入一个人后,队列都会为其分配一个NSThread来启动它,线程就会运行操作的main方法。一旦操作完成,线程就会报告给队列以让队列释放该操作。
     
    NSOperationQueue的并发
    在这个简单的实例中,由于很难再队列中加入足够多的任务使得我们很难看到它们是并发运行的。但如果你的任务需要更多的时间,你就可以看到队列是同时运行所有的任务。如果你想限制并行运行的任务数目,你可以在 AppDelegate的init方法中做如下修改。
     
      
      
    1. - (id)init  
    2. {  
    3.     if (shared) {  
    4.         [self autorelease];  
    5.         return shared;  
    6.     }  
    7.     if (![super init]) return nil;  
    8.    
    9.     NSMutableArray *array = [[NSMutableArray alloc] init];  
    10.     [array addobject:@"http://www.google.com"];  
    11.     [array addobject:@"http://www.apple.com"];  
    12.     [array addobject:@"http://www.yahoo.com"];  
    13.     [array addobject:@"http://www.zarrastudios.com"];  
    14.     [array addobject:@"http://www.macosxhints.com"];  
    15.     urlArray = array;  
    16.     queue = [[NSOperationQueue alloc] init];  
    17.     [queue setMaxConcurrentOperationCount:2];  
    18.     shared = self;  
    19.     return self;  
    在这个修改的 init方法中,队列被限制只能同时运行两个操作。剩余的操作就需要等待之前的两个操作有一个完成后才有机会被运行。
     
    结论
    这是 NSOperation和NSOperationQueue最基本的使用。你可以注意到该实例的大部分代码和NSOperation和NSOperationQueue 的设定和使用无关。NSOperation所需要的代码是惊人地少。但是通过这少量的代码你就可以在你的应用中轻松地使用多线程以为用户提供更好的用户体验并可以更好地优化复杂任务。
    原文链接:http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/

    Cocoa深入学习:NSOperationQueue、NSRunLoop和线程安全

    Cocoa深入学习:NSOperationQueue、NSRunLoop和线程安全

    该文转自刘坤的技术博客

    目前在 iOS 和 OS X 中有两套先进的同步 API 可供我们使用:NSOperation 和 GCD 。其中 GCD 是基于 C 的底层的 API ,而 NSOperation 则是 GCD 实现的 Objective-C API。 虽然 NSOperation 是基于 GCD 实现的, 但是并不意味着它是一个 GCD 的 “dumbed-down” 版本, 相反,我们可以用NSOperation 轻易的实现一些 GCD 要写大量代码的事情。 因此, NSOperationQueue 是被推荐使用的, 除非你遇到了 NSOperationQueue 不能实现的问题。
    1. 为什么优先使用NSOperationQueue而不是GCD
    曾经我有一段时间我非常喜欢使用GCD来进行并发编程,因为虽然它是C的api,但是使用起来却非常简单和方便,不过这样也就容易使开发者忘记并发编程中的许多注意事项和陷阱。
    比如你可能写过类似这样的代码(这样来请求网络数据):

    dispatch_async(_Queue,^{
    
      //请求数据
      NSData *data = [NSData dataWithContentURL:[NSURL URLWithString:@"http://domain.com/a.png"]];
    
        dispatch_async(dispatch_get_main_queue(),^{
    
             [self refreshViews:data];
        });
    });

    没错,它是可以正常的工作,但是有个致命的问题:这个任务是无法取消的 dataWithContentURL:是同步的拉取数据,它会一直阻塞线程直到完成请求,如果是遇到了超时的情况,它在这个时间内会一直占有这个线程;在这个期间并发队列就需要为其他任务新建线程,这样可能导致性能下降等问题。
    因此我们不推荐这种写法来从网络拉取数据。
    操作队列(operation queue)是由 GCD 提供的一个队列模型的 Cocoa 抽象。GCD 提供了更加底层的控制,而操作队列则在 GCD 之上实现了一些方便的功能,这些功能对于 app 的开发者来说通常是最好最安全的选择。NSOperationQueue相对于GCD来说有以下优点:
    提供了在 GCD 中不那么容易复制的有用特性。
    可以很方便的取消一个NSOperation的执行
    可以更容易的添加任务的依赖关系
    提供了任务的状态:isExecuteing,isFinished.
    名词: 本文中提到的 “任务”, “操作” 即代表要再NSOperation中执行的事情。
    2. Operation Queues的使用
    2.1 NSOperationQueue
    NSOperationQueue 有两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。在两种类型中,这些队列所处理的任务都使用 NSOperation 的子类来表述。

    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];  //主队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //自定义队列
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
                    //任务执行
                }];
    [queue addOperation:operation];

    我们可以通过设置 maxConcurrentOperationCount 属性来控制并发任务的数量,当设置为 1 时, 那么它就是一个串行队列。主对列默认是串行队列,这一点和 dispatch_queue_t 是相似的。
    2.2 NSOperation
    你可以使用系统提供的一些现成的 NSOperation 的子类, 如 NSBlockOperation、 NSInvocationoperation 等(如上例子)。你也可以实现自己的子类, 通过重写 main 或者 start 方法 来定义自己的 operations 。
    使用 main 方法非常简单,开发者不需要管理一些状态属性(例如 isExecuting 和 isFinished),当 main 方法返回的时候,这个 operation 就结束了。这种方式使用起来非常简单,但是灵活性相对重写 start 来说要少一些, 因为main方法执行完就认为operation结束了,所以一般可以用来执行同步任务。

    @implementation YourOperation
    - (void)main
    {
        // 任务代码 ...
    }
    @end

    如果你希望拥有更多的控制权,或者想在一个操作中可以执行异步任务,那么就重写 start 方法,但是注意:这种情况下,你必须手动管理操作的状态, 只有当发送 isFinished 的 KVO 消息时,才认为是 operation 结束

    @implementation YourOperation
    - (void)start
    {
      self.isExecuting = YES;
        // 任务代码 ...
    }
    - (void)finish //异步回调
    {
      self.isExecuting = NO;
      self.isFinished = YES;
    }
    @end

    当实现了start方法时,默认会执行start方法,而不执行main方法
    为了让操作队列能够捕获到操作的改变,需要将状态的属性以配合 KVO 的方式进行实现。如果你不使用它们默认的 setter 来进行设置的话,你就需要在合适的时候发送合适的 KVO 消息。
    需要手动管理的状态有:
    isExecuting 代表任务正在执行中
    isFinished 代表任务已经执行完成
    isCancelled 代表任务已经取消执行
    手动的发送 KVO 消息, 通知状态更改如下 :

    [self willChangeValueForKey:@"isCancelled"];
    _isCancelled = YES;
    [self didChangeValueForKey:@"isCancelled"];

    为了能使用操作队列所提供的取消功能,你需要在长时间操作中时不时地检查 isCancelled 属性,比如在一个长的循环中:

    @implementation MyOperation
    
    - (void)main
    {
        while (notDone && !self.isCancelled) {
            // 任务处理
        }
    }
    @end

    3. RunLoop
    在cocoa中讲到多线程,那么就不得不讲到RunLoop。 在ios/mac的编码中,我们似乎不需要过多关心代码是如何执行的,一切仿佛那么自然。比如我们知道当滑动手势时,tableView就会滚动,启动一个NSTimer之后,timer的方法就会定时执行, 但是为什么呢,其实是RunLoop在帮我们做这些事情:分发消息。
    3.1 什么是RunLoop
    你应该看过这样的伪代码解释ios的app中main函数做的事情:

    int main(int argc,char * argv[])
    {
        while (true) {
          [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }

    也应该看过这样的代码用来阻塞一个线程:

    while (!complete) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }

    或许你感觉到他们有些神奇,希望我的解释能让你明白一些.
    我们先思考一个问题: 当我们打开一个IOS应用之后,什么也不做,这时候看起来是没有代码在执行的,为什么应用没有退出呢?
    我们在写c的简单的只有一个main函数的程序时就知道,当main的代码执行完,没有事情可做的时候,程序就执行完毕退出了。而我们IOS的应用是如何做到在没有事情做的时候维持应用的运行的呢? 那就是RunLoop。
    RunLoop的字面意思就是“运行回路”,听起来像是一个循环。实际它就是一个循环,它在循环监听着事件源,把消息分发给线程来执行。RunLoop并不是线程,也不是并发机制,但是它在线程中的作用至关重要,它提供了一种异步执行代码的机制。
    3.2 事件源
    runloop
    由图中可以看出NSRunLoop只处理两种源:输入源、时间源。而输入源又可以分为:NSPort、自定义源、performSelector:OnThread:delay:,下面简单介绍下这几种源:
    3.2.1 NSPort 基于端口的源
    Cocoa和 Core Foundation 为使用端口相关的对象和函数创建的基于端口的源提供了内在支持。Cocoa中你从不需要直接创建输入源。你只需要简单的创建端口对象,并使用NSPort的方法将端口对象加入到run loop。端口对象会处理创建以及配置输入源。
    NSPort一般分三种: NSMessagePort(基本废弃)、NSMachPort、 NSSocketPort。 系统中的NSURLConnection就是基于NSSocketPort进行通信的,所以当在后台线程中使用NSURLConnection 时,需要手动启动RunLoop,因为后台线程中的RunLoop默认是没有启动的,后面会讲到。
    3.2.2 自定义输入源
    在Core Foundation程序中,必须使用CFRunLoopSourceRef类型相关的函数来创建自定义输入源,接着使用回调函数来配置输入源。Core Fundation会在恰当的时候调用回调函数,处理输入事件以及清理源。常见的触摸、滚动事件等就是该类源,由系统内部实现。
    一般我们不会使用该种源,第三种情况已经满足我们的需求
    3.2.3 performSelector:OnThread
    Cocoa提供了可以在任一线程执行函数(perform selector)的输入源。和基于端口的源一样,perform selector请求会在目标线程上序列化,减缓许多在单个线程上容易引起的同步问题。而和基于端口的源不同的是,perform selector执行完后会自动清除出run loop。
    此方法简单实用,使用也更广泛。
    3.2.4 定时源
    定时源就是NSTimer了,定时源在预设的时间点同步地传递消息。因为Timer是基于RunLoop的,也就决定了它不是实时的。
    3.3 RunLoop观察者
    我们可以通过创建CFRunLoopObserverRef对象来检测RunLoop的工作状态,它可以检测RunLoop的以下几种事件:
    Run loop入口
    Run loop将要开始定时
    Run loop将要处理输入源
    Run loop将要休眠
    Run loop被唤醒但又在执行唤醒事件前
    Run loop终止
    3.4 Run Loop Modes
    RunLoop对于上述四种事件源的监视,可以通过设置模式来决定监视哪些源。 RunLoop只会处理与当前模式相关联的源,未与当前模式关联的源则处于暂停状态。
    cocoa和Core Foundation预先定义了一些模式(Apple文档翻译):
    Mode Name Description
    Default NSDefaultRunLoopMode (Cocoa) kcfRunLoopDefaultMode (Core Foundation) 缺省情况下,将包含所有操作,并且大多数情况下都会使用此模式
    Connection NSConnectionReplyMode (Cocoa) 此模式用于处理NSConnection的回调事件
    Modal NSModalPanelRunLoopMode (Cocoa) 模态模式,此模式下,RunLoop只对处理模态相关事件
    Event Tracking NSEventTrackingRunLoopMode (Cocoa) 此模式下用于处理窗口事件,鼠标事件等
    Common Modes NSRunLoopCommonModes (Cocoa) kcfRunLoopCommonModes (Core Foundation) 此模式用于配置”组模式”,一个输入源与此模式关联,则输入源与组中的所有模式相关联。
    我们也可以自定义模式,可以参考ASIHttpRequest在同步执行时,自定义了 runLoop 的模式叫 ASIHTTPRequestRunLoopMode。ASI的Timer源就关联了此模式。
    3.5 常见问题一:为什么TableView滑动时,Timer暂停了?
    我们做个测试: 在一个 viewController 的 scrollViewWillBeginDecelerating: 方法里面打个断点, 然后滑动 tableView。 待断点处, 使用 lldb 打印一下 [NSRunLoop currentRunLoop] 。 在描述中可以看到当前的RunLoop的运行模式:

    current mode = UITrackingRunLoopMode
    common modes = <CFBasicHash 0x14656e60 [0x3944dae0]>{type = mutable set,count = 2,entries =>
    0 : <CFString 0x398d54c0 [0x3944dae0]>{contents = "UITrackingRunLoopMode"}
    1 : <CFString 0x39449d10 [0x3944dae0]>{contents = "kcfRunLoopDefaultMode"}
    }

    也就是说,当前主线程的 RunLoop 正在以 UITrackingRunLoopMode 的模式运行。 这个时候 RunLoop 只会处理与 UITrackingRunLoopMode “绑定”的源, 比如触摸、滚动等事件;而 NSTimer 是默认“绑定”到 NSRunLoopDefaultMode 上的, 所以 Timer 是事情是不会被 RunLoop 处理的,我们的看到的时定时器被暂停了!
    常见的解决方案是把Timer“绑定”到 NSRunLoopCommonModes 模式上, 那么Timer就可以与:

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

    这样这个Timer就可以和当前组中的两种模式 UITrackingRunLoopMode 和 kcfRunLoopDefaultMode 相关联了。 RunLoop在这两种模式下,Timer都可以正常运行了。
    注意: 由上面可以发现 NSTimer 是不准确的。 因为RunLoop只负责分发源的消息。如果线程当前正在处理繁重的任务,比如循环,就有可能导致Timer本次延时,或者少执行一次。网上有人做过实验:

    这里写图片描述

    runloop_timer
    上面的Log是一个间隔为 1 s 的计时器,我们可以发现在 12.836s ~ 15.835s 之间的时间段内, 明显的 13s 的方法没有执行。 14s 的方法有所延迟。
    因此当我们用NSTimer来完成一些计时任务时,如果需要比较精确的话,最好还是要比较“时间戳”。
    3.6 常见问题二:后台的NSURLConnection不回调,Timer不运行
    我们知道每个线程都有它的RunLoop,我们可以通过 [NSRunLoop currentRunLoop] 或 CFRunLoopGetCurrent() 来获取。 但是主线程和后台线程是不一样的。主线程的RunLoop是一直在启动的。而后台线程的RunLoop是默认没有启动的。
    后台线程的RunLoop没有启动的情况下的现象就是:“代码执行完,线程就结束被回收了”。就像我们简单的程序执行完就退出了。 所以如果我们希望在代码执行完成后还要保留线程等待一些异步的事件时,比如NSURLConnection和NSTimer, 就需要手动启动后台线程的RunLoop。
    启动RunLoop,我们需要设定RunLoop的模式,我们可以设置 NSDefaultRunLoopMode。 那默认就是监听所有时间源:

    //Cocoa
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    
    //Core Foundation
    CFRunLoopRun();

    我们也可以设置其他模式运行,但是我们就需要把“事件源” “绑定”到该模式上:

    //NSURLConnection   
    [_connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]];
    
    //Timer
    [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; [[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]];

    3.7 问题三:本节开头的例子为何可以阻塞线程

    while (!complete) {
      [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }

    你应该知道这样一段代码可以阻塞当前线程,你可能会奇怪:RunLoop就是不停循环来检测源的事件,为什么还要加个 while 呢?
    这是因为RunLoop的特性,RunLoop会在没有“事件源”可监听时休眠。也就是说如果当前没有合适的“源”被RunLoop监听,那么这步就跳过了,不能起到阻塞线程的作用,所以还是要加个while循环来维持。
    同时注意:因为这段代码可以阻塞线程,所以请不要在主线程写下这段代码,因为它很可能会导致界面卡住。
    4. 线程安全
    讲了这么多,你是否已经对并发编程已经跃跃欲试了呢? 但是并发编程一直都不是一个轻松的事情,使用并发编程会带来许多陷阱。哪怕你是一个很成熟的程序员和架构师,也很难避免线程安全的问题;使用的越多,出错的可能就越大,因此可以不用多线程就不要使用。
    关于并发编程的不可预见性有一个非常有名的例子:在1995年, NASA (美国宇航局)发送了开拓者号火星探测器,但是当探测器成功着陆在我们红色的邻居星球后不久,任务嘎然而止,火星探测器莫名其妙的不停重启,在计算机领域内,遇到的这种现象被定为为优先级反转,也就是说低优先级的线程一直阻塞着高优先级的线程。在这里我们想说明的是,即使拥有丰富的资源和大量优秀工程师的智慧,并发也还是会在不少情况下反咬你你一口。
    4.1 资源共享和资源饥饿
    并发编程中许多问题的根源就是在多线程中访问共享资源。资源可以是一个属性、一个对象,通用的内存、网络设备或者一个文件等等。在多线程中任何一个共享的资源都可能是一个潜在的冲突点,你必须精心设计以防止这种冲突的发生。
    一般我们通过锁来解决资源共享的问题,也就是可以通过对资源加锁保证同时只有一个线程访问资源
    4.1.1 互斥锁
    互斥访问的意思就是同一时刻,只允许一个线程访问某个特定资源。为了保证这一点,每个希望访问共享资源的线程,首先需要获得一个共享资源的互斥锁。 对资源加锁会引发一定的性能代价。
    4.1.2 原子性
    从语言层面来说,在 Objective-C 中将属性以 atomic 的形式来声明,就能支持互斥锁了。事实上在默认情况下,属性就是 atomic 的。将一个属性声明为 atomic 表示每次访问该属性都会进行隐式的加锁和解锁操作。虽然最把稳的做法就是将所有的属性都声明为 atomic,但是加解锁这也会付出一定的代价。
    4.1.3 死锁
    互斥锁解决了竞态条件的问题,但很不幸同时这也引入了一些其他问题,其中一个就是死锁。当多个线程在相互等待着对方的结束时,就会发生死锁,这时程序可能会被卡住。
    比如下面的代码:

    dispatch_sync(_queue,^{
      dispatch_sync(_queue,^{
          //do something
      });
    })

    再比如:

    main() {
    dispatch_sync(dispatch_get_main_queue(),^{ //do something });
    }

    上面两个例子也可以说明 dispatch_sync 这个API是危险的,所以尽量不要用。
    当你的代码有死锁的可能时,它就会发生
    4.1.4 资源饥饿
    当你认为已经足够了解并发编程面临的问题时,又出现了一个新的问题。锁定的共享资源会引起读写问题。大多数情况下,限制资源一次只能有一个线程进行读取访问其实是非常浪费的。因此,在资源上没有写入锁的时候,持有一个读取锁是被允许的。这种情况下,如果一个持有读取锁的线程在等待获取写入锁的时候,其他希望读取资源的线程则因为无法获得这个读取锁而导致资源饥饿的发生。
    4.2 优先级反转
    优先级反转是指程序在运行时低优先级的任务阻塞了高优先级的任务,有效的反转了任务的优先级。GCD提供了3种级别的优先级队列,分别是Default,High,Low。 高优先级和低优先级的任务之间共享资源时,就可能发生优先级反转。当低优先级的任务获得了共享资源的锁时,该任务应该迅速完成,并释放掉锁,这样高优先级的任务就可以在没有明显延时的情况下继续执行。然而高优先级任务会在低优先级的任务持有锁的期间被阻塞。如果这时候有一个中优先级的任务(该任务不需要那个共享资源),那么它就有可能会抢占低优先级任务而被执行,因为此时高优先级任务是被阻塞的,所以中优先级任务是目前所有可运行任务中优先级最高的。此时,中优先级任务就会阻塞着低优先级任务,导致低优先级任务不能释放掉锁,这也就会引起高优先级任务一直在等待锁的释放。如下图:

    这里写图片描述

    使用不同优先级的多个队列听起来虽然不错,但毕竟是纸上谈兵。它将让本来就复杂的并行编程变得更加复杂和不可预见。因此我们写代码的时候最好只用Default优先级的队列,不要使用其他队列来让问题复杂化。
    关于dispatch_queue的底层线程安全设计可参考:底层并发 API
    5. 总结 本文主要讲了 NSOperationQueue、 NSRunLoop、 和线程安全等三大块内容。 希望可以帮助你理解 NSOperation的使用, NSRunLoop的作用, 还有并发编程带来的复杂性和相关问题。 并发实际上是一个非常棒的工具。它充分利用了现代多核 cpu 的强大计算能力。但是因为它的复杂性,所以我们尽量使用高级的API,尽量写简单的代码,让并发模型保持简单; 这样可以写出高效、结构清晰、且安全的代码。

    关于多线程NSOperation和NSOperationQueue的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于10.2 NSOperation/NSOperationQueue:提供了一些在GCD中不容易实现的特性,如:限制最大并发数量,操作之间的依赖关系.、Cocoa Tutorial: NSOperation and NSOperationQueue、Cocoa:NSOperation和NSOperationQueue、Cocoa深入学习:NSOperationQueue、NSRunLoop和线程安全的相关信息,请在本站寻找。

    本文标签: