IOS multithreading technology solution

Technical solution/Frequency of use Introduction to the language Thread life cycle
Pthread/ hardly A set of multithreaded apis for Unix/Linux/Windows

Cross-platform, portable and difficult to use

The C language Programmer management
NSthread/ Occasionally used Use more object-oriented

Easy to use, direct manipulation of thread objects

This is essentially the encapsulation of pThreads

OC language Programmer management
GCD/ Used a lot Threading technology that replaces NSThreads

Take full advantage of multi-core equipment

The C language Automatic management
NSOperation/ Frequently used GCD based (bottom is GCD)

More than GCD some more simple and practical functions

Use more object-oriented

OC language Automatic management

Pthread

// (void)touch began :(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{// create pthread_t pthread; Void * (*) (void *) // 4) Pthread_CREATE (pthread_t _Nullable * RESTRICT _Nonnull, const pthread_attr_t *restrict _Nullable, void * _Nullable (* _Nonnull)(void * _Nullable), void *restrict _Nullable) int result = pthread_create(&pthread, NULL, demo, NULL); // result 0: success,! If (result == 0) {NSLog(@" success "); } else {NSLog(@" failed "); } } void *demo(void *param){ NSLog(@"hello %@", [NSThread currentThread]); return NULL; }Copy the code

// (void)touchesBegan:(NSSet< uittouch *> *)touches withEvent:(UIEvent *)event{pthread_t pthread; NSString *name = @"xyh"; int result = pthread_create(&pthread, NULL, demo, (__bridge void *)(name)); If (result == 0) {NSLog(@" success "); } else {NSLog(@" failed "); } } void *demo(void *param){ NSString *name = (__bridge NSString *)param; NSLog(@"hi %@", name); NSLog(@"hello %@", [NSThread currentThread]); return NULL; }Copy the code

NSThread

The most common way to look at threads for NSthreads is NSThread currentThread.

Thread creation

  • A:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
[thread start];
Copy the code
  • Method 2:
[NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];
Copy the code
  • Three:
NSThread *thread = [[NSThread alloc] initWithBlock:^{
   NSLog(@"%@", [NSThread currentThread]);
}];
[thread start];
Copy the code

Why does a method of creating a thread that can be solved in one sentence spawn method one and method three? Just because you can get the state of the thread and do something else.

Thread states (New, ready, Running, blocked, dead)

When calling thread start method, means that the thread object is in the ready state, as long as the thread object is in the ready state, then the thread object is put into the schedulable thread pool, in the ready state to a running state is performed by the CPU for quick jump, the execution of my threads shall be kept in memory, the next time jump to continue; When a thread object is blocked, the thread object is removed from the schedulable thread pool to memory, and when it is ready, it is added to the schedulable thread pool again, and then waits to be scheduled for execution.

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil]; ThreadPriority: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService: qualityOfService // -(BOOL)isMainThread; // +(BOOL)isMainThread; [thread start]; - (void)demo { for (int i = 0; i < 20; i++) { NSLog(@"%d", i); If (I == 5) {NSThread sleepForTimeInterval:3]; } if (I == 10) {// thread exits the dead state [NSThread exit]; }}}Copy the code

Thread-safety issues (perennial read-write issues)

Read and write operations on the same thread when accessing a resource with a mutex, prevent data chaos caused by the multiple threads to read and write data at the same time, that is, data security issues, the role of the mutex is in order to ensure thread synchronization, at the same time lock will affect the performance of the program, but in order to guarantee the correctness of the data is also need to lock the mutex locking code as little as possible.

#import "viewController.h" @interface ViewController () @property (nonatomic, assign) int ticketsCount; @property (nonatomic, strong) NSObject *obj; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.ticketsCount = 10; self.obj = [NSObject new]; } - (void)touchesBegan:(NSSet< touch *> *)touches withEvent:(UIEvent *)event [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil]; [thread1 start]; NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil]; [thread2 start]; } // Thread is unsafe // Simulate the method of selling tickets - (void)sellTickets {while (YES) {// Simulate the elapsed time [NSThread sleepForTimeInterval:1.0]; Synchronized (self.obj) {if (self.ticketscount > 0) {if (self.ticketscount > 0) { self.ticketsCount = self.ticketsCount - 1; NSLog(@" remaining %d tickets ", self.ticketscount); } else {NSLog(@" late, tickets are gone "); break; } } } } @endCopy the code

