iOS knowledge combing - multi thread API mathematics

In general, there are four schemes for using multithreading on the iOS platform:

  1. Pthreads interface based on c language, which is POSIX c language is widely used in programming, but it is seldom used in iOS development. The exposed interface is relatively low-level and has perfect functions. Accordingly, it requires programmers to manage the life cycle of threads, which is relatively troublesome to use.
  2. NSThread, the thread interface of objc, can be understood as the object-oriented encapsulation of Pthreads. It is easier to manage after object-oriented, but it still needs to pay a certain price of manual management.
  3. GCD (Grand Central Dispatch) is a multitask management interface based on thread pool. GCD abstracts the concept of queue and task. Developers only need to put tasks in the appropriate queue without paying attention to specific thread management. GCD will automatically manage the life cycle of threads. Stripped many details of thread use, the interface is convenient and friendly, and it is widely used in practice.
  4. NSOperation/NSOperationQueue, roughly equivalent to the Objc encapsulation of GCD, provides some features that are not easy to implement in GCD, such as: limit the maximum number of concurrent operations, dependency between operations, etc. Because it is more difficult to use than GCD, it is not widely used. But when it comes to limiting concurrent number and operation dependency, NSOperation is definitely better.

Pthreads

Pthreads are POSIX standard threads. This set of standards specifies the complete capabilities of a standard thread. Here are the main interfaces:

  1. Establish

    • int pthread_create (pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg)
  2. End thread

    • void pthread_exit (void *retval): in thread exit
    • Int pthread? Cancel (pthread? T thread): terminate a thread externally
  3. block

    • int pthread_join(pthread_t thread, void **retval)
    • Block waiting for another thread to end (exit)
    • unsigned sleep(unsigned seconds)
    • have a nap
  4. separate

    • int pthread_detach(pthread_t tid)
    • By default, a pthread will not release resources after it is executed, but will keep its execution end state. In the detach state, it will release immediately after the execution end. You can also create a detach thread with a parameter.
  5. lock

    1. Put the lock back together for comparison.
    2. Pthread ﹣ mutex_
    3. Spin lock_
    4. pthread_rwlock_
    5. Conditional lock pthread ABCD con_

Put the lock back together for comparison.

Reference resources iOS multithreaded Pthreads

NSThread

The Objc encapsulation of threads is not used much. Now it should be based on pthread encapsulation.

// 1. Create thread
- (instancetype)init;
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;
- (instancetype)initWithBlock:(void (^)(void))block ;

// Create a detach thread, which will be released immediately after execution
+ (void)detachNewThreadWithBlock:(void (^)(void))block;
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

// 2. End thread
+ (void)exit;//End current thread
- (void)start;//End that thread

// 3. obstruction
// NSThread does not have a join like method, which can be implemented through locks.
// But it sleep s
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

About priority

At the beginning, thread priority is controlled by threadPriority, which is a property of double type between 0 and 1.0. However, after IOS 8, apple promoted the use of NSQualityOfService to represent the priority. The main reason is that developers want to ignore the details related to the underlying threads. You can see that the description of Qos has been a division method that is partial to the upper application layer:

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    NSQualityOfServiceUserInteractive = 0x21,
    NSQualityOfServiceUserInitiated = 0x19,
    NSQualityOfServiceUtility = 0x11,
    NSQualityOfServiceBackground = 0x09,
    NSQualityOfServiceDefault = -1
}

GCD

Grand Central Dispatch (GCD) is a multi-core programming solution developed by Apple, which can be basically understood as the thread pool interface under the iOS platform.

