Generally speaking, there are four schemes to use multithreading in iOS platform:

  1. The C-based Pthreads interface, a POSIX threading standard, is implemented on Linux/ Unix/Windows platforms. It is widely used in C programming but rarely used in iOS development. The exposed interface is relatively low-level and fully functional, which, in turn, requires programmers to manage the life cycle of threads, which is relatively cumbersome to use.
  2. Nsthreads, objC’s thread interface, is basically an object-oriented wrapper for Pthreads. Object oriented management is easier, but still need to pay a certain cost of manual management.
  3. Grand Central Dispatch (GCD) is a multi-task management interface based on thread pools. GCD abstracts the concept of queues and tasks, and developers simply need to drop tasks into the appropriate queue. Instead of focusing on specific thread management, GCD automatically manages the thread life cycle. Stripped a lot of details of the use of threads, convenient interface friendly, the actual use of more extensive.
  4. NSOperation/NSOperationQueue, roughly equivalent to the GCD Objc encapsulation, provides some not easy to implement in the GCD features, such as: limit the maximum number of concurrent, dependencies between operation and so on. Since it is more troublesome to use than GCD, it is not particularly widely used. But it’s definitely better to use NSOperation when it comes to limiting concurrency and operation dependencies.

Pthreads

Pthreads is a STANDARD POSIX thread that specifies the full capabilities of a standard thread. Here are the main interfaces:

  1. create

    • int pthread_create (pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg)
  2. The end of the thread

    • void pthread_exit (void *retval): Exit from the thread
    • int pthread_cancel (pthread_t thread): Terminates a thread externally
  3. blocking

    • int pthread_join(pthread_t thread, void **retval)
    • Blocking to wait for another thread to finish (exit)
    • unsigned sleep(unsigned seconds)
    • Sleep for a while
  4. The separation of

    • int pthread_detach(pthread_t tid)
    • By default, a pthread does not release resources after it finishes executing. Instead, it remains in the finished state.detachIn the state, the system releases the data immediately after the execution ends. You can also create detach threads with an argument at creation time.
  5. The lock

    1. Lock the back together for comparison.
    2. The mutexpthread_mutex_
    3. spinlockspthread_spin_
    4. Read-write lockpthread_rwlock_
    5. Conditions for the lockpthread_con_

Lock the back together for comparison.

See the iOS Multithreading Pthreads section

NSThread

Objc encapsulation of threads is not used much. It should all be pThread based encapsulation now.

// 1. Create thread - (instanceType)init; - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument; - (instancetype)initWithBlock:(void (^)(void))block ; / / create the detach thread that performed immediately release + (void) detachNewThreadWithBlock: (void (^) (void)) block; + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument; // 2. End thread + (void)exit; // End the current thread - (void)start; Block // NSThread does not have a method like join, it can be done by locking. Sleep + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti;Copy the code

About Priority

ThreadPriority is initially controlled by threadPriority, which is a double between 0 and 1.0. However, after iOS8, apple promoted the use of NSQualityOfService to represent the priority, the main reason is still to want developers to ignore the details of the underlying thread, you can see that the description of Qos has been biased towards the upper layer of the application division:

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

GCD

Grand Central Dispatch (GCD) is a multi-core programming solution developed by Apple and basically understood as a thread pool interface on iOS.

There are four key concepts in GCD:

  • Synchronous call:dispatch_sync
  • Asynchronous call:dispatch_async
  • Serial queue:DISPATCH_QUEUE_SERIAL
  • Concurrent queue:DISPATCH_QUEUE_CONCURRENT

When dispatching a task, there are synchronous and asynchronous ways. The focus is on the relationship between the current context and the invoked task. Synchronous calls block and wait for the call to complete before proceeding, just like synchronous network requests. Asynchronous calls run straight down, and the dispatched task can play with itself.

In GCD, tasks are queued and then scheduled to be executed on different threads according to certain rules. There are two types of queues: serial queue and concurrent queue. If it is a serial queue, then the tasks thrown in are executed in serial, and if it is a concurrent queue, then the tasks thrown in are executed concurrently.

The basic use

GCD provides us with several default queues:

  1. The home side column
dispatch_queue_t queue = dispatch_get_main_queue();
Copy the code

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

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

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

We can also create queues ourselves:

Dispatch_queue_t queue = dispatch_queue_create("testQueue1", DISPATCH_QUEUE_SERIAL); Dispatch_queue_t queue = dispatch_queue_create("testQueue2", DISPATCH_QUEUE_CONCURRENT);

Copy the code

When used, tasks that need to be executed on the main thread are dropped to main_queue, mainly UI or other system methods that can only be called on the main thread, which is fine;

Something that is ordered but doesn’t need to be put into the main thread, we can create our own serial queue for processing.

For concurrent tasks, send dispatch_async to the global_queue of the default level. You generally do not need to create concurrent queues yourself.

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

We rarely use the dispatch_sync interface unless the context is strongly sequence-dependent and has to execute logic on another queue.