GCD

  • The two cores of the COMMUNIST Party

2. Queue: Used to store tasks

  • Two steps used by the COMMUNIST Party

2. Add the task to the queue – GCD will automatically take out the task in the queue and put it into the corresponding thread for execution – Task extraction follows the QUEUE FIFO principle, first in, first out, last in, last out

  • GCD’s execution task function

Dispatch_sync (dispatch_queue_t _Nonnull queue, ^(void)block) dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block)

  • GCD queue type

(DISPATCH_QUEUE_CONCURRENT) – Allows multiple tasks to be executed concurrently (automatically enabling multiple threads to execute tasks simultaneously) – Concurrency only works with asynchronous functions (dispatch_asyc) – Execute tasks one by one (when one is finished, Dispatch_get_main_queue () dispatch_get_global_queue(0, 0)

  1. Serial queues execute synchronouslyDo not open thread order execution
- (void)demo1 {dispatch_queue_t queue = dispatch_queue_create("xyh", DISPATCH_QUEUE_SERIAL); for (int i = 0; i < 10; i++) { dispatch_sync(queue, ^{ NSLog(@"hello %d %@", i, [NSThread currentThread]); }); }}Copy the code

  1. Serial queues execute asynchronouslyNew threads are opened and executed sequentially
- (void)demo2 {dispatch_queue_t queue = dispatch_queue_create("xyh", DISPATCH_QUEUE_SERIAL); for (int i = 0; i < 10; i++) { dispatch_async(queue, ^{ NSLog(@"hello %d %@", i, [NSThread currentThread]); }); }}Copy the code

  1. Parallel queues execute synchronouslyDo not open thread order execution
- (void)demo3 {dispatch_queue_t queue = dispatch_queue_create("xyh", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i < 10; i++) { dispatch_sync(queue, ^{ NSLog(@"hello %d %@", i, [NSThread currentThread]); }); }}Copy the code

4. The parallel queue executes asynchronously but the new thread executes in disorder

- (void)demo4 {dispatch_queue_t queue = dispatch_queue_create("xyh", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i < 10; i++) { dispatch_async(queue, ^{ NSLog(@"hello %d %@", i, [NSThread currentThread]); }); }}Copy the code

5. The deadlock is synchronized to the primary queue

- (void)demo5 { NSLog(@"begin"); for (int i = 0; i < 10; I ++) {dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"hello %d %@", I, [NSThread currentThread]); }); } NSLog(@"end"); }Copy the code

Deadlock cause: The main queue has a characteristic that when the main queue is executing the code, it does not schedule the task temporarily and will execute the task after the main queue finishes executing the code. When the main queue is executing the above code, the main queue will schedule the task after the main queue finishes executing the code. In the synchronous execution operation, if a task is not executed, Wait for the first task to complete and then execute the next task. In this case, the program cannot continue to execute.

6. The main queue executes the main thread asynchronously and sequentially

- (void)demo6 { for (int i = 0; i < 10; i++) { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"hello %d %@", i, [NSThread currentThread]); }); }}Copy the code

7. Operations performed on global queues and parallel queues are not described here

8.GCD queue - Perform summary

9.Barrier functions

  • After the completion of multiple asynchronous operations, non-thread-safe objects are uniformly updated
  • Suitable for large-scale I/O operations
  • When accessing a database or file, it can be used that the follow new data cannot be performed at the same time as other follow new or read operationsdispatch_barrier_asyncTo solve
- (void)downloadImage:(int)index {dispatch_queue_t queue = dispatch_queue_create("xyh", DISPATCH_QUEUE_CONCURRENT); Dispatch_async (queue, ^{NSString *fileName = [NSString stringWithFormat:@"%02d.jpg", index % 10 + 1]; NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil]; UIImage *img = [UIImage imageWithContentsOfFile:path]; Dispatch_barrier_async (queue, ^{[self.photolist addObject:img]; NSLog(@" save image %@ %@", fileName, [NSThread currentThread]); }); NSLog(@" image download completed %@ %@", fileName, [NSThread currentThread]); }); }Copy the code

10. Delayed operation (dispatch_after())

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"task"); // Prints after 2 seconds});Copy the code
  1. One-time execution (dispatch_once())

One-time execution is thread-safe, and there is a good mechanism for using singletons in creating a single column that is somewhat similar to lazy loading, where there is only one copy in the class, and singletons, where there is only one copy in the entire project

@interface NetworkTools: NSObject // singleton method + (instanceType)sharedNetworkTools; + (instancetype)sharedNetworkToolsOnce; @end #import "NetworkTools.h" @implementation NetworkTools + (instancetype)sharedNetworkTools { static id instance = nil; @synchronized (self) {if (instance == nil) {instance = [[self alloc] init]; } } return instance; } + (instancetype)sharedNetworkToolsOnce { static id instance = nil; // dispatch_once thread safe static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (instance == nil) { instance = [[self alloc] init]; }}); return instance; } @ the end / / * * * * * * * * * * * * * * * * * * * * * * * tactical line * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - (void) shareDemo {CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < 10000; i++) { [NetworkTools sharedNetworkTools]; } CFAbsoluteTime end = CFAbsoluteTimeGetCurrent(); NSLog(@" lock %f", end-start); } - (void)shareDemoOnce { CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < 10000; i++) { [NetworkTools sharedNetworkToolsOnce]; } CFAbsoluteTime end = CFAbsoluteTimeGetCurrent(); NSLog(@"once%f", end - start); }Copy the code

One-off singletons are more efficient than mutex singletons

12. Dispatch group –dispatch_group_t Dispatch group –dispatch_group_async() asynchronous execution of dispatch_group_notify() –dispatch_group_enter() Dispatch_group_leave () Dispatch_group_leave () Dispatch_group_leave () Scheduling small number a set of tasks – dispatch_group_wait () task scheduling group into the number 0

- (void)demo1 {dispatch_group_t group = dispatch_group_create(); Dispatch_queue_t queue = dispatch_get_global_queue(0, 0); Dispatch_group_async (group, queue, ^{NSLog(@" downloading first song "); [NSThread sleepForTimeInterval: 1.0]; }); / / * * * * * * * * * * * * * * equivalent above * * * * * * * * * * * * * / / dispatch_group_enter (group); // dispatch_async(queue, ^{NSLog(@" downloading first song ")); / / [NSThread sleepForTimeInterval: 1.0]; // dispatch_group_leave(group); / /}); Dispatch_group_async (group, queue, ^{NSLog(@" downloading second song "); [NSThread sleepForTimeInterval: 2.0]; }); Dispatch_group_async (group, queue, ^{NSLog(@" downloading the third song "); [NSThread sleepForTimeInterval: 3.0]; }); // When all three asynchronous tasks are completed, Dispatch_group_notify (dispatch_get_main_queue(), ^{NSLog(@"over %@", [NSThread currentThread]); }); }Copy the code

13. Dispatch_semaphore

  • Keep thread synchronization and convert asynchronous execution tasks to synchronous execution tasks
  • Keep the thread safe and lock the thread

Dispatch_semaphore_create (long value)

  • Vale is 0 and only creates semaphores greater than 0 and creates and sends value semaphores

Dispatch_semaphore_signal (dispatch_semaphore_t dSEMa)

  • The signal count increases by 1

Dispatch_semaphore_wait (dispatch_semaphore_t dsema, dispatch_time_t timeout)

  • Signal count minus 1, wait time
- (void)waitThreadWithSemaphore { dispatch_queue_t currentQueue = dispatch_queue_create("current queue", DISPATCH_QUEUE_CONCURRENT); dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); dispatch_async(currentQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); [NSThread sleepForTimeInterval:5]; NSLog(@" enter password %@", [NSThread currentThread]); dispatch_semaphore_signal(semaphore); }); dispatch_async(currentQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@" verify password %@", [NSThread currentThread]); dispatch_semaphore_signal(semaphore); }); dispatch_async(currentQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@" login successful %@", [NSThread currentThread]); dispatch_semaphore_signal(semaphore); }); NSLog of at sign semaphore; } / / * * * * * * * * * * * * * * * * * * * * * * * tactical line * * * * * * * * * * * * * * * * * * * * * * * * * * * - (void) waitThreadWithSemaphore {dispatch_queue_t currentQueue = dispatch_queue_create("current queue", DISPATCH_QUEUE_CONCURRENT); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_async(currentQueue, ^{ [NSThread sleepForTimeInterval:5]; NSLog(@" enter password %@", [NSThread currentThread]); dispatch_semaphore_signal(semaphore); }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); Dispatch_async (currentQueue, ^{NSLog(@" password %@", [NSThread currentThread]); dispatch_semaphore_signal(semaphore); }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); Dispatch_async (currentQueue, ^{NSLog(@" login successfully %@", [NSThread currentThread]); dispatch_semaphore_signal(semaphore); }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog of at sign semaphore; }Copy the code

NSOperation

NSOperation role

  • The OC language is a GCD-based package, which is easier to use
  • Apple recommends it, regardless of threads and their life cycles
  • NSOperation is an abstract class, and you use its subclassesNSInvocationOperation, NSBlockOperation, custom Operation

Subclasses NSInvocationOperation

NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationDemo) object:nil]; NSLog(@" End = %d", op.isFinished); Calling main does not start a new thread [op start]; // do not start a new thread [op start]; [op start]; NSLog(@" End = %d", op.isFinished); - (void)operationDemo { NSLog(@"hello %@", [NSThread currentThread]); }Copy the code

[NSThread detachNewThreadSelector:@selector(detachOperation) toTarget:self withObject:nil];

- (void)detachOperation {
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationDemo) object:nil];
    [op start];
}

- (void)operationDemo {
    NSLog(@"hello %@", [NSThread currentThread]);
}
Copy the code

As you can see from the two examples above, when subclass NSInvocationOperation does not use NSOperationQueue, the operation is executed on the current thread and no new thread is started.

Subclasses NSBlockOperation

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"hello = %@", [NSThread currentThread]);
}];
    [op start];
Copy the code

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 3; i++) { NSLog(@"1---%@", [NSThread currentThread]); } }]; // add operation [op addExecutionBlock:^{for (int I = 0; I < 3; I ++) {NSThread sleepForTimeInterval:2]; NSLog(@"2-- %@", [NSThread currentThread]); } }]; // add operation [op addExecutionBlock:^{for (int I = 0; I < 3; I ++) {[NSThread sleepForTimeInterval:2]; NSLog(@"3-- %@", [NSThread currentThread]); } }]; [op start];Copy the code

If subclass NSBlockOperation does not use NSOperationQueue, it will still complete the operation in the current thread if it does not use addExecutionBlock to add additional operation tasks. Once the addExecutionBlock method is called, it is possible to start a new thread to perform the operation, depending on the number of threads, as determined by the system.

Custom subclass XXXOperation

The custom subclass simply inherits Operation and overrides main. In the absence of NSOperationQueue, the use of NSOperationQueue is the same as that of its two subclasses.

NSOperationQueue

NSOperationQueue has a primary queue ([NSOperationQueue mainQueue]) and a non-primary queue ([NSOperationQueue alloc] init]). Operations added to the primary queue are performed only in the primary queue. The non-primary queue can automatically start the thread to perform the added operation, in addition to controlling whether the operation is executed concurrently or serially.

NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationDemo1) object:nil]; NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationDemo2) object:nil]; NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{NSLog(@" operation 3 = %@", [NSThread currentThread]); }]; NSOperationQueue *queue = [NSOperationQueue mainQueue]; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; - (void)operationDemo1 {NSLog(@" operation 1 = %@", [NSThread currentThread]); } - (void)operationDemo2 {NSLog(@" operation2 = %@", [NSThread currentThread]); }Copy the code

NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationDemo1) object:nil]; NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationDemo2) object:nil]; NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{NSLog(@" operation 3 = %@", [NSThread currentThread]); }]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; - (void)operationDemo1 {NSLog(@" operation 1 = %@", [NSThread currentThread]); } - (void)operationDemo2 {NSLog(@" operation2 = %@", [NSThread currentThread]); }Copy the code

MaxConcurrentOperationCount called maximum concurrency, the duty to 1, the queue can open multiple threads serial implementation, as follows:

NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationDemo1) object:nil]; NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationDemo2) object:nil]; NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{NSLog(@" operation 3 = %@", [NSThread currentThread]); }]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.maxConcurrentOperationCount = 1; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; - (void)operationDemo1 {NSLog(@" operation 1 = %@", [NSThread currentThread]); } - (void)operationDemo2 {NSLog(@" operation2 = %@", [NSThread currentThread]); }Copy the code

Operation dependency addDependency

Adding dependencies between operations ensures that operations are executed in the first order

Op1 = [NSBlockOperation blockOperationWithBlock:^{NSLog(@" id = %@", [NSThread currentThread]);}]; NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{[NSThread sleepForTimeInterval:2]; NSLog(@" password = %@", [NSThread currentThread]); }]; NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{NSLog(@" login = %@", [NSThread currentThread]);}]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // Add operation dependency [op2 addDependency:op1]; [op3 addDependency:op2]; [queue addOperation:op3]; [queue addOperation:op2]; [queue addOperation:op1];Copy the code

Priority queuePriority

Setting the queuePriority for an operation changes the execution priority of the current operation in the same queue and does not replace the operation dependency. Determines the order of start execution between operations that enter the ready state. op3 -> op2 -> op1 op4

At this point, op2 and OP3 have dependencies, but OP1 and OP4 have no dependencies and are in ready state. If the priority of OP1 is higher than that of OP4, the execution sequence is OP1 and then OP4, but the end sequence is not guaranteed.

Interthread communication

[[NSOperationQueue mainQueue] addOperationWithBlock:^{NSLog(@" update UI");}];Copy the code

NSOperation Common attributes and methods

  1. Cancel operation method

    • - (void)cancel;Operations can be cancelled, essentially marking isCancelled status.
  2. Method to determine operation status

    • - (BOOL)isFinished;Check whether the operation is complete.
    • - (BOOL)isCancelled;Determines whether the operation has been marked as cancelled.
    • - (BOOL)isExecuting;Determine whether the operation is running.
    • - (BOOL)isReady;Determines whether an operation is in a ready state. This value depends on the dependency of the operation.
  3. Synchronous operation

    • - (void)waitUntilFinished;Blocks the current thread until the operation is complete. Can be used for sequential synchronization of threads.
    • - (void)setCompletionBlock:(void (^)(void))block; completionBlockExecutes the completionBlock when the current operation completes.
    • - (void)addDependency:(NSOperation *)op;Add a dependency that makes the current operation dependent on the completion of the operation op.
    • - (void)removeDependency:(NSOperation *)op;Remove a dependency to remove the dependency of the current operation on op.
    • @property (readonly, copy) NSArray<NSOperation *> *dependencies;Array of all action objects that are executed before the current operation begins.

Common properties and methods of NSOperationQueue

  1. Cancel/pause/resume action

    • - (void)cancelAllOperations;You can cancel all operations on the queue.
    • - (BOOL)isSuspended;Determines whether the queue is paused. YES indicates the pause state, and NO indicates the recovery state.
    • - (void)setSuspended:(BOOL)b;You can set the pause and resume of an operation. YES stands for pause queue, and NO stands for resume queue.
  2. Synchronous operation

    • - (void)waitUntilAllOperationsAreFinished;Blocks the current thread until all operations in the queue have completed.
  3. Add/get operations

    • - (void)addOperationWithBlock:(void (^)(void))block;Add an operation object of type NSBlockOperation to the queue.
    • - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;Add an array of operations to the queue and wait to block the current thread until all operations are complete
    • - (NSArray *)operations;An array of operations currently in the queue that are automatically cleared when an operation completes.
    • - (NSUInteger)operationCount;Operands in the current queue.
  4. Access to the queue

    • + (id)currentQueue;Gets the current queue, and returns nil if the current thread is not running on NSOperationQueue.
    • + (id)mainQueue;Gets the main queue.

References:

  • # iOS multithreading: exhaustive summary of NSOperation, NSOperationQueue