Blocks and GCDS are an important part of iOS knowledge and development requirements. We talked about blocks earlier, and this chapter is about GCDS. Before we talk about THE COMMUNIST Party of China, let’s get to the basics.

Basic knowledge of

Process, thread

  • process

1. Process is a program with a certain independent function about a data set of a running activity, it is the basic unit of the operating system to allocate resources.

2. Process refers to an application that is running in the system, which is the execution process of a program. We can understand it as the operation of an APP on a mobile phone.

3. Each process is independent of each other. Each process runs in its dedicated and protected memory space and has all resources required for independent running.

  • thread

1. The smallest unit of a program’s execution flow. A thread is an entity in a process.

2. To execute a task, a process must have at least one thread. When an application starts, the system starts a thread, called the main thread, by default.

  • The relationship between the two

1. A thread is the execution unit of a process. All tasks of a process are executed in a thread.

2. Thread is the minimum unit of CPU resource allocation and scheduling.

3. A program can correspond to multiple processes (multi-process). There can be multiple threads in a process, but there must be at least one thread.

4. Threads in the same process share process resources.

Multi-process, multi-thread

  • Multiple processes

Open the MAC’s activity monitor and you can see many processes running at the same time.

  • multithreading

1. The CPU can process only one thread at a time, and only one thread is executing. With a single CPU, multiple threads execute concurrently, which is essentially the CPU scheduling quickly between multiple threads. If the CPU schedules threads fast enough, it creates the illusion that multiple threads are executing concurrently. With multiple cpus, it’s true parallelism.

2. In a single CPU, if there are too many threads, the CPU will schedule the threads among N threads, consuming a large amount of CPU resources, and the execution frequency of each thread is reduced (the execution efficiency of the thread is reduced).

3. Advantages of multi-threading:

Can properly improve the execution efficiency of the program can properly improve the utilization of resources (CPU, memory utilization)Copy the code

4. Disadvantages of multithreading

Starting threads occupies a certain amount of memory (by default, the main thread occupies 1 MB and the sub-threads occupy 512KB). If a large number of threads are enabled, the memory occupies a large amount and the program performance deteriorates. The more threads there are, the more overhead the CPU has on scheduling threads and the more complex the programming is: communication between threads, data sharing between multiple threadsCopy the code

Tasks, queues

  • task

Perform an operation, that piece of code executed in a thread. GCD is placed in a block. There are two ways to execute tasks: sync and Async.

Synchronization (Sync)

Synchronously add tasks to a specified queue. Before the added tasks are finished, the system waits until the tasks in the queue are finished and then continues to execute the tasks. That is, the thread is blocked. It can only execute tasks in the current thread (the current thread, not necessarily the main thread) and does not have the ability to start new threads.Copy the code

Asynchronous (Async)

The thread returns immediately and continues without waiting, without blocking the current thread. You can execute tasks in new threads and have the ability to start new threads (not necessarily new threads). If not added to the main queue, asynchrony executes tasks in child threadsCopy the code
  • The queue

The queue here refers to the waiting queue for executing tasks, that is, the queue for storing tasks.

A queue is a special linear table that uses FIFO (first in, first out), meaning that new tasks are always inserted at the end of the queue and read from the head of the queue. Each time a task is read, a task is released from the queue.

There are two types of queues in GCD: serial queues and concurrent queues. Both comply with FIFO (first in, first out). The main differences between the two are: the execution order is different, and the number of threads started is different.

Serial Dispatch Queue

Only one task can be executed in a queue at a time. The next task can be executed only after the current task is completed. (Only one thread is started. After one task is completed, the next task is executed.) The main queue is a serial queue on the main thread that the system automatically creates for us.Copy the code

Concurrent Dispatch Queue

Multiple tasks can be executed concurrently. (Multiple threads can be started and tasks can be executed simultaneously). The concurrency functionality of concurrent queues is only available with asynchronous (dispatch_async) functions.Copy the code

Multithreading in iOS

pthread

Based on C language, it is used in many OS with high portability, but rarely used in iOS, mainly including the following three.

NSThread

Through apple dad’s encapsulation, object-oriented. Lightweight multithreading technology.

There are three ways.

NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1) object:nil]; [thread1 start]; //Copy the code
[NSThread detachNewThreadSelector:@selector(thread1) toTarget:self withObject:nil];Copy the code
/ / 【 NSObject subclass and objects, can call this method, a way of opening the child thread is NSThread] [self performSelectorInBackground: @ the selector (thread1) withObject: nil];Copy the code

Resident threads are usually implemented using NSThread+NSRunloop.

+ (NSThread *)shareThread {
    static NSThread *shareThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest2) object:nil];
        [shareThread setName:@"threadTest"];
        [shareThread start];
    });
    return shareThread;
}
+ (void)threadTest
{
    @autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; }}Copy the code

GCD

All you need to do is define the tasks you want to execute and append them to the appropriate Dispatch Queue.

Automatic management thread life cycle, the use of C language, the introduction of block, convenient development.

dispatch queue