ImageNamed, for example, was not thread-safe before iOS9, we threw it to the main thread, but there is a strong dependency on it below:

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

But more often than not, to avoid dispatch_sync, we send dispatch_async to the destination queue and then dispatch_async back.

Use dispatch_sync with extreme caution. If a serial queue dispatches to the current queue, a deadlock occurs. Because the current task is blocked and waits for the block to finish, but the serial mechanism of GCD, the block is still queued and will not start executing until the current task finishes, so it is deadlocked. There is no deadlock if it is a concurrent queue. But if the logic inside the block involves other locking mechanisms, things can get very complicated. So use dispatch_sync as little as possible and be very clear when you have to.

The power of GCD is very rich, in addition to the basic usage above, there are many scenarios to use:

The timer

Because NSTimer is easy to cause circular reference, and when the RunLoopMode is not changed, it will be squeezed out by the interface sliding, and the child thread cannot use Runloop without opening it. There are many limitations, and sometimes we will use the related ability provided by GCD instead.

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(), ^{
});
Copy the code

GCD provides the time type dispatch_time_t, which looks like Int64 as if it were the same as int64_t (3 * NSEC_PER_SEC), Add dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC) minus DISPATCH_TIME_NOW and the result is exactly 1 * NSEC_PER_SEC, looks real… Dispatch_time_t = dispatch_time_t = dispatch_time_t I believe it… Too young for that

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;
Copy the code

Dispatch_time_t is an abstract representation of time, expressed in sectors from now to forever. The mapping is still opaque, which means that there is a high probability that it will not be a uniform mapping.

The 1s example mentioned above, which is just a coincidence, works on the simulator, but not on the real machine. And the demo that I saw, which was plus and minus, was actually written by Swift, and under Swift the corresponding type overloads the operator so you can just use the +/-.

In general, dispatch_time_t can only be used in conjunction with the GCD interface, and its value corresponds to a time but has nothing to do with the units of time we understand as minutes and seconds. So let’s just be honest and use NSTimerInterval.

Cycle timer

The GCD provides a mechanism called Source, which associates a Source with a task and executes the associated task by triggering the Source. A timer is one of these sources. Other sources are actually used very little.

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);
Copy the code

Dispatch Group

Dispatch_group can group multiple tasks together so you know when a group of tasks is complete.

NSLog(@"-- Start setting task ----"); // Since dispatch_group_wait blocks threads, create a new thread, Dispatch_queue_t tasksQueue = dispatch_queue_create("tasksQueue", DISPATCH_QUEUE_CONCURRENT); Dispatch_async (tasksQueue, ^{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++) {// block will be listened to by group. Dispatch_group_enter must be paired with dispatch_group_leave. Dispatch_group_enter (group) appears. dispatch_async(performTasksQueue, ^{ NSLog(@"Start task % Zd", i);
            [NSThread sleepForTimeInterval:(3 - i)];
            dispatch_group_leave(group);
            NSLog(@"Complete task % Zd", i);
        });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"All missions completed.");
    });
});
NSLog(@"-- End setting task ----");
Copy the code

dispatch_once

With dispatch_once_t, it is guaranteed to be executed only once, because the semantics are relatively clear, it is now the best way to write the internship singleton.

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

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

Copy the code

dispatch_barrier_async

Fence functions only make sense with concurrent queues. The current task can be executed only after all the previous tasks are completed. Subsequent tasks can be executed only after the current task is completed.

- (void)barrier { &emsp; &emsp; // Use 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(@"Three -- -- -- -- -- -- % @", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"-- 4 -- -- -- -- -- % @", [NSThread currentThread]);
    });
}
Copy the code

NSOperationQueue

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

The advantages of NSOperationQueue/NSOperation contrast the GCD:

  1. You can add completed blocks of code to execute after the action is complete.
  2. Add dependencies between operations to easily control the order of execution.
  3. Set the priority of the operation.
  4. You can easily cancel an operation.
  5. Use KVO to observe changes in the execution status of operations: isExecuteing, isFinished, isCancelled.

The basic usage 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 operation NSLog(@"1 - % @", [NSThread currentThread]); // Prints the current thread}}]; [queue addOperation:operation];Copy the code

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

Let’s take a look at each one.

  1. You can add completed blocks of code to execute after the action is complete.

    • NSOperationThe method of
    • - (void)setCompletionBlock:(void (^)(void))block; completionBlockExecutes the completionBlock when the current operation completes. `
  2. Add dependencies between operations to easily control the order of execution.

    • Is stillNSOperationThe method of
    • - (void)addDependency:(NSOperation *)op;
    • - (void)removeDependency:(NSOperation *)op;
  3. Set the priority of the operation.

    • NSOpetationThe properties of the@property NSOperationQueuePriority queuePriority;
    • Dependencies are prioritized, and tasks are prioritized only when all tasks are ready
  4. You can easily cancel an operation.

    • NSOperationThe method of- (void)cancel;
  5. Use KVO to observe changes in the execution status of operations: isExecuteing, 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;
    Copy the code