Concurrent programming recommended article objC ChinaConcurrent programming API challenges

pthread

POSIX Threads, or Pthreads for short, is the POSIX standard for threads. The standard defines a set of apis for creating and manipulating threads. On UniX-like operating systems (Unix, Linux, Mac OS X, etc.), Pthreads are used as operating system threads. Is a set of C language written thread library.

Include its header file first

#import <pthread.h>
Copy the code
- (void)viewDidLoad {
    [super viewDidLoad];
    [self createPThread];
}

- (void)createPThread{
    pthread_t pthreadID;
    NSString *param = @"piaojin"; Int result = pthread_create(&pthreaDID, NULL, &cfunc, &pthreadid) (__bridge void *)(param));printf("%d",result);
}

void * cFunc(void *param){
    NSLog(@"%@,param:%@",[NSThread currentThread],param);
    return NULL;
}
Copy the code

If you look at the code, you can see that it needs c functions, which is a little bit more painful, but even more painful is that you have to manually handle the transitions between the states of the thread and manage the life cycle. For example, this code creates a thread, but it doesn’t destroy it.

NSThread

The solution is packaged by Apple and fully object-oriented. So you can manipulate thread objects directly, which is very intuitive and convenient. However, its life cycle still needs to be managed manually, so this scheme is also used occasionally, such as [NSThread currentThread], it can get the currentThread class, you can know the various properties of the currentThread, very convenient for debugging. Here’s a look at some of its uses.

Create and start

Create the thread class first and then start it

// create NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run: object:nil); // start [thread start]; [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil]; / / use NSObject methods to create and automatically start [self performSelectorInBackground: @ the selector (run) withObject: nil]; In addition to creating and starting nsthreads, there are many other methods that nsthreads use. Here are a few common methods. // Cancel the thread - (void)cancel; // Start thread - (void)start; Thread state @property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled; // Set and get the thread name -(void)setName:(NSString *)n; -(NSString *)name; + (NSThread *)currentThread; + (NSThread *)mainThread; + (void)sleepForTimeInterval:(NSTimeInterval)time; + (void)sleepForTimeInterval:(NSTimeInterval)time; + (void)sleepUntilDate:(NSDate *)date;Copy the code

GCD

Grand Central Dispatch. It sounds like it. It is apple multicore parallel computing solutions, so automatically reasonably use more CPU kernel, such as dual-core, quad-core), the most important thing is that it will automatically manage the life cycle of a thread (create threads, thread scheduling tasks, destroy), without the need for our management, we just need to tell what to do. It also uses C, but the use of blocks (called closures in Swift) makes it easier and more flexible to use. So basically everyone uses the GCD program, suitable for the old and the young, it is really a necessary medicine for family travel, killing. Sorry, it’s a bit of a double. Let’s move on.

Tasks and queues

In GCD, two very important concepts have been added: tasks and queues.

Task: Action, what you want to do, is basically a piece of code, and in GCD it’s a Block, so it’s easy to add tasks. Tasks can be executed in two ways: synchronous and asynchronous, and the difference between them is whether a new thread is created.

The main difference between sync and async is that it blocks the thread until the task in the Block completes! In the case of sync, it blocks the current thread and waits for tasks in the Block to complete before the current thread can continue running. If an async operation is performed, the current thread will proceed directly, and it will not block the current thread.

Queue: Used to store tasks. There are two types of queues, serial queues and parallel queues.

For tasks placed in a serial queue, GCD FIFO retrieves one, executes one, then removes the next, and so on.

The GCD also fetches tasks in FIFO when they are placed in a parallel queue, but the difference is that it fetches one and puts it in another thread, and then fetches another and puts it in another thread. So, because I’m doing it so fast, I’m ignoring it, and it looks like all the tasks are being done together. Note, however, that GCD controls the amount of parallelism based on system resources, so if there are many tasks, it will not execute them all at once.

// sync task + queue (task is still synchronized, one by one) // Create a concurrent queue DISPATCH_QUEUE_CONCURRENT (DISPATCH_QUEUE_SERIAL,DISPATCH_QUEUE_PRIORITY_DEFAULT, dispatch_queue_t) queue = dispatch_queue_create("Synchronous task + Concurrent queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        for(int i = 0; i < 5; i++){ NSLog(@"currentThread:%@,->1<-%d",[NSThread currentThread],i); }}); dispatch_sync(queue, ^{for(int i = 0; i < 5; i++){ NSLog(@"currentThread:%@,->2<-%d",[NSThread currentThread],i); }}); dispatch_sync(queue, ^{for(int i = 0; i < 5; i++){ NSLog(@"currentThread:%@,->3<-%d",[NSThread currentThread],i); }}); } // async (async) {// async (async) { // Create a concurrent queue DISPATCH_QUEUE_CONCURRENT (DISPATCH_QUEUE_SERIAL,DISPATCH_QUEUE_PRIORITY_DEFAULT, dispatch_queue_t) queue = dispatch_queue_create("Asynchronous task + Serial queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        for(int i = 0; i < 5; i++){ NSLog(@"currentThread:%@,->1<-%d",[NSThread currentThread],i); }}); dispatch_async(queue, ^{for(int i = 0; i < 5; i++){ NSLog(@"currentThread:%@,->2<-%d",[NSThread currentThread],i); }}); dispatch_async(queue, ^{for(int i = 0; i < 5; i++){ NSLog(@"currentThread:%@,->3<-%d",[NSThread currentThread],i); }}); }#define ImageUrl @"http://upload-images.jianshu.io/upload_images/3362328-bdcbda41eac5e8a2.png? imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"// Communication between threads,NSThread using PerformSelector series method - (void)communication{__weak Typeof (self) weakSelf = self; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:ImageUrl]]; ^{NSData dataWithContentsOfURL:[NSURL URLWithString:ImageUrl]]; UIImage *image = [UIImage imageWithData:imageData]; // dispatch_async(dispatch_get_main_queue(), ^{// Update UI weakself.imageview.image = image; }); }); } // delay - (void)delay{//NSObject delay method [self performSelector:@selector(run) withObject:nil afterDelay:2.0]; // Dispatch_after (dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), dispatch_get_main_queue(), ^{ [self run]; }); / / use a timer can also achieve the effect of the delay time repeats to set to NO, executed only once, use scheduledTimerWithTimeInterval method to create a timer will default to add to the current RunLoop, and start [NSTimer ScheduledTimerWithTimeInterval: 2.0 target: self selector: @ the selector (run) the userInfo: nil repeats: NO]; } - (void)run{ NSLog(@"Delay two seconds"); } // One time operation - (void)once{static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"One-time operation"); }); } // Multiple iterations - (void)apply{/** *size_t iterations Number of times **/ dispatch_apply(10, Dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index)"%zu",index); }); } // Queue group operation - (void)group{dispatch_group_t group = dispatch_group_create(); Dispatch_group_async (group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{for (int i = 0; i < 100; i++) {
            NSLog(@"queue1:%d",i); }}); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{for (int i = 0; i < 100; i++) {
            NSLog(@"queue2:%d",i); }}); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"queue3"); }); // Execute block code dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"All three missions accomplished!");
    });
}

Copy the code

If the dispatch_group_async block executes an asynchronous task, then the dispatch_group_notify block executes an asynchronous task before the dispatch_group_notify block executes an asynchronous task. Use dispatch_group_enter and dispatch_group_leave to solve this problem. Dispatch_group_enter (group) before the command is executed and dispatch_group_leave(group) after the command is executed.

NSOperation and NSOperationQueue

NSOperation is apple’s version of GCD, completely object-oriented, so it’s easier to understand. You can see that NSOperation and NSOperationQueue correspond to GCD tasks and queues respectively

NSOperation is an abstract class that does not encapsulate operations. You must use a subclass of NSOperation:

  • NSInvocationOperation

  • NSBlockOperation

  • Custom subclasses inherit NSOperation, which is an abstract class that overrides the corresponding internal method (main method).

The steps are also easy to understand:

  • Encapsulate the operations to be performed into an NSOperation object

  • Add the NSOperation object to the NSOperationQueue

  • The system will automatically fetch the NSOperation from the NSOperationQueue

  • Put the extracted NSOperation encapsulated operations into a thread for execution

The role of NSOperationQueue

If NSOperation is added to NSOperationQueue, NSOperation is executed asynchronously by default

Add NSOperation to NSOperationQueue

  • Call the addOperation: method

  • Call the addOperationWithBlock: method

NSOperation @implementation PJCustomOperation - (void)main{NSLog(@)"PJCustomOperation:%@",[NSThread currentThread]);
}

@end
Copy the code

Not added to NSOperationQueue

//NSInvocationOperation - (void)invocationOperation{//NSInvocationOperation is executed by default in the main thread, and no new thread is created performSelector:@selector(run) withObject:nil]; NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil]; // Start [invocationOperation start]; } //NSBlockOperation - (void)blockOperation{//NSBlockOperation is executed in the main thread by default, and no new thread is created performSelector:@selector(run) withObject:nil]; NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"blockOperation1%@",[NSThread currentThread]); }]; // Add additional tasks to start a new thread,number! = 1, complete in a new thread [blockOperation addExecutionBlock:^{NSLog(@"blockOperation2%@",[NSThread currentThread]); }]; [blockOperation start]; - (void)customOperation{// PJCustomOperation is not started in the main thread *customOperation = [[PJCustomOperation alloc] init]; [customOperation start]; } - (void)run{ NSLog(@"Execute task %@",[NSThread currentThread]);
}

Copy the code

Add to NSOperationQueue

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{// create a queue, NSOperationQueue *queue =  [[NSOperationQueue alloc] init]; //GCD queue form // global main queue serial queue parallel queue //NSOperationQueue queue form // Main queue self-created queue // create NSOperation NSInvocationOperation *invocationOperation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"invocationOperation1"]; / / in the queue, will be executed without display to [invocationOperation start], will create a new thread, asynchronous execution [queue addOperation: invocationOperation1]; NSInvocationOperation *invocationOperation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"invocationOperation2"]; / / in the queue, will be executed without display to [invocationOperation start], will create a new thread, asynchronous execution [queue addOperation: invocationOperation2]; Queue addOperationWithBlock:^{NSLog(@); // A new thread is created asynchronously"AddOperationWithBlock of the form: Thread :%@",[NSThread currentThread]);
    }];

}

- (void)run:(NSString *)name{
    NSLog(@"name:%@,thread:%@",name,[NSThread currentThread]);
}
Copy the code

NSOperation Communication and dependencies

- (void)queueMessage{// Use NSOperation and NSOperationQueue to implement __weak Typeof (self) weakSelf = self; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{// Time-consuming operation NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:ImageUrl]]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{Weakself. imageView.image = [UIImage imageWithData:imageData]; }]; }]; (void)dependency{NSOperationQueue *queue = [[NSOperationQueue alloc] init]; (void)dependency{NSOperationQueue *queue = [NSOperationQueue alloc] init]; NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation1:%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation2:%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation3:%@",[NSThread currentThread]); }]; // Do not create dependency loops A -> B,B -> C,C -> A between executing tasks of the former (operation1) [operation1 addDependency:operation2]; [queue addOperations:@[operation1,operation2,operation3]waitUntilFinished:NO];
}
Copy the code

NSOperation Number of concurrent and pending operations

Maximum concurrency

A large number of open threads increases the CPU workload and reduces the number of times each thread is called. To reasonably improve CPU efficiency, we can set the maximum number of concurrent queues.

  • Can pass for maxConcurrentOperationCount attribute assignment, maxConcurrentOperationCount the default value is 1, the unlimited number of concurrent. When set, the queue becomes a serial queue.

@interface ViewController ()

@property (nonatomic, strong)NSOperationQueue *queue;

@end
Copy the code
/ / maximum number of concurrent - (void) maxConcurrentOperationsCount {_queue = [[NSOperationQueue alloc] init]; If set to 1, only 2 tasks can be executed concurrently. If set to 1, only 1 and 2 tasks can be executed concurrently. If set to 1, 1, 2, 3, 4 tasks can be executed successively _queue.maxConcurrentOperationCount = 2; [_queue addOperationWithBlock:^{for (int i = 0; i < 5000; i++) {
            NSLog(@"< -- - > 1 - % @",[NSThread currentThread]); }}]; [_queue addOperationWithBlock:^{for (int i = 0; i < 5000; i++) {
            NSLog(@"-- - > 2 < -- - % @",[NSThread currentThread]); }}]; [_queue addOperationWithBlock:^{for (int i = 0; i < 5000; i++) {
            NSLog(@"-- - > 3 < -- - % @",[NSThread currentThread]); }}]; [_queue addOperationWithBlock:^{for (int i = 0; i < 5000; i++) {
            NSLog(@"-- - > 4 < -- - % @",[NSThread currentThread]); }}]; }Copy the code

Queue suspension and cancellation

hang

Suspended: Assign it to YES to suspend, assign it to NO to resume

  • When a thread is in the executing state, setting the queue to hang does not affect its execution, but only those tasks that have not yet been executed

  • When a queue is set to the pending state, you can modify its state to resume the task again

cancel

CancelAllOperations cancelAllOperations cannot be cancelled

If the NSOperation type is user-defined during a task operation, you need to add one to determine whether to cancel the task after performing a time-consuming operation, and then perform another time-consuming operation. Also cancelling does not affect the current executing thread, subsequent threads will be cancelled.

// suspend and cancel - (void)suspendedAndCancel{// SuspendedTasks are not affected, pending tasks are suspended, and future tasks can be restored // [self.queuesetSuspended:! self.queue.suspended]; Queue cancelAllOperations [self.queue cancelAllOperations]; } - (void)customOperation{ NSOperationQueue *queue = [[NSOperationQueue alloc] init]; PJCustomOperation *customOperation1 = [[PJCustomOperation alloc] init]; [queue addOperation:customOperation1]; self.queue = queue; }Copy the code
@implementation PJCustomOperation - (void)main{implementation PJCustomOperation - (void)main{implementation PJCustomOperation - (void)main{implementation PJCustomOperation - (void)mainif(self.isCancelled){
        return; } // simulate multiple time-consuming operations in the same threadfor (int i = 0; i < 50000; i++) {
        NSLog(@"--->PJCustomOperation1<---%@",[NSThread currentThread]); } // If the task is cancelled, the current thread is not affected by the cancellation of the queue, so if the thread has multiple time-consuming operations, it will continue to proceedif(self.isCancelled){
        return;
    }
    
    for (int i = 0; i < 50000; i++) {
        NSLog(@"--->PJCustomOperation2<---%@",[NSThread currentThread]); } // If the task is cancelled, the current thread is not affected by the cancellation of the queue, so if the thread has multiple time-consuming operations, it will continue to proceedif(self.isCancelled){
        return;
    }
    
    for (int i = 0; i < 50000; i++) {
        NSLog(@"--->PJCustomOperation3<---%@",[NSThread currentThread]); } // If the task is cancelled, the current thread is not affected by the cancellation of the queue, so if the thread has multiple time-consuming operations, it will continue to proceedif(self.isCancelled){
        return;
    }
    
    for (int i = 0; i < 50000; i++) {
        NSLog(@"--->PJCustomOperation4<---%@",[NSThread currentThread]); }}Copy the code