Wait queues to perform processing.

The append mode is FIFO. Processing is performed in the order appended.

When processing is performed, there are two types.

Serial Dispatch Queue: Waits for ongoing processing to finish.

Concurrent Dispatch Queue: does not wait for ongoing processing to end.

The amount of processing executed in parallel depends on the current state of the system.

The system generates and consumes only one thread for a Serial Dispatch Queue. If 200 queues are generated, 200 threads will be generated.

The Serial Dispatch Queue can generate more threads than a Concurrent Dispatch Queue.

There are two creation modes.

// dispatch_queue_createCopy the code

With the CREATE API, when not needed, the queue must be released manually by the developer via the dispatch_release function. – under the MRC

// dispatch_get_main_queue() dispatch_get_global_queue()Copy the code

No manual management is required.

dispatch_after

The case in which you want to perform processing after specifying an event.

Instead of executing after the specified time, the queue is appended at the specified time.

The contents of a block are executed at the earliest 3s and at the slowest 3s+1/60 in the next runloop of the main thread.

Problems can arise under strict time requirements. Roughly delayed processing is ok.

dispatch_group

The end processing is performed after all the processing is completed.

Dispatch_group dispatch_group_notify.

And finally don’t forget dispatch_release(group). Because the group is created with dispatch_group_create(). –MRC

You can also use dispatch_group_wait and simply wait for all processing execution to complete.

{
    NSLog(@"Main thread");
    dispatch_queue_t queue = dispatch_queue_create("gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"end task 2");
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"All over");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Back to the main thread");
        });
    });
}
Copy the code

The console prints the following.

Main thread Start task 1 Start task 2 End task 1 End Task 2 All Over Return to the main threadCopy the code

Each task just sleeps for 2s, which is easy. Let’s do another example.

{
    NSLog(@"Main thread");
    dispatch_queue_t queue = dispatch_queue_create("gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (NSInteger i = 0; i < 5; i++) {
                NSLog(@"%ld",i); }}); }); dispatch_group_async(group, queue, ^{ dispatch_async(dispatch_get_global_queue(0, 0), ^{for (NSInteger i = 11; i < 20; i++) {
                NSLog(@"%ld",i); }}); }); dispatch_group_notify(group, queue, ^{ NSLog(@"All over");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Back to the main thread");
        });
    });
}
Copy the code

You’ll notice that the console didn’t wait for all the numbers to print out all over. Why is that?

The reason is that group_async is an asynchronous operation that is no longer in the queue at the time of execution, and the group does not hold the operation.

Therefore, the group_async code should be synchronous code.

So, if so, how to achieve this effect? Use dispatch_group_enter and dispatch_group_leave. Always come in pairs.

{
    NSLog(@"Main thread");
    dispatch_queue_t queue = dispatch_queue_create("gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (NSInteger i = 0; i < 5; i++) {
                NSLog(@"%ld",i);
            }
            dispatch_group_leave(group);
        });
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (NSInteger i = 11; i < 20; i++) {
                NSLog(@"%ld",i);
            }
            dispatch_group_leave(group);
        });
    }); 
    dispatch_group_notify(group, queue, ^{
        NSLog(@"All over");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Back to the main thread");
        });
    });
}  
Copy the code

Look at the console print at this time, the result is correct.

dispatch_barrier_async

This principle is similar to group, which is similar to fence. After all the previous operations are completed, the following operations can be performed. Unlike dispatch_group_notify, dispatch_barrier_async cannot use a queue defined by the system, but a queue created by dispatch_barrier_async.

dispatch_once

Ensure that this command is executed only once.

static dispatch_once_t pred; Dispatch_once (&pred, ^{// initialization operation});Copy the code

Even in multithreading, security can be guaranteed. If it is through common code, multiple initialization may occur in multithreaded lines.

dispatch_apply

Dispatch_sync +group API associated with the dispatch_sync+group. Blocks are appended to the specified queue at specified times.

Dispatch Semaphore

A GCD Semaphore is a Dispatch Semaphore, a signal that holds counts.

Dispatch Semaphore provides three functions:

2. Dispatch_semaphore_signal: dispatch_semaphore_wait: You can decrease the total semaphore by 1, and wait until the total number of signals reaches zero (blocking thread), otherwise it will execute normally.

Dispatch Semaphore is mainly used in practical development for:

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

1. Keep threads synchronized

{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    __block NSInteger number = 0;
    NSLog(@"before");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        number = 100;
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
}
Copy the code

Before semaphore– end,number = 100 Will wait until a signal is received

For thread safety, consider dispatch_semaphore_WAIT as locking and dispatch_semaphore_signal as unlocking.

Start by creating global variables

{ _semaphore = dispatch_semaphore_create(1); // Notice that the initial semaphore is 1. - (void)asyncTask { dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); count++; sleep(1); NSLog(@"Execute task :%zd",count); dispatch_semaphore_signal(_semaphore); }}Copy the code

Asynchronously invoke asyncTask concurrently

{
    for(NSInteger i = 0; i < 100; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self asyncTask]; }); }}Copy the code