There are four key concepts in GCD:

  • Synchronous call: dispatch [sync]
  • Asynchronous call: dispatch [Async
  • Serial queue: dispatch? Queue? Serial
  • Concurrent queue: dispatch? Queue? Current

When a task is dispatched, there are two ways: synchronous and asynchronous. The focus is on the relationship between the current context and the task being called. Synchronous calls are blocked and wait for the call to be completed before continuing to execute, just like synchronous network requests. Asynchronous calls are executed directly to execute, and the task out of dispatch is played by itself.

In GCD, tasks are put in the queue and scheduled to different threads according to certain rules. The queue here is divided into serial queue and concurrent queue. If it is a serial queue, the tasks dropped are executed serially. If it is a concurrent queue, the tasks dropped are executed concurrently.

Basic use

GCD provides us with several default queues:

  1. Main queue
dispatch_queue_t queue = dispatch_get_main_queue();

The main queue is a serial queue and is bound to the main thread.

  1. Global concurrent queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

The system provides four global concurrent queues with different priorities, usually we use the default level.

We can also create our own queues:

// Creating method of serial queue
dispatch_queue_t queue = dispatch_queue_create("testQueue1", DISPATCH_QUEUE_SERIAL);
// Creating method of concurrent queue
dispatch_queue_t queue = dispatch_queue_create("testQueue2", DISPATCH_QUEUE_CONCURRENT);

When using, the tasks that need to be executed in the main thread are lost to the main queue, mainly UI or other system methods that can only be called in the main thread, which is no problem.

We can create our own serial queues to process those that are orderly but do not need to be put into the main thread.

Concurrent tasks are usually dispatched to the global queue at the default level. In general, you do not need to create concurrent queues yourself.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
    // do something
});

We seldom use the interface of dispatch Ou sync unless the context has a strong dependence on the order and the logic has to be executed in another queue.

For example, imageNamed: before IOS 9, it was not thread safe. We want to throw it to the main thread for execution, but we have strong dependence on it as follows:

// ...currently in a subthread
__block UIImage *image;
dispatch_sync_on_main_queue(^{
    image = [UIImage imageNamed:@"Resource/img"];
});
attachment.image = image;

But more often, in order to avoid using dispatch [sync], we will use dispatch [async] to the target queue, and then dispatch [async] will come back after execution.

The use of dispatch sync must be careful. If a serial queue is dispatched to the current queue, a deadlock will occur. Because the current task is blocked, wait for the block to finish executing, but the serial mechanism of gcd, the block is still queued, and it will not start executing until the current task finishes executing, so it is deadlocked. If it is a concurrent queue, it will not cause deadlock. But if the logic in the block involves other locking mechanisms, the situation here may be very complicated. Therefore, the thing of dispatch [sync] should be used as little as possible. When you have to use it, you must sort it out very clearly.

GCD has rich capabilities. In addition to the above basic usage, there are many scenarios to use:

timer

Because NSTimer is easy to cause circular reference, and it will be pushed out by the interface sliding when the RunLoopMode is not changed, and the sub thread cannot be used without opening Runloop. There are many restrictions. Sometimes we will replace it with the relevant capabilities provided by GCD.

One time timer

You can use dispatch after as follows:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
});

gcd provides the time type of dispatch (time), which looks like an Int64. It seems to be the same thing as the following (3 * nsec \\\\\\\\\\\\\\\\\\\\\ _Add and subtract to calculate time. I believe it. It's too young.

Take a look at the official explanation

/*!
 * @typedef dispatch_time_t
 *
 * @abstract
 * A somewhat abstract representation of time; where zero means "now" and
 * DISPATCH_TIME_FOREVER means "infinity" and every value in between is an
 * opaque encoding.
 */
typedef uint64_t dispatch_time_t;

Dispatch "time" is an abstract representation of time. From now on to forever, the mapping on uint64 is still opaque, that is to say, the probability is not a uniform mapping. Anyway, don't expect to use it casually.

The 1s example mentioned above is just a coincidence. It's right on the simulator, but not on the real machine. In fact, the demo that I saw adding and subtracting was written by swift. Under swift, the corresponding type overloads the operator, so the + / - operation can be used directly.

In a word, dispatch "time" can only be used in combination with gcd interface. Its value corresponds to a time but has nothing to do with the time unit of time, minute and second that we understand. Let's calculate the time with NSTimerInterval.

Cycle timer

GCD provides a mechanism called source. By associating source with a task, the associated task can be executed by triggering the source. timer is one of the sources. Other sources are rarely used in practice.

Use as follows

_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC), 1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
    // do something
});
dispatch_resume(_timer);

Dispatch Group

Dispatch group can combine multiple tasks into a group, so you can know when a group of tasks are completed.

NSLog(@"--- Start setting up tasks ----");
// Because dispatch group wait blocks a thread, a new thread is created to complete the task
// At the same time, add tasks to the new thread (tasks queue) asynchronously
dispatch_queue_t tasksQueue = dispatch_queue_create("tasksQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(tasksQueue, ^{
    // The thread used to complete the task
    dispatch_queue_t performTasksQueue = dispatch_queue_create("performTasksQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    for (int i = 0; i < 3; i++) {
        // The block after joining the group will be monitored by the group
        // Note: the dispatch group enter and the dispatch group leave must be paired.
        dispatch_group_enter(group);
        dispatch_async(performTasksQueue, ^{
            NSLog(@"Beginning %zd Task", i);
            [NSThread sleepForTimeInterval:(3 - i)];
            dispatch_group_leave(group);
            NSLog(@"Completion of %zd Task", i);
        });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"All tasks completed");
    });
});
NSLog(@"--- End setup task ----");

dispatch_once

Combined with dispatch ﹣ once ﹣ T, it is guaranteed to be executed only once. Due to the clear semantics, it is now the best way to write the internship case.

+ (SomeManager *)sharedInstance
{
    static SomeManager *manager = nil;
    static dispatch_once_t token;

    dispatch_once(&token, ^{
        manager = [[SomeManager alloc] init];
    });
    return manager;
}

dispatch_barrier_async

Fence function, only with concurrent queue only meaningful. When all the previous tasks are completed, the current task will begin to execute, and the later tasks will begin to execute after the current task is completed.

- (void)barrier
{
  //Used with the concurrent Dispatch Queue queue generated by the dispatch queue create function
    dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
}

NSOperationQueue

NSOperationQueue/NSOperation itself was introduced earlier than GCD, but later it was rewritten based on GCD, which can be understood as the object-oriented encapsulation based on GCD. Similarly, the concept of task / queue was also used.

Advantages of NSOperationQueue/NSOperation over GCD:

  1. The completed code block can be added and executed after the operation is completed.
  2. Add dependencies between operations to facilitate control of execution order.
  3. Sets the priority of operation execution.
  4. It is very convenient to cancel the execution of an operation.
  5. Use KVO to observe the change of operation execution state: isexecuting, isFinished, isCancelled.

The basic use is as follows:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
  for (int i = 0; i < 2; i++) {
    [NSThread sleepForTimeInterval:2]; // Simulate time-consuming operations
    NSLog(@"1---%@", [NSThread currentThread]); // Print current thread
  }
}];
[queue addOperation:operation];

It can be seen that it is much more difficult to use than GCD, so it is usually only used when the above advantages are involved.

One by one.

  1. The completed code block can be added and executed after the operation is completed.

    • Method of NSOperation
    • -(void)setCompletionBlock:(void (^)(void))block; completionBlock will execute completionBlock when the current operation is completed. `
  2. Add dependencies between operations to facilitate control of execution order.

    • Still the method of NSOperation
    • - (void)addDependency:(NSOperation *)op;
    • - (void)removeDependency:(NSOperation *)op;
  3. Sets the priority of operation execution.

    • Property of NSOpetation @ property NSOperationQueuePriority queuePriority;
    • Priority is given to dependency. Priority is given only when multiple task s are ready.
  4. It is very convenient to cancel the execution of an operation.

    • Method - (void)cancel of NSOperation;
  5. Use KVO to observe the change of operation execution state: isexecuting, isFinished, isCancelled. ,

    @property (readonly, getter=isCancelled) BOOL cancelled;
    @property (readonly, getter=isExecuting) BOOL executing;
    @property (readonly, getter=isFinished) BOOL finished;
    @property (readonly, getter=isReady) BOOL ready;

Tags: iOS Programming Swift C

Posted on Mon, 21 Oct 2019 19:03:12 -0700 by Zilvermeeuw