Welcome to the iOS Exploration series.

  • IOS explores the alloc process
  • IOS explores memory alignment &malloc source code
  • IOS explores ISA initialization & pointing analysis
  • IOS exploration class structure analysis
  • IOS explores cache_T analysis
  • IOS explores the nature of methods and the method finding process
  • IOS explores dynamic method resolution and message forwarding mechanisms
  • IOS explores the dyLD loading process briefly
  • The loading process of iOS explore classes
  • IOS explores the loading process of classification and class extension
  • IOS explores isa interview question analysis
  • IOS Explore Runtime interview question analysis
  • IOS explores KVC principles and customization
  • IOS explores KVO principles and customizations
  • IOS explores the principle of multithreading
  • IOS explores multi-threaded GCD applications
  • IOS explores multithreaded GCD underlying analysis
  • IOS explores NSOperation for multithreading
  • IOS explores multi-threaded interview question analysis
  • IOS Explore the locks in iOS
  • IOS explores the full range of blocks to read

Writing in the front

The previous four articles respectively introduce the principle of multi-threading, the application of GCD, the underlying principle of GCD, NSOperation. This article will analyze the high-frequency multi-threading interview questions in iOS interview, and hope that you can answer them correctly (part of the content is a bit repeated in the previous several articles).

One, multi-threaded selection scheme

Technical solution Introduction to the language Thread life cycle Using a review of the rate
pthread A set of generic multithreading apis

It is applicable to Unix, Linux, and Windows

Cross-platform/portable

Difficult to use
C Programmer management Almost no
NSThread Use more object-oriented

Easy to use, direct manipulation of thread objects
OC Programmer management Occasionally use
GCD Designed to replace threading technologies such as NSThreads

Take full advantage of multi-core equipment
C Automatic management Often use
NSOperation Based on GCD (underlying is GCD)

More than GCD some more simple and practical functions

Use more object-oriented
OC Automatic management Often use

Note: if you use the NSThread performSelector: withObject: afterDelay: when you need to add to the current thread runloop, because create a NSTimer, government departments

2. Comparison between GCD and NSOperation

  • The relationship between GCD and NSOperation is as follows:

    • GCDIs an API for the underlying C language
    • NSOperationIs to useGCDEncapsulating the build, yesGCDHigh level abstractions of
  • The comparison between GCD and NSOperation is as follows:

    1. GCDExecution is more efficient, and because the queue is executed byblockThis is a lightweight data structure — much easier to write
    2. GCDOnly supportFIFOWhileNSOprationYou can set the maximum number of concurrent requests, set priorities, and add dependencies to adjust the execution sequence
    3. NSOprationYou can even set dependencies across queues, butGCDThis can only be done by setting up a serial queue, or by adding it to a queuebarrierTasks can control the execution sequence, which is more complex
    4. NSOperationsupportKVO(Object oriented) Can detect whether operation is executing, ending, and canceling
  • In real projects, asynchronous operations are often used and there is no particularly complex thread relationship management, so Apple favors a well-optimized, fast RUNNING GCD
  • If you consider transactional, sequential, and dependent relationships between asynchronous operations, such as multi-threaded concurrent downloads, GCD needs to write more code to implement, and NSOperation already has built-in support for this
  • In both GCD and NSOperation, we are dealing with tasks and queues, and we are not dealing with threads directly. In fact, thread management does not 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

Three, multi-threaded application scenarios

  • Asynchronous execution
    • Place time-consuming operations in child threads so that they do not block the main thread
  • Refresh the UI
    • Asynchronous network request. Request completedispatch_get_main_queue()Go back to the main thread and refresh the UI
    • Multiple network requests on the same pagedispatch_groupUniformly scheduled UI refresh
  • dispatch_once
    • inThe singleton, a class has only one instance and provides a global access point
    • inmethod-SwizzlingSwap only once using the guarantee method
  • dispatch_afterThe task is delayed to queue
  • Barrier functionCan be used as a synchronization lock
  • dispatch_semaphore_t
    • Used as a lock to keep threads safe
    • controlGCDThe maximum number of concurrent requests
  • Dispatch_source timerReplace the error largerNSTimer
  • AFNetworking,SDWebImageAnd other well-known tripartite libraryNSOperationuse
  • .

Principle of thread pool

  • ifThread pool sizeLess thanCore thread pool sizewhen
    • Create threads to execute tasks
  • ifThread pool sizeGreater than or equal toCore thread pool sizewhen
    1. Determine whether the thread pool work queue is full
    2. If not, the task is pushed to the queue
    3. If it is full andmaximumPoolSize>corePoolSize, a new thread will be created to execute the task
    4. Otherwise hand overSaturated strategyTo deal with
Parameter names On behalf of the meaning
corePoolSize Base size of the thread pool (core thread pool size)
maximumPool The maximum size of the thread pool
keepAliveTime The maximum lifetime of idle threads in the thread pool that exceed the corePoolSize tree
unit KeepAliveTime Indicates the time unit of the parameter
workQueue Task blocking queue
threadFactory Create a factory for the thread
handler When the number of submitted tasks exceeds the sum of maxmumPoolSize and workQueue,

The task will be handled by RejectedExecutionHandler

There are four saturation strategies as follows:

  • AbortPolicyDirect selling RejectedExecutionExeception exceptions to prevent normal operation of system
  • CallerRunsPolicyRoll back the task to the caller
  • DisOldestPolicyDrop the most waiting task
  • DisCardPolicyDrop the task directly

Fifth, the similarities and differences of fence functions and points to pay attention to

The fence function has two apis:

  • dispatch_barrier_async: Controls the execution sequence of tasks in a queue
  • dispatch_barrier_sync: blocks not only queue execution, but also thread execution

Fence function points to note:

  1. Use custom concurrent queues whenever possible:
    • useGlobal queueIt doesn’t work as a fence function
    • useGlobal queueWhen the global queue is blocked, other parts of the system that call the global queue may also be blocked and crash (you are not the only one using this queue).
  2. The fence function can only control the same concurrent queue: for example, in ordinary useAFNetworkingWhy can’t we use the fence function to block the synchronization lock when making network requests, becauseAFNetworkingIt has its own queue internally

The barrier function reads and writes the lock

The multi-read and single-write function allows multiple readers to read data at the same time, but no data can be written during reading. No other writer can write during the writing process. That is, readers are concurrent, and writers are mutually exclusive with other writers and readers

- (id)readDataForKey:(NSString*)key {
    __block id result;
    dispatch_sync(_concurrentQueue, ^{
        result = [self valueForKey:key];
    });
    return result;
}

- (void)writeData:(id)data forKey:(NSString*)key {
    dispatch_barrier_async(_concurrentQueue, ^{
        [self setValue:data forKey:key];
    });
}
Copy the code
  • Read:Concurrent synchronizationThe value is retrieved and returned to the reader
    • If useConcurrent asynchronousEmpty is returned firstresult 0x0And get the value through the getter method
  • Write: there must be no readers + other writers at the time of writing
    • dispatch_barrier_asyncIf yes, the current task is executed after all previous read/write tasks in the queue are completed

Vii. Concurrency of GCD

Unlike NSOperation can go through maxConcurrentOperationCount concurrency control, the GCD to signal to achieve the effect

dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 10; i++) {
    dispatch_async(queue, ^{
        NSLog(@" Current %d---- thread %@", i, [NSThread currentThread]);
        // The semaphore is unlocked after the printing task is complete
        dispatch_semaphore_signal(sem);
    });
    // Because of asynchronous execution, the printing task will be slow, so the semaphore is lockeddispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); } -------------------- The command output is ------------------- Current1Thread - <NSThread: 0x600001448d40>{number = 3, name = (null)} Current0Thread - <NSThread: 0x60000140c240>{number = 6, name = (null)} Current2Thread - <NSThread: 0x600001448d40>{number = 3, name = (null)} Current3Thread - <NSThread: 0x60000140c240>{number = 6, name = (null)} Current4Thread - <NSThread: 0x60000140c240>{number = 6, name = (null)} Current5Thread - <NSThread: 0x600001448d40>{number = 3, name = (null)} Current6Thread - <NSThread: 0x600001448d40>{number = 3, name = (null)} Current7Thread - <NSThread: 0x60000140c240>{number = 6, name = (null)} Current8Thread - <NSThread: 0x600001448d40>{number = 3, name = (null)} Current9Thread - <NSThread: 0x60000140c240>{number = 6Name = (null)} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --Copy the code

Interviews are more likely to test the developer’s multithreading knowledge of a given scenario, so let’s take a look at some comprehensive applications

Eight, comprehensive use of a

1. Will the following code report an error?

int a = 0;
while (a < 5) {
    dispatch_async(dispatch_get_global_queue(0.0), ^{
        a++; 
    });
}
Copy the code
  • Compilation will report an errorVariable is not assignable (missing __block type specifier)
    • This is part of the knowledge of the block
  • To capture external variables and modify them, add__block int a = 0;
    • This is in the next sectionblockTalk about

2. Output of the following code

__block int a = 0;
while (a < 5) {
    dispatch_async(dispatch_get_global_queue(0.0), ^{
        a++;
    });
}
NSLog(@"%d", a);
Copy the code
  • Will be output0?
    • No, although it is executed concurrently and asynchronously, there iswhileIf you don’t, you don’t get out of the loop
  • Will be output1 ~ 4?
    • No (see below for reasons)
  • Will be output5?
    • Possibly (read on for reasons)
  • Will be output6 ~ up?
    • Most likely

Analysis:

  • When I first enter the while loop,a=0And then proceeda++
  • Because it isAsynchronous concurrentIt opens up child threads and may overtake to finish
    • whenThread 2ina=0performa++When,Thread 3It may have been donea++makea=1
    • And since we’re operating on the same memory space,Thread 3To modify theaLead toThread 2In theaThe value of theta also changes
    • Slow a beatThread 2To have isa=1fora++operation
  • In the same way andThread 4,Thread 5,Thread nThe presence of
    • Threads 2, 3, 4, 5, 6 are all at the same timea=0When operatinga
    • Threads 2, 3, 4, and 5 have completed their operations in that ordera=4
    • thenThread 6It starts, but it doesn’t finish before it jumps to the next loop and opens upThread 7starta++
    • whenThread 6End Modifya=5Later came toWhile condition judgmentIt breaks out of the loop
    • However,I/OThe output is time-consuming, and the output will be printed when thread 7 is just finishedMore than 5
  • There is also an ideal situation,Asynchronous concurrentThey were obedient. They were right therea=5There are no child threads when
    • And then it prints5

You can add print code to the while loop if you don’t understand

__block int a = 0;
while (a < 5) {
    dispatch_async(dispatch_get_global_queue(0.0), ^ {NSLog(@ % d -- - % @ "", a, [NSThread currentThread]);
        a++;
    });
}
NSLog(@" at this time %d", a);
Copy the code

Printing indicates that the printing outside of the while has been done, but the child thread may still be operating on A

3. How to solve thread insecurity?

Some people may say the need doesn’t exist, but we just have to deal with it

At this point we should think of several solutions:

  • The synchronous function replaces the asynchronous function
  • Using the fence function
  • Use semaphore
  1. The synchronous function replaces the asynchronous function
  • Result: it can meet the demand
  • Effect: not very good – why not use asynchronous functions to call child threads when you can (memory consuming, but efficient)
  1. Using the fence function
  • Result: it can meet the demand
  • Effect: Average
    • First of all,Barrier functionandGlobal queueIf used together, the queue type is invalid and needs to be changed.
    • The seconddispatch_barrier_syncThreads block, which affects performance
    • whiledispatch_barrier_asyncCan not meet the requirements, it can only control the previous task to complete the execution of the fence task (control task execution) but asynchronous fence execution is also in the child thread, whena=4Will continue the next loop to add tasks to the queue, and then perform the fence task asynchronously (you cannot control the addition of tasks).
__block int a = 0;
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
while (a < 5) {
    dispatch_async(queue, ^{
        a++;
    });
    dispatch_barrier_async(queue, ^{});
}

NSLog(@" at this time %d", a);
sleep(1);
NSLog(@" at this time %d", a); -------------------- The command output is as follows: -------------------5At this time17-------------------- The command output is -------------------Copy the code
  1. Use semaphore
  • Result: it can meet the demand
  • Result: very good, concise and efficient
__block int a = 0;
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
while (a < 5) {
    dispatch_async(dispatch_get_global_queue(0.0), ^{
        a++;
        dispatch_semaphore_signal(sem);
    });
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

NSLog(@" at this time %d", a);
sleep(1);
NSLog(@" at this time %d", a); -------------------- The command output is as follows: -------------------5At this time5-------------------- The command output is -------------------Copy the code

Nine, comprehensive use of two

1. Output content

dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        [marr addObject:@(i)];
    });
}
NSLog(@"%lu", marr.count);
Copy the code
  • You: Print a number less than 1000 because the for loop is asynchronous
  • Interviewer: Go back and wait for news
  • And then you go back and try it and get a shock — it crashes

Why is that?

In fact, the same thing happens when the for loop is asynchronous, so that countless threads access the array, making it thread unsafe

2. How to solve thread insecurity?

  • Using serial queues
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        [marr addObject:@(i)];
    });
}
NSLog(@"%lu", marr.count); -------------------- The command output is -------------------998-------------------- The command output is -------------------Copy the code
  • Use mutex
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        @synchronized (self) { [marr addObject:@(i)]; }}); }NSLog(@"%lu", marr.count); -------------------- The command output is -------------------997-------------------- The command output is -------------------Copy the code
  • Using the fence function
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        [marr addObject:@(i)];
    });
    dispatch_barrier_async(queue, ^{});
}
NSLog(@"%lu", marr.count);
Copy the code

3. Analyze your thinking

There are other ways to get to Rome

  • Using serial queues
    • Although inefficient, it can always solve the thread safety problem
    • althoughSerial asynchronousIt is the tasks that execute one after another, but it is the tasks in the queue that satisfy the execution rule
    • To get a printout1000Can be executed in a queue
    • In general, it’s satisfying but not very effective
  • Use mutex
    • @synchronizedIt’s a good thing, it’s easy to use and it works, but it doesn’t meet our needs
    • Using either in-queue synchronous or asynchronous outside of the for loop is not available100
    • Or sleep for a second — uncontrolled code is not desirable
    • And in the iOS lock family@synchronizedEfficiency is very low
  • Using the fence function
    • Fence function can effectively control the execution of tasks
    • And with theComprehensive application IDifferent. In this case, it’s a for loop
    • How to get the print result1000, just print in the same queue.
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        [marr addObject:@(i)];
    });
    dispatch_barrier_async(queue, ^{});
}
dispatch_async(queue, ^{
    NSLog(@"%lu", marr.count);
});
Copy the code

Write in the back

Multithreading plays an important role in daily development and is also a must-ask module in interviews. However, only the basic knowledge is invariable, and a slight change in the comprehensive application question is another type of knowledge consideration, and there are many solutions