The print is executed sequentially from task 1 to 100; no two tasks are executed simultaneously.

Here’s why:

If asyncTask is executed concurrently in a child thread, the first to be added to the concurrent queue decreases the semaphore by one, and the semaphore is equal to zero, and the next task can be executed. For other tasks in the concurrent queue, since the semaphore is not equal to 0 at this time, you must call dispatch_semaphore_signal to add 1 to the semaphore after the execution of the current task is completed, and then you can continue to execute the next task, and so on, so as to achieve the purpose of thread locking.

NSOperation

An encapsulation of GCD, an abstract base class, requires the use of subclasses.

There are two ways to create it:

  • What the system already provides,NSInvocationOptionation & NSBlockOperation
  • Custom class, inheritanceNSOperation

NSOperationQueue: queue, which can be understood as a thread pool.

addOperation

setMaxConcurrentOperationCount

Status: ready, cancelled (executing a task will not be cancelled but will still be executed), Executing, Finished, asynchronous (determining whether the task is concurrent or not)

Dependencies: addDependency, interdependencies between tasks

NSInvocationOptionation

- (void)nsOperationInv
{
    NSLog(@"Main thread");
    NSInvocationOperation *invoOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    [invoOp start];
}
- (void)run
{
    for (NSInteger i = 0; i < 5; i++) {
        NSLog(@"% @", @(i)); }}Copy the code

When you look at the console, you see that it is executed in the main thread because you are using the start method, which blocks the current thread. You can put it in dispatch_async.

NSBlockOperation

NSLog(@"Main thread");
NSBlockOperation *blockInvoke = [NSBlockOperation blockOperationWithBlock:^{
    for (NSInteger i = 0; i < 5; i++) {
        NSLog(@"% @", @(i)); }}]; [blockInvoke start];Copy the code

Also, block the current thread.

What if it’s asynchronous? The introduction of NSOperationQueue.

NSOperationQueue is usually used together with NSInvocationOptionation and NSBlockOperation.

Major modifications: Change the startup mode of operation to [queue addOperation:].

The custom Operation

Inherit from NSOperation and override the main or start methods.

NSLog(@"Main thread");
if(! _operationQueue) { _operationQueue = [[NSOperationQueue alloc] init]; } CustomOperation *opA = [[CustomOperation alloc] initWithName:@"opA"];
CustomOperation *opB = [[CustomOperation alloc] initWithName:@"opB"];
[_operationQueue addOperation:opA];
[_operationQueue addOperation:opB];
NSLog(@"Mainline-end");
Copy the code

Asynchronous concurrency, open four child threads.

If set the value of the queue setMaxConcurrentOperationCount, asynchronous concurrent threads = the value, finish a task execution, will open the next task.

What if you add dependencies?

[opD addDependency:opA];
[opA addDependency:opC];
[opC addDependency:opB];
Copy the code

In this case, the command output is B -> C -> A -> D.

When overriding the main method, if the process itself is asynchronous, the result will be distorted because the queue itself has finished, and the asynchronous task is in another thread.

How to solve it?

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [NSThread sleepForTimeInterval:1];
    if (self.cancelled) {
        return ;
    }
    NSLog(@"% @", _operationName);
    self.over = YES;
});
while(! self.over && ! self.cancelled) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; }Copy the code

NSRunLoop is used to ensure that the thread is not immediately released.

Or you can pass a block to operation by constructively initializing it, and operation calls a block in start, holding currentThread, and when the block finishes, it tells Operation to set nil. The whole point is to make sure that threads don’t get released.

To compare the

GCD is a c-oriented API, NSOperationQueue is built with GCD encapsulation, is a high-level abstraction of GCD.

GCD performs more efficiently, and because the queue performs tasks made up of blocks, it is a lightweight data structure that is easier to write.

GCD only supports FIFO queues, while NSOperationQueue can be adjusted by setting the maximum number of concurrent requests, setting priorities, adding dependencies, etc.

NSOperationQueue can even be set up across queues, but GCD can only control the order of execution by setting up a serial queue or adding a barrier(dispatch_barrier_async) task to the queue, which is complicated.

NSOperationQueue, because it is object-oriented, supports KVO and can monitor whether an operation isExecuted, finished, or cancelled.

In actual project development, asynchronous operations are often used and there is no particularly complex thread relationship management, so the optimized and fast RUNNING GCD favored by Apple is preferred.

If you consider transactionality, sequential lines, dependencies between asynchronous operations, such as multi-threaded concurrent downloads, GCD will need to write more code to implement this, and NSOperationQueue already has built-in support for this.

Whether it’s GCD or NSOperationQueue, we’re dealing with tasks and queues, we’re not dealing with threads directly, and in fact thread management really doesn’t need us to worry about, the system does a good job of creating, scheduling, and releasing threads. However, NSThreads require us to manage the life cycle of threads by ourselves, and consider thread synchronization and locking, resulting in some performance overhead.

Good article recommended iOS multithreading: “GCD” exhaustive summary