An overview of

As we all know, in the development process should minimize the user wait time, let the program as fast as possible to complete the calculation. However, programs developed in any language are often eventually translated into assembly language and interpreted into machine code for execution. But machine code is executed sequentially, and a complex multi-step operation can only be executed one step at a time. There are two ways to change this: for single-core processors, multiple steps can be placed on different threads, so that after the user completes the UI, other tasks can continue in other threads when the CPU is idle, while other operations can continue for the user. For multi-core processors, if the user in the UI thread to complete an operation, after other subsequent operations continue to be executed in the other thread, the user can also proceed other UI operation, at the same time before a subsequent task operation can continue across multiple idle CPU (of course according to the specific scheduling order program design), And solve the thread blocking and improve the operation efficiency. Apple started with a dual-core A5 processor in the iPad2 (and started with the iPhone 4S in the iPhone) and added a coprocessor to the A7, so it’s worth thinking about how to get the most out of those processors. Today’s focus will be on iOS multi-threaded development:

  1. multithreading
    1. Introduction to the
    2. IOS multithreading
  2. NSThread
    1. Resolve thread blocking
    2. Multithreaded concurrency
    3. Thread state
    4. Extension -NSObject class extension
  3. NSOperation
    1. NSInvocationOperation
    2. NSBlockOperation
    3. Thread execution order
  4. GCD
    1. Serial queues
    2. Concurrent queue
    3. Other task execution methods
  5. Thread synchronization
    1. NSLock synchronization lock
    2. @ synchronized code block
    3. Extension – Use GCD to solve resource preemption problems
    4. Extension – Controls thread communication
  6. conclusion
  7. Orders to record

multithreading

Introduction to the

When users play audio, download resources, or perform image processing, they often want to do so without interruption or without interruption. In a single thread, a thread can only do one thing, and one thing cannot be started until the other thing is finished, which inevitably affects the user experience. As early as the period of a single core processor multi-threading, more used to solve this time multithreading thread blocking a user waiting (usually after operation the UI users no longer intervene, other threads in the waiting queue, CPU once free will continue, do not affect the user and other UI operation), its handling capacity and no obvious change. As mobile operating systems, PCS and servers are all multi-core processors, the term “parallel computing” is often mentioned. One thing can be divided into multiple steps. Using multithreading without sequential requirements can not only solve thread blocking but also make full use of the running power of multi-core processors.

The following figure shows an eight-operation task running on a two-core CPU creating four threads. Given that there are two threads per core, the two threads in each CPU will execute alternately, and the operations between the two cpus will run in parallel. As far as a single CPU is concerned, two threads can solve the problem of not being smooth caused by thread blocking. It does not improve the running efficiency by itself. It is the parallel operation of multiple cpus that really solves the running efficiency problem, which is the difference between concurrent and parallel. Of course, whether multi-core or single-core developers do not have to worry too much, because the task is assigned to how many cpus by the system scheduling, developers do not have to worry too much about how many cpus the system has. A developer needs to be concerned about dependencies between threads, because some operations must be completed before they can be executed, and failure to ensure that order can cause problems.

IOS multithreading

In iOS, every process starts with a main thread (UI thread), which is the parent thread of other threads. Since the child threads in iOS are independent of Cocoa Touch except for the main thread, only the main thread can update the UI. (In the new version of iOS, updating the UI using other threads may also be successful, but it is not recommended.) The use of multithreading in iOS is not complicated, the key is how to control the execution sequence of each thread and deal with the problem of resource competition. There are three common ways of multithreaded development:

1.NSThread

2.NSOperation

3.GCD

The three methods were gradually introduced with the development of iOS, so the latter is easier to use than the former, and GCD is currently officially recommended by Apple (it makes full use of the computing performance of the multi-core processor). For those of you who have done.NET development, it is not difficult to find that these three development methods correspond exactly. Net multithreading, thread pooling, and asynchronous invocation, so this article will also compare and contrast.

NSThread

Nsthreads are lightweight multithreaded development and are not complex to use, but you need to manage the thread life cycle. You can use the object method + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument to add the action directly to the thread and start it, You can also create a thread object using the object method – (instanceType)initWithTarget (ID) Target Selector (SEL) Selector Object (ID)argument, and then call the start method to start the thread.

Resolve thread blocking

In the process of resource download, it is sometimes difficult to ensure the download time due to network reasons. If users do not use multi-threading, it may take a long time to complete a download operation, and other operations cannot be carried out during this process. In this example, clicking a button will start a thread to download the image. After downloading the image, use UIImageView to display the image to the interface. It can be seen that after the user clicks the download button, the user can continue to operate the interface regardless of whether the image is downloaded or not, without causing congestion.

// // NSThread implementation multithreading // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (C) 2014 Kenshin Cui. All rights reserved. // #import "KCMainViewController.h" @interface KCMainViewController (){ UIImageView *_imageView; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; } #pragma mark -(void)layoutUI{_imageView =[[UIImageView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame]; _imageView.contentMode=UIViewContentModeScaleAspectFit; [self.view addSubview:_imageView]; UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect]; button.frame=CGRectMake(50, 500, 220, 25); [button setTitle: @ "the image" forState: UIControlStateNormal]; / / add methods [button addTarget: self action: @ the selector (loadImageWithMultiThread) forControlEvents: UIControlEventTouchUpInside]; [self.view addSubview:button]; } #pragma mark -(void)updateImage:(NSData *)imageData{UIImage *image=[UIImage imageWithData:imageData]; _imageView.image=image; } #pragma mark -(NSData *)requestData{NSURL *url=[NSURL *url URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"]; NSData *data=[NSData dataWithContentsOfURL:url]; return data; } #pragma mark -(void)loadImage{// NSData *data= [self requestData]; / * will be according to the UI controls, attention can update the UI in the main thread, another performSelectorOnMainThread method is NSObject classification method, each NSObject objects with this method, it calls the selector method is the method of the current call control, So for example, when we call UIImageView, selector is the method Object of UIImageView: The argument that represents the calling method, but only one argument can be passed (wrap it with an object if there are more than one) waitUntilDone: Whether the thread task is finished executing */ [self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES]; } #pragma mark pragma -(void)loadImageWithMultiThread{// Use the object method // to create a thread with the requested action as the first argument, // NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil]; // // to start a thread, note that starting a thread is not always executed immediately, but is in a ready state, when the system is scheduled to execute; // Method 2: use class method [NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil]; } @endCopy the code

Operation effect:

The procedure is relatively simple, but need to pay attention to the execution steps: When clicking on the “image” button after the start a new thread, the thread in the demonstration took about 5 s or so, in this 5 s is not blocking the UI thread, the user can perform other operations, about 5 s images after the download is complete, now called the UI thread according to the picture the interface (instantaneous) this process. And as I mentioned earlier, when you update the UI, you use the UI thread, so you call NSObject’s class extension method, and you call the UI thread to do the update.

Concurrency of multiple threads

The above illustration does not demonstrate the relationship between multiple child thread operations, so now you can load a few more images in the interface, each from a remote request.

You should notice that either you use + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument, – (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument again uses – (void) performSelectorOnMainThread: (SEL) aSelector withObject: (id) arg waitUntilDone: (BOOL) wait method can only pass a parameter, Since updating an image requires passing the UIImageView index and image data, we can define a class to hold the index and image data for later use.

KCImageData.h

// // kcimagedata. h // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (c) 2014 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> @interface KCImageData : NSObject #pragma mark index @property (nonatomic,assign) int index; #pragma mark @property (nonatomic,strong) NSData *data; @endCopy the code

Next we’ll create multiple UIImageViews and create multiple threads to fill the UIImageView with images.

KCMainViewController.m

// // NSThread implementation multithreading // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (C) 2014 Kenshin Cui. All rights reserved. // #import "KCMainViewController.h" #import "KCImageData.h" #define ROW_COUNT 5 #define COLUMN_COUNT 3 #define ROW_HEIGHT 100 #define ROW_WIDTH ROW_HEIGHT #define CELL_SPACING 10 @interface KCMainViewController (){ NSMutableArray *_imageViews; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; } #pragma mark -(void)layoutUI{imageviews =[NSMutableArray array]; for (int r=0; r<ROW_COUNT; r++) { for (int c=0; c<COLUMN_COUNT; c++) { UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)]; imageView.contentMode=UIViewContentModeScaleAspectFit; // imageView.backgroundColor=[UIColor redColor]; [self.view addSubview:imageView]; [_imageViews addObject:imageView]; } } UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect]; button.frame=CGRectMake(50, 500, 220, 25); [button setTitle: @ "the image" forState: UIControlStateNormal]; / / add methods [button addTarget: self action: @ the selector (loadImageWithMultiThread) forControlEvents: UIControlEventTouchUpInside]; [self.view addSubview:button]; } #pragma mark -(void)updateImage:(KCImageData *)imageData{UIImage *image=[UIImage imageWithData:imageData.data]; UIImageView *imageView= _imageViews[imageData.index]; imageView.image=image; } #pragma mark -(NSData *)requestData:(int)index{NSURL *url=[NSURL] URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"]; NSData *data=[NSData dataWithContentsOfURL:url]; return data; } #pragma mark pragma -(void)loadImage:(NSNumber *)index{// NSLog(@"% I ", I); NSLog(@"current thread:%@",[NSThread currentThread]); int i=[index integerValue]; // NSLog(@"%i",i); NSData *data= [self requestData: I]; KCImageData *imageData=[[KCImageData alloc]init]; imageData.index=i; imageData.data=data; [self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES]; } #pragma mark -(void)loadImageWithMultiThread{// Create multiple threads to fill the picture for (int I =0; i<ROW_COUNT*COLUMN_COUNT; ++i) { // [NSThread detachNewThreadSelector:@selector(loadImage:) toTarget:self withObject:[NSNumber numberWithInt:i]]; NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];  thread.name=[NSString stringWithFormat:@"myThread%i",i]; // Set the thread name [thread start]; } } @endCopy the code

The thread name and number are recorded in the currentThread of NSThread. Note that the number of the main thread is always 1. Multiple threads are started sequentially, but the actual execution may not load the photos sequentially (loadImage: methods may not be created sequentially, which can be seen by printing the index in loadImage:), because the threads are only in the ready state after starting, and the actual execution will be scheduled by the CPU based on the current state.

Images are not loaded in order for two reasons. First, the actual order of execution of each thread is not necessarily sequential (although it is started in order). Second, the actual network condition is likely to be inconsistent with each thread executing. Of course, you can’t change the network problem, you can only make the network speed as fast as possible, but you can change the priority of the thread, so that 15 threads execute a thread first. The priority of a thread ranges from 0 to 1. A larger value indicates a higher priority. The default priority of each thread is 0.5. Modify the image download method as follows, change the last image loading priority, this can increase the probability that it is loaded first, but it is not necessarily loaded first. Because first, other threads are started first, and second, we cannot change the network condition:

-(void)loadImageWithMultiThread{ NSMutableArray *threads=[NSMutableArray array]; int count=ROW_COUNT*COLUMN_COUNT; For (int I =0; i<count; ++i) { // [NSThread detachNewThreadSelector:@selector(loadImage:) toTarget:self withObject:[NSNumber numberWithInt:i]]; NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];  thread.name=[NSString stringWithFormat:@"myThread%i",i]; If (I ==(count-1)){thread.threadpriority =1.0; } else {thread. ThreadPriority = 0.0; } [threads addObject:thread]; } for (int i=0; i<count; i++) { NSThread *thread=threads[i]; [thread start]; }}Copy the code

Thread state

You can put a thread to sleep during a thread operation, give priority to other thread operations, and modify the state of a thread or terminate a specified thread in the process. To solve the problem of loading the last image first, let the other threads sleep for a while and wait for the last thread to execute. Modify the image loading method as follows:

-(NSData *)requestData:(int)index{// if (index! = (ROW_COUNT * COLUMN_COUNT - 1)) {[NSThread sleepForTimeInterval: 2.0]; } NSURL *url=[NSURL URLWithString:_imageNames[index]]; NSData *data=[NSData dataWithContentsOfURL:url]; return data; }Copy the code

A thread state can be isExecuting, isFinished, and isCancellled. The cancel status program can interfere with the setting by calling the thread’s Cancel method. However, it should be noted that only the thread state can be set in the main thread, and the current thread cannot really be stopped. To terminate the thread, exist method must be called in the thread, which is a static method and can be called to exit the current thread.

If you click the stop button during the image loading process to stop the unfinished thread, you can modify the program as follows:

// // NSThread implementation multithreading // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (C) 2014 Kenshin Cui. All rights reserved. // #import "KCMainViewController.h" #import "KCImageData.h" #define ROW_COUNT 5 #define COLUMN_COUNT 3 #define ROW_HEIGHT 100 #define ROW_WIDTH ROW_HEIGHT #define CELL_SPACING 10 @interface KCMainViewController (){ NSMutableArray *_imageViews; NSMutableArray *_imageNames; NSMutableArray *_threads; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; } #pragma mark -(void)layoutUI{imageviews =[NSMutableArray array]; for (int r=0; r<ROW_COUNT; r++) { for (int c=0; c<COLUMN_COUNT; c++) { UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)]; imageView.contentMode=UIViewContentModeScaleAspectFit; // imageView.backgroundColor=[UIColor redColor]; [self.view addSubview:imageView]; [_imageViews addObject:imageView]; }} / / load button UIButton * buttonStart = [UIButton buttonWithType: UIButtonTypeRoundedRect]; buttonStart.frame=CGRectMake(50, 500, 100, 25); [buttonStart setTitle: @ "the image" forState: UIControlStateNormal]; [buttonStart addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:buttonStart]; / / stop button UIButton * buttonStop = [UIButton buttonWithType: UIButtonTypeRoundedRect]; buttonStop.frame=CGRectMake(160, 500, 100, 25); [buttonStop setTitle: @ "stop loading" forState: UIControlStateNormal]; [buttonStop addTarget:self action:@selector(stopLoadImage) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:buttonStop]; _imageNames=[NSMutableArray array]; [_imageNames addObject:@ for (int i=0; i<IMAGE_COUNT;  i++) { [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]]; } #pragma mark -(void)updateImage:(KCImageData *)imageData{UIImage *image=[UIImage imageWithData:imageData.data]; UIImageView *imageView= _imageViews[imageData.index]; imageView.image=image; } #pragma mark -(NSData *)requestData:(int)index{NSURL *url=[NSURL URLWithString:_imageNames[index]];  NSData *data=[NSData dataWithContentsOfURL:url]; return data; } #pragma mark pragma -(void)loadImage:(NSNumber *)index{int I =[index integerValue];  NSThread *currentThread=[NSThread currentThread]; / / if the current thread is in a state to cancel, withdraw from the current thread if (currentThread. IsCancelled) {NSLog (@ "thread (% @) will be cancelled! } KCImageData *imageData=[[KCImageData alloc]init]; imagedata.index = I;  imageData.data=data; [self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES]; } #pragma mark loadImageWithMultiThread{int count=ROW_COUNT*COLUMN_COUNT; _threads=[NSMutableArray arrayWithCapacity:count]; for (int I =0; I <count;  ++i) { NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber Thread.name =[NSString stringWithFormat:@"myThread% I ", I]; For (int I =0; I <count; ++ I) {NSThread *thread= _threads[I]; [thread start]; }} #pragma mark -(void)stopLoadImage{for (int I =0; I <ROW_COUNT*COLUMN_COUNT; If (I ++) {NSThread *thread= _threads[I]; if (I ++) {NSThread *thread= _threads[I]; thread.isFinished) { [thread cancel]; } } } @endCopy the code

Run effect (click load about 1 second later click stop load) :

 

Use NSThread in multithreaded development process operation is simple, but it’s not easy to control the thread execution order (front adopted the approach of sleep as a last resort), also in the process, if print thread will find cycle it several times to create several threads, it is had to be taken into account in the process of actual development problems, Because the creation of each thread is also quite overhead.

Extension -NSObject class extension method

To simplify multithreaded development, Apple has officially classified NSObject extensions (essentially creating NSThreads) that can be used directly for simple multithreaded operations.

– (void) performSelectorInBackground aSelector: (SEL) withObject: (id) arg: perform an operation in the background, essence is to create a thread to perform current method.

– (void)performSelector (SEL)aSelector onThread (NSThread *) THR withObject (id)arg waitUntilDone (BOOL) Wait: Executing a method on a specified thread requires the user to create a thread object.

– (void) performSelectorOnMainThread: (SEL) aSelector withObject: (id) arg waitUntilDone: (BOOL) wait: on the main thread to execute a method (it has already been used).

For example, the previous method of loading multiple images of the graph can be changed to background thread execution:

-(void)loadImageWithMultiThread{ int count=ROW_COUNT*COLUMN_COUNT; for (int i=0; i<count; ++i) { [self performSelectorInBackground:@selector(loadImage:) withObject:[NSNumber numberWithInt:i]]; }}Copy the code

NSOperation

Multi-threaded development using NSOperation and NSOperationQueue is similar to thread pools in C#, Once you put an NSOperation (subclasses NSInvocationOperation, NSBlockOperation) in the NSOperationQueue, the threads will start in turn. NSOperationQueue is responsible for managing and executing all NSOperations, making it easier to manage the total number of threads and control dependencies between threads.

There are two common subclasses of NSOperation used to create thread operations: NSInvocationOperation and NSBlockOperation. The two methods are essentially the same, but the latter uses Block form to organize code, which is relatively convenient to use.

NSInvocationOperation

Start with an image loading demonstration using NSInvocationOperation. The whole process is to create an operation, specify the call method and parameters in the operation, and then join the operation queue. Other codes basically do not need to be modified, directly modify the loading image method is as follows:

-(void)loadImageWithMultiThread{/* Create a call operation object: call method argument */ NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil]; // The NSInvocationOperation object is not called after it is created. It is started by a start method, but note that if the start method is called directly, the operation will be called in the main thread. Instead of doing this, add it to NSOperationQueue // [invocationOperation Start]; NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init]; / / note added to the operation team, the queue will open a thread to perform this operation [operationQueue addOperation: invocationOperation]; }Copy the code

NSBlockOperation

Let’s use NSBlockOperation to create multiple threads loading images.

// // NSOperation implementation multithreading // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (C) 2014 Kenshin Cui. All rights reserved. // #import "KCMainViewController.h" #import "KCImageData.h" #define ROW_COUNT 5 #define COLUMN_COUNT 3 #define ROW_HEIGHT 100 #define ROW_WIDTH ROW_HEIGHT #define CELL_SPACING 10 @interface KCMainViewController (){ NSMutableArray *_imageViews; NSMutableArray *_imageNames; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; } #pragma mark -(void)layoutUI{imageviews =[NSMutableArray array]; for (int r=0; r<ROW_COUNT; r++) { for (int c=0; c<COLUMN_COUNT; c++) { UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)]; imageView.contentMode=UIViewContentModeScaleAspectFit; // imageView.backgroundColor=[UIColor redColor]; [self.view addSubview:imageView]; [_imageViews addObject:imageView]; } } UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect]; button.frame=CGRectMake(50, 500, 220, 25); [button setTitle: @ "the image" forState: UIControlStateNormal]; / / add methods [button addTarget: self action: @ the selector (loadImageWithMultiThread) forControlEvents: UIControlEventTouchUpInside]; [self.view addSubview:button]; _imageNames=[NSMutableArray array]; for (int i=0; i<IMAGE_COUNT; i++) { [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]]; } #pragma mark -(void)updateImageWithData:(NSData *)data andIndex:(int)index{UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= _imageViews[index]; imageView.image=image; } #pragma mark -(NSData *)requestData:(int)index{NSURL *url=[NSURL URLWithString:_imageNames[index]]; NSData *data=[NSData dataWithContentsOfURL:url]; return data; } #pragma mark pragma -(void)loadImage:(NSNumber *)index{int I =[index integerValue]; NSData *data= [self requestData: I]; NSLog(@"%@",[NSThread currentThread]); [[NSOperationQueue mainQueue] addOperationWithBlock:^{[self updateImageWithData:data andIndex:i]; }]; } #pragma mark loadImageWithMultiThread{int count=ROW_COUNT*COLUMN_COUNT; NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init]; operationQueue.maxConcurrentOperationCount=5; For (int I =0; i<count; ++ I) {// method 1: Create operation block add to queue // // Create multithreaded operation // NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{// [self loadImage:[NSNumber numberWithInt:i]]; // }]; / / / / the create operation queue / / / / [operationQueue addOperation: blockOperation]; [operationQueue addOperationWithBlock:^{[self loadImage:[NSNumber numberWithInt: I]];}]; [operationQueue addOperationWithBlock:^{[self loadImage:[NSNumber numberWithInt: I]];}]; } } @endCopy the code

Compared with the previous image loaded by NSThread, I found that the core code was much simpler. Here I emphasize two points:

  1. With the NSBlockOperation method, all operations do not have to define separate methods, and the problem of passing only one parameter is solved.
  2. Call the addOperationWithBlock: method on the main thread queue to update the UI without defining a parameter entity (previously you had to define a KCImageData to solve the problem of passing only one parameter).
  3. Using NSOperation for multi-threaded development allows you to set the maximum number of concurrent threads, effectively controlling the number of threads. (When the code above runs, you can see that only a limited number of threads can be created while printing the current process. If the code above sets the maximum number of threads to 5, the image is basically loaded five at a time.)

Thread execution order

Previously, it was difficult to control the order of execution of threads using NSThreads, but it is much easier to use NSOperations, where each NSOperation can be set up to depend on threads. Assuming that operation A depends on operation B, the thread action queue will perform operation B first and then operation A when the thread is started. For the previous requirement to load the last diagram first, simply set the dependency thread for the previous thread operation to be the last operation. Modify the image loading method as follows:

-(void)loadImageWithMultiThread{ int count=ROW_COUNT*COLUMN_COUNT; NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init]; operationQueue.maxConcurrentOperationCount=5; NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{[self loadImage:[NSNumber numberWithInt:(count-1)]]; }]; For (int I =0; i<count-1; ++ I) {// method 1: NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{[self loadImage:[NSNumber numberWithInt:i]]; }]; / / load operation Settings depend on the operation for the last picture [blockOperation addDependency: lastBlockOperation]; [operationQueue addOperation:blockOperation]; } / / will be the last image load operation to join the thread queue [operationQueue addOperation: lastBlockOperation]; }Copy the code

Operation effect:

You can see that although the loading of the last image is the last to be added to the action queue, it is executed first. You can set multiple operation dependencies. For example, A depends on B, B depends on C… But never set cyclic dependencies (e.g., A depends on B, B depends on C, and C depends on A), otherwise they will not be implemented.

GCD

Grand Central Dispatch (GCD) is a multi-threaded development mechanism based on C language. It is also the current multi-threaded development method officially recommended by Apple. As mentioned earlier, GCD has the highest level of abstraction and is of course the easiest to use. However, it is based on C language development, unlike NSOperation, which is object-oriented development, but completely process-oriented. Those familiar with C# asynchronous calls should be quick to learn about GCD as it is basically the same as asynchronous calls in C#. The most significant advantage of this mechanism is that it is more efficient for multi-core computation compared with the previous two multi-threaded development methods.

There is also a queue similar to NSOperationQueue in the GCD, which uniformly manages the tasks in the entire queue. However, queues in GCD are divided into parallel queues and serial queues:

  • Serial queue: There is only one thread and the operations added to the queue are performed in the order they are added.
  • Concurrent queues: Multiple threads that queue operations on available processors as they come in, while ensuring that incoming tasks take precedence.

There is also a special queue in GCD called the main queue, which is used to perform operations on the main thread (as you can see from the previous demonstration, there is also a main queue in NSOperation).

Serial queues

When using a serial queue, you first create a serial queue and then call an asynchronous invocation method, in which the serial queue and thread operations are passed in automatically. Using the thread queue to demonstrate the loading process, you will see that multiple images are loaded in order because there is only one thread in the queue.

// // GCD implements multithreading // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (C) 2014 Kenshin Cui. All Rights reserved. // #import "KCMainViewController.h" #import "KCImageData.h" #define ROW_COUNT 5 #define COLUMN_COUNT 3 #define  ROW_HEIGHT 100 #define ROW_WIDTH ROW_HEIGHT #define CELL_SPACING 10 @interface KCMainViewController (){ NSMutableArray *_imageViews; NSMutableArray *_imageNames; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; } #pragma mark -(void)layoutUI{imageviews =[NSMutableArray array]; for (int r=0; r<ROW_COUNT; r++) { for (int c=0; c<COLUMN_COUNT; c++) { UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)]; imageView.contentMode=UIViewContentModeScaleAspectFit; // imageView.backgroundColor=[UIColor redColor]; [self.view addSubview:imageView]; [_imageViews addObject:imageView]; } } UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect]; button.frame=CGRectMake(50, 500, 220, 25); [button setTitle: @ "the image" forState: UIControlStateNormal]; / / add methods [button addTarget: self action: @ the selector (loadImageWithMultiThread) forControlEvents: UIControlEventTouchUpInside]; [self.view addSubview:button]; _imageNames=[NSMutableArray array]; for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) { [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]]; } #pragma mark -(void)updateImageWithData:(NSData *)data andIndex:(int)index{UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= _imageViews[index]; imageView.image=image; } #pragma mark -(NSData *)requestData:(int)index{NSURL *url=[NSURL URLWithString:_imageNames[index]]; NSData *data=[NSData dataWithContentsOfURL:url]; return data; } #pragma mark pragma -(void)loadImage:(NSNumber *)index{ Because they are in a thread NSLog(@" Thread is :%@",[NSThread currentThread]); int i=[index integerValue]; NSData *data= [self requestData: I]; Dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ [self updateImageWithData:data andIndex:i]; }); } #pragma mark loadImageWithMultiThread{int count=ROW_COUNT*COLUMN_COUNT; /* Create a serial queue. Queue type */ dispatch_queue_t serialQueue=dispatch_queue_create("myThreadQueue1", DISPATCH_QUEUE_SERIAL); For (int I =0; i<count; Dispatch_async (serialQueue, ^{[self loadImage:[NSNumber numberWithInt: I]]; }); } // Dispatch_release (seriQueue); } @endCopy the code

Operation effect:

 

The above code also uses the GCD method’s main thread dispatch_get_main_queue() to update the UI, which is essentially the same as the previous two main threads.

Concurrent queue

The concurrent queue is also created using the dispatch_queue_create() method, except that the last parameter is DISPATCH_QUEUE_CONCURRENT. However, in real development we usually do not recreate a concurrent queue but use the dispatch_get_global_queue() method to get a global concurrent queue(although we can use the former if there are multiple concurrent queues). Here is a demonstration of loading multiple images using parallel queues. The code is similar to the serial queue loading above, just modify the photo loading method as follows:

-(void)loadImageWithMultiThread{ int count=ROW_COUNT*COLUMN_COUNT; /* Get the first argument: thread priority GlobalQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); For (int I =0; i<count; Dispatch_async (globalQueue, ^{[self loadImage:[NSNumber numberWithInt: I]]; }); }}Copy the code

Operation effect:

 

If you can use dispatch_async() asynchronously, there is also a synchronous method. Indeed, there is also a dispatch_sync() method in GCD. If you change the above code to make a synchronous call, you can see the following:

It can be seen that the button cannot be clicked again after it is clicked, because all images are loaded in the main thread (which can be viewed by the print thread), and the main thread is blocked, resulting in the image being displayed at one time. It can be concluded that:

  • Whether an operation is performed in multiple threads in GDC depends on the current queue type and execution method, and can only be performed in multiple threads if the queue type is parallel and the asynchronous method is used.
  • Serial queues can execute sequentially, whereas asynchronous methods of parallel queues cannot determine the order of execution.
  • UI updates should be synchronous and other operations should be asynchronous.

Other task execution methods

There are more common methods for GCD tasks than simple synchronous and asynchronous calls:

  1. Dispatch_apply (): execute a task repeatedly, but note that this method cannot be executed asynchronously (wrap dispatch_async() to avoid blocking threads).
  2. Dispatch_once (): dispatch_once(): dispatch_once(): dispatch_once(): dispatch_once(): dispatch_once(): dispatch_once(): dispatch_once(): dispatch_once(): dispatch_once()
  3. Dispatch_time () : execution is delayed for a certain period of time.
  4. Dispatch_barrier_async () : tasks created using this method first check if there are other tasks in the queue to execute, and if there are, wait for the existing tasks to complete. Meanwhile, tasks added after this method must wait for tasks in this method to execute. (This method can be used to control the order of execution. For example, if you want to load the last image first, you can use this method to add the last image loading operation to the queue and then call dispatch_async() to add other image loading tasks.)
  5. Dispatch_group_async () : implements task group management. If a group of tasks are completed, the dispatch_group_notify() method can be used to obtain completion notification (dispatch_group_t needs to be defined as group identification).

Thread synchronization

When it comes to multi-threading, we have to mention the locking mechanism in multi-threading. In the process of multi-threading operation, multiple threads are often executed concurrently. The same resource may be accessed by multiple threads at the same time, resulting in resource robbery. Every Spring Festival, for example, is hard to get, and hundreds of tickets disappear instantly during the process of buying tickets at 12306. Let’s say there are 1,000 tickets on a certain train, and tens of thousands of people are fighting for tickets on the train. Hopefully, everyone in front of them will get tickets. But if only one ticket now, and at the same time there are thousands of people are buying the ticket, although at the time of entering the ticket will determine whether the current votes, but the current is 100 a thread into the ticket link, each thread to handle the votes will reduce 1100 threads for execution of the current votes – 99, in this case is obviously not allowed.

There are two common ways to solve this problem in iOS: using NSLock and @synchronized blocks. The principles are similar, but the blocks are simpler to use (synchronized and lock are similar in C#).

Here we can also take image loading as an example. Suppose there are 9 images, but there are 15 threads preparing to load the 9 images. The convention is not to load the same image repeatedly, thus forming a resource grab situation. In the following program, 9 images will be created. Each time a photo link is read, it will first determine whether the current link number is greater than 1, and immediately remove one of them, up to 9. Let’s look at the error before using the synchronous method:

// // Thread synchronization // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (c) 2014 Kenshin Cui. All Rights reserved. // #import "KCMainViewController.h" #import "KCImageData.h" #define ROW_COUNT 5 #define COLUMN_COUNT 3 #define  ROW_HEIGHT 100 #define ROW_WIDTH ROW_HEIGHT #define CELL_SPACING 10 #define IMAGE_COUNT 9 @interface KCMainViewController (){ NSMutableArray *_imageViews; NSMutableArray *_imageNames; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; } #pragma mark -(void)layoutUI{imageviews =[NSMutableArray array]; for (int r=0; r<ROW_COUNT; r++) { for (int c=0; c<COLUMN_COUNT; c++) { UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)]; imageView.contentMode=UIViewContentModeScaleAspectFit; // imageView.backgroundColor=[UIColor redColor]; [self.view addSubview:imageView]; [_imageViews addObject:imageView]; } } UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect]; button.frame=CGRectMake(50, 500, 220, 25); [button setTitle: @ "the image" forState: UIControlStateNormal]; / / add methods [button addTarget: self action: @ the selector (loadImageWithMultiThread) forControlEvents: UIControlEventTouchUpInside]; [self.view addSubview:button]; _imageNames=[NSMutableArray array]; for (int i=0; i<IMAGE_COUNT; i++) { [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]]; } #pragma mark -(void)updateImageWithData:(NSData *)data andIndex:(int)index{UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= _imageViews[index]; imageView.image=image; } #pragma mark -(NSData *)requestData:(int)index{NSData *data; NSString *name; if (_imageNames.count>0) { name=[_imageNames lastObject]; [_imageNames removeObject:name]; } if(name){ NSURL *url=[NSURL URLWithString:name]; data=[NSData dataWithContentsOfURL:url]; } return data; } #pragma mark pragma -(void)loadImage:(NSNumber *)index{int I =[index integerValue]; NSData *data= [self requestData: I]; Dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ [self updateImageWithData:data andIndex:i]; }); } #pragma mark loadImageWithMultiThread{int count=ROW_COUNT*COLUMN_COUNT; dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); For (int I =0; i<count; Dispatch_async (globalQueue, ^{[self loadImage:[NSNumber numberWithInt: I]]; }); } } @endCopy the code

We first store 9 links in _imageNames to download images, then in requestData: we just need to check the number of _imageNames ata time, if more than one we read one link to load images, then delete the used links, and everything seems to be fine. Run the program:

This may not happen every time, but it depends on how fast you can read and delete links from _imageNames. If you’re fast enough, you probably won’t have any problems, but if you’re slow enough, you’ll see the above situation, which obviously doesn’t meet the previous requirements.

The reasons for this problem are as follows: When thread A has started to fetch the image link before removing it from _imageNames, another thread B has entered the corresponding code, and since it reads the last element of _imageNames each time, So the next thread is actually getting the same image link as the previous thread and that’s what you see here. To solve this problem, make sure that thread A cannot enter the corresponding code after B, and wait for A to complete the relevant operation before B can enter. Let’s change the code using NSLock and @synchronized, respectively.

NSLock

In iOS, NSLock can be used to solve the problem of resource preemption. When using NSLock, the code that needs to be locked (temporarily called “lock code” in the future) is placed between the LOCK and unlock of NSLock. After thread A enters the lock code, the other thread B cannot access it because it has been locked. Thread B can access the lock code only after it is unlocked after the previous thread A finishes executing the lock code. It is important to note that the “lock code” between lock and UNLOCK is used to preempt read and modify code. Do not put too much other code in it. Otherwise, one thread is executing while another thread is waiting.

Also, in the code above, the “grab resource” _imageNames is defined as a member variable, which is unwise and should be defined as an “atomic attribute”. Defining it as an atomic attribute is a good practice for preempted resources, as it is sometimes difficult to ensure that the same resource is not read and modified elsewhere. Nonatomic attribute is read data memory (register good results), and atomic guarantees register directly read data, so that you won’t get a thread is modify the data, and another thread reads the modify before (in memory) data, always ensure that only one thread at the same time during a visit to an attribute.

The following code demonstrates how to use NSLock for thread synchronization:

KCMainViewController.h

//
//  KCMainViewController.h
//  MultiThread
//
//  Created by Kenshin Cui on 14-3-22.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface KCMainViewController : UIViewController

@property (atomic,strong) NSMutableArray *imageNames;
@endCopy the code

KCMainViewController.m

// // Thread synchronization // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (c) 2014 Kenshin Cui. All Rights reserved. // #import "KCMainViewController.h" #import "KCImageData.h" #define ROW_COUNT 5 #define COLUMN_COUNT 3 #define  ROW_HEIGHT 100 #define ROW_WIDTH ROW_HEIGHT #define CELL_SPACING 10 #define IMAGE_COUNT 9 @interface KCMainViewController (){ NSMutableArray *_imageViews; NSLock *_lock; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; } #pragma mark -(void)layoutUI{imageviews =[NSMutableArray array]; for (int r=0; r<ROW_COUNT; r++) { for (int c=0; c<COLUMN_COUNT; c++) { UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)]; imageView.contentMode=UIViewContentModeScaleAspectFit; [self.view addSubview:imageView]; [_imageViews addObject:imageView]; } } UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect]; button.frame=CGRectMake(50, 500, 220, 25); [button setTitle: @ "the image" forState: UIControlStateNormal]; / / add methods [button addTarget: self action: @ the selector (loadImageWithMultiThread) forControlEvents: UIControlEventTouchUpInside]; [self.view addSubview:button]; _imageNames=[NSMutableArray array]; for (int i=0; i<IMAGE_COUNT; i++) { [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]]; } // initialize lock object _lock=[[NSLock alloc]init]; } #pragma mark -(void)updateImageWithData:(NSData *)data andIndex:(int)index{UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= _imageViews[index]; imageView.image=image; } #pragma mark -(NSData *)requestData:(int)index{NSData *data; NSString *name; // lock [_lock]; if (_imageNames.count>0) { name=[_imageNames lastObject]; [_imageNames removeObject:name]; } // unlock [_lock unlock]; if(name){ NSURL *url=[NSURL URLWithString:name]; data=[NSData dataWithContentsOfURL:url]; } return data; } #pragma mark pragma -(void)loadImage:(NSNumber *)index{int I =[index integerValue]; NSData *data= [self requestData: I]; Dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ [self updateImageWithData:data andIndex:i]; }); } #pragma mark loadImageWithMultiThread{int count=ROW_COUNT*COLUMN_COUNT; dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); For (int I =0; i<count; Dispatch_async (globalQueue, ^{[self loadImage:[NSNumber numberWithInt: I]]; }); } } @endCopy the code

Operation effect:

If thread A is already locked, thread B cannot enter. How does B know if the resource has been locked by another thread? You can use the tryLock method, which returns a BOOL if YES, the lock was obtained successfully, otherwise it fails. There is also a lockBeforeData method that specifies a certain amount of time to acquire the lock and also returns a BOOL. YES on success, NO on failure.

@ synchronized code block

Using @synchronized to solve thread synchronization problems is simpler than NSLock, and is recommended for daily development. First, select an object as a synchronization object (typically using self), and then place the “lock code” (read, modify code for contending for resources) into the code block. The code in @synchronized checks to see if the synchronized object is occupied by another thread, and if it is occupied, it waits until the synchronized object is released. The following code shows how to use @synchronized for thread synchronization:

-(NSData *)requestData:(int )index{ NSData *data; NSString *name; @synchronized(self){if (_imageNames. Count >0) {name=[_imageNames lastObject]; [NSThread sleepForTimeInterval: 0.001 f]; [_imageNames removeObject:name]; } } if(name){ NSURL *url=[NSURL URLWithString:name]; data=[NSData dataWithContentsOfURL:url]; } return data; }Copy the code

Extension – Use GCD to solve resource preemption problems

GCD provides a signaling mechanism that also addresses resource preemption (unlike synchronous locking). GCD semaphores are of the dispatch_semaphoRE_T type and support signal notification and signal waiting. Each time a signal notification is sent, the semaphore +1; Semaphore -1, whenever a wait signal is sent; If the semaphore is 0, the signal will wait until the semaphore is greater than 0. According to this principle we can initialize a semaphore variables, the semaphore is set to 1, whenever there is a thread into the lock code “after the call signal for order (the semaphore 0) began to wait for, at this time other threads cannot enter, after the execution to send signals to (semaphore is 1) at this time, other threads into execution, This achieves thread synchronization purposes.

// // GCD multithreading -- Message signals // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (C) 2014 Kenshin Cui. All rights reserved. // #import "KCMainViewController.h" #import "KCImageData.h" #define ROW_COUNT 5 #define COLUMN_COUNT 3 #define ROW_HEIGHT 100 #define ROW_WIDTH ROW_HEIGHT #define CELL_SPACING 10 #define IMAGE_COUNT 9 @interface KCMainViewController (){ NSMutableArray *_imageViews; NSLock *_lock; dispatch_semaphore_t _semaphore; } @implementation KCMainViewController - (void)viewDidLoad {[super viewDidLoad]; [self layoutUI]; } #pragma mark -(void)layoutUI{imageviews =[NSMutableArray array]; for (int r=0; r<ROW_COUNT; r++) { for (int c=0; c<COLUMN_COUNT; c++) { UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)]; imageView.contentMode=UIViewContentModeScaleAspectFit; [self.view addSubview:imageView]; [_imageViews addObject:imageView]; } } UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect]; button.frame=CGRectMake(50, 500, 220, 25); [button setTitle: @ "the image" forState: UIControlStateNormal]; / / add methods [button addTarget: self action: @ the selector (loadImageWithMultiThread) forControlEvents: UIControlEventTouchUpInside]; [self.view addSubview:button]; _imageNames=[NSMutableArray array]; for (int i=0; i<IMAGE_COUNT; i++) { [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]]; } /* The initial semaphore parameter is the initial semaphore value */ _semaphore=dispatch_semaphore_create(1); } #pragma mark -(void)updateImageWithData:(NSData *)data andIndex:(int)index{UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= _imageViews[index]; imageView.image=image; } #pragma mark -(NSData *)requestData:(int)index{NSData *data; NSString *name; /* Dispatch_semaphore_wait (_semaphore, DISPATCH_TIME_FOREVER); /* dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); if (_imageNames.count>0) { name=[_imageNames lastObject]; [_imageNames removeObject:name]; } // Signal dispatch_semaphore_signal(_semaphore); if(name){ NSURL *url=[NSURL URLWithString:name]; data=[NSData dataWithContentsOfURL:url]; } return data; } #pragma mark pragma -(void)loadImage:(NSNumber *)index{int I =[index integerValue]; NSData *data= [self requestData: I]; Dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ [self updateImageWithData:data andIndex:i]; }); } #pragma mark loadImageWithMultiThread{int count=ROW_COUNT*COLUMN_COUNT; // dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); Dispatch_queue_t queue=dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT); for (int i=0; i<count; i++) { dispatch_async(queue, ^{ [self loadImage:[NSNumber numberWithInt:i]]; }); } } @endCopy the code

The effect is the same as before using the synchronization lock.

Extension – Controls thread communication

Because thread scheduling is transparent, it is sometimes difficult to control it effectively. To solve this problem, iOS provides NSCondition to control thread communication (similar to the previous GCD signal mechanism). NSCondition implements the NSLocking protocol, so it also has lock and unlock methods, so it can also be used as NSLock to solve the thread synchronization problem. At this time, the use method is no different from NSLock, as long as the thread is locked at the beginning and the lock is released after obtaining resources. This part is a little bit easier and I’m not going to show it here. Of course, simply solving the thread synchronization problem is not the main purpose of NSCondition design. What NSCondition is more important is solving the scheduling relationship between threads (of course, locks and unlocks must be added and unlocked first in this process). NSCondition can call the WATI method to keep a thread in wait state until another thread calls signal (which wakes up one thread, or any one if there are multiple threads waiting) or broadcast (which wakes up all waiting threads).

Assuming that the current imageNames does not have any images and that the entire interface can load 15 images (none of which can be repeated), now create 15 threads to load images from the imageNames. Since there are no images in imageNames, all 15 threads are in a waiting state, and the loading of images can only continue when the image-creation method is called to add images to imageNames (one at a time) and other threads wake up (in this case, only one thread is awakened). Thus, each time an image is created, a thread is awakened to load it, which is a typical producer-consumer pattern. NSCondition is used to control this process:

KCMainViewController.h

// // kcmainViewController.h // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (C) 2014 Kenshin Cui.  All rights reserved. // #import <UIKit/UIKit.h> @interface KCMainViewController : @property (atomic,strong) NSMutableArray *imageNames; #pragma mark currentIndex @property (atomic,assign) int currentIndex; @endCopy the code

KCMainViewController.m

// // Thread Control // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (C) 2014 Kenshin Cui. All Rights reserved. // #import "KCMainViewController.h" #import "KCImageData.h" #define ROW_COUNT 5 #define COLUMN_COUNT 3 #define  ROW_HEIGHT 100 #define ROW_WIDTH ROW_HEIGHT #define CELL_SPACING 10 #define IMAGE_COUNT 9 @interface KCMainViewController (){ NSMutableArray *_imageViews; NSCondition *_condition; } @implementation KCMainViewController #pragma mark - (void)viewDidLoad {[super viewDidLoad]; [self layoutUI]; } # imageviews =[NSMutableArray array];} # imageviews =[NSMutableArray array]; for (int r=0; r<ROW_COUNT; r++) { for (int c=0; c<COLUMN_COUNT; c++) { UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)]; imageView.contentMode=UIViewContentModeScaleAspectFit; [self.view addSubview:imageView]; [_imageViews addObject:imageView]; } } UIButton *btnLoad=[UIButton buttonWithType:UIButtonTypeRoundedRect]; btnLoad.frame=CGRectMake(50, 500, 100, 25); [btnLoad setTitle: @ "the image" forState: UIControlStateNormal]; [btnLoad addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btnLoad]; UIButton *btnCreate=[UIButton buttonWithType:UIButtonTypeRoundedRect]; btnCreate.frame=CGRectMake(160, 500, 100, 25); [btnCreate setTitle: @ "create pictures" forState: UIControlStateNormal]; [btnCreate addTarget:self action:@selector(createImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btnCreate]; _imageNames=[NSMutableArray array]; _condition=[[NSCondition alloc]init]; _currentIndex=0; } #pragma mark -(void)createImageName{[_condition lock]; If (_imageNames. Count >0) {NSLog(@"createImageName wait, current:% I ",_currentIndex); [_condition wait]; }else{ NSLog(@"createImageName work, current:%i",_currentIndex); // the producer, Produce one image at a time [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",_currentIndex++]]; [_condition signal]; } [_condition unlock]; } # pragma mark the image and display the picture the interface - (void) loadAnUpdateImageWithIndex: (int) index data = {/ / request data NSData * [the self requestData:index]; Dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= _imageViews[index]; imageView.image=image; }); } #pragma mark -(NSData *)requestData:(int)index{NSData *data; NSString *name; name=[_imageNames lastObject]; [_imageNames removeObject:name]; if(name){ NSURL *url=[NSURL URLWithString:name]; data=[NSData dataWithContentsOfURL:url]; } return data; } #pragma mark pragma -(void)loadImage:(NSNumber *)index{int I =(int)[index integerValue]; [_condition lock]; If (_imageNames. Count >0) {NSLog(@"loadImage work,index is % I ", I); [self loadAnUpdateImageWithIndex:i]; [_condition broadcast]; }else{ NSLog(@"loadImage wait,index is %i",i); NSLog(@"%@",[NSThread currentThread]); [_condition wait]; NSLog(@"loadImage resore,index is %i",i); / / once created the pictures immediately load [self loadAnUpdateImageWithIndex: I]; } // unlock [_condition unlock]; } # pragma mark - UI calls the method # pragma mark asynchronous create a picture link - (void) createImageWithMultiThread {dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); Dispatch_async (globalQueue, ^{[self createImageName]; }); } #pragma mark loadImageWithMultiThread{int count=ROW_COUNT*COLUMN_COUNT; dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (int i=0; i<count; Dispatch_async (globalQueue, ^{[self loadImage:[NSNumber numberWithInt: I]]; }); } } @endCopy the code

Operation effect:

In the code above, the loadImage: method is the consumer, which creates 15 consumer threads when “loadImage” is clicked in the interface. In this process, each thread will be locked after entering the image loading method. After locking, other processes cannot enter the “lock code”. But the first thread goes into the “lock code” to load the image and finds that there aren’t any images, so it has to wait. Once the NSCondition wait method is invoked, other threads can continue to access the lock code. (NSLock, @synchronized) No other thread can enter the “lock code” as long as it is not unlocked) while the first thread is in the wait queue (it is not unlocked at this time). The second thread enters the wait state as the first thread does, and the third thread enters the wait state. And so on until the fifteenth thread is also waiting. The createImageName method, which is a producer, creates an image link and puts it in imageNames. The NSCondition signal method is called to select a thread from the conditional wait queue. If thread A is started, the thread will continue to execute. In the above code, the wati method will continue to perform the image loading method, so the image loading method will continue after thread A starts, of course, the image can be successfully loaded. After loading the image, thread A releases the lock, and the entire thread completes its task. Click the “Create Image” button again and repeat the previous steps to load the other images.

To illustrate the above process, here is a flow chart. The blue part of the flow chart represents 15 threads loading images, and the green part represents threads creating image resources.

 

Other locks in iOS

In iOS development, there are some other lock types that are sometimes used in addition to synchronous locks. Here’s a brief overview:

NSRecursiveLock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock: recursivelock A recursive lock can be acquired repeatedly in a thread without causing a deadlock. The number of times the lock is acquired and released is recorded, and the lock is finally released only when the two balance.

NSDistributedLock: A distributed lock, which is itself a mutex. The lock mechanism is based on files and can be accessed across processes.

Pthread_mutex_t: Synchronization lock. The synchronization lock mechanism based on THE C language is similar to other synchronization lock mechanisms.

Tip: in the development process unless must use lock, otherwise should not use lock as far as possible, because multithreaded development itself is to improve the program execution order, and synchronization lock itself can only be executed by a process, which inevitably reduces the execution efficiency.

conclusion

1> No matter which method is used for multi-threaded development, each thread does not necessarily execute the corresponding operation immediately after it is started, and the specific time is scheduled by the system (it will execute when the CPU is idle).

2> UI updates should be made in the main thread (UI thread), and synchronous calls are recommended. Common methods are as follows:

  • – (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait (or -(void)performSelector:(SEL)aSelector onThread:(NSThread *) THR withObject:(id)arg waitUntilDone:(BOOL) wait; [NSThread mainThread];
  • [NSOperationQueue mainQueue] addOperationWithBlock:
  • dispatch_sync(dispatch_get_main_queue(), ^{})

3>NSThread is suitable for lightweight multi-threaded development. It is difficult to control the thread order, and the total number of threads can not be controlled (each creation cannot reuse the previous thread, only a new thread can be created).

4> It is recommended to use NSObject’s extension method rather than NSThread for simple multithreaded development.

You can use the currentThread method of NSThread to get the currentThread and sleepForTimeInterval: method to get the currentThread to sleep.

6>NSOperation can control the total number of threads and thread dependencies.

7> Creating an NSOperation should not call the start method directly (it would be called in the main thread if it were directly started) but should be started in NSOperationQueue.

8> Compared with NSInvocationOperation, it is recommended to use NSBlockOperation. The code is simple, and there is no parameter passing problem due to closure.

9>NSOperation is an object-oriented ObjC encapsulation of GCD, but it is more efficient than GCD development based on C language. It is recommended to choose NSOperation first if there is a dependency between tasks or you want to monitor the completion status of tasks. Otherwise, use GCD.

10> Tasks in serial queues in GCD are arranged to be executed on a single thread (not the main thread), allowing easy control of the execution order; Concurrent queues are executed in multiple threads (if asynchronous methods are used), and order control is relatively complex, but more efficient.

11> Whether an operation in GDC is multithreaded or single-threaded depends on the current queue type and execution method. It can only be executed in multiple threads if the queue type is parallel and the execution method is asynchronous (or in the main thread if the parallel queue uses synchronous method calls).

12> @synchronized is simpler than NSLock and recommended.

Articles you may be interested in

IOS Development Series – An overview of IOS Application development

IOS Development Series – Endless loop image browser

IOS development series -UITableView full parsing

IOS Development Series – View Switching

IOS development series – Touch event, gesture recognition, shake event, headset wire control

IOS Development Series — Create your own “Meitu Xiu Xiu”

IOS Development Series – Get your apps moving

IOS Technology Exchange Group

In order to facilitate communication, welcome to join the iOS development group: 64555322(full)

The group above is full, suggest adding: 132785059

Source code download

This work adoptsCreative Commons Attribution 2.5 License agreement in Mainland ChinaReproduced, reproduced, interpreted or used for commercial purposes. But reprint please indicate fromKenshinCuiAnd contains related links.