Threads and processes


Almost all operating systems support running multiple tasks at the same time. A task is usually a program, and each program is a process. When a program runs, it may contain multiple sequential execution streams, each of which is a thread.

  • Process

When a program runs in memory, it becomes a process. Process is in the running process of the program, and has a certain independent function, process is the system for resource allocation and scheduling an independent unit. In general, processes have the following characteristics:

  1. Independence: Has its own independent resources and has its own private address space. Other processes cannot directly access the address space of their processes without their own permission.
  2. Dynamic: A program is a static set of instructions, while a process is a set of instructions that are active in the system. A process has a concept of time, its own life cycle, and various states.
  3. Concurrency: Multiple processes can execute concurrently on a single processor without affecting each other.
  • Threads

Threads, also known as lightweight processes, are the execution unit of a process. Just like a process in a system, threads execute processes independently and concurrently in a process. A process can have multiple threads, and a thread must have a parent process that no longer owns system resources, but shares all the resources of the parent process with the parent process. Multithreading makes programming easier by sharing resources from the parent process, but it also needs to be careful that the thread does not affect other threads in the parent process. Threads run independently and are unaware of the existence of other threads. Thread execution is preemptive, that is, the currently running thread can be suspended at any time so that another thread can run.

  • Multithreading advantages
  1. Memory cannot be shared between processes, but it is easy to share memory between threads.
  2. System creation processes need to reallocate system resources for them, but creating threads is much cheaper and therefore more efficient

Why multithreaded programming


In order to improve resource utilization and improve the overall efficiency of the system, time-consuming operations are usually executed in the background to avoid blocking the main thread. In iOS, UI drawing and user response are the main thread.

NSThread


Commonly used API

- (void)viewDidLoad { [super viewDidLoad]; // Prints the current thread NSLog(@"Start: %@ Priority: %d", [NSThread currentThread], [NSThread currentThread].qualityOfService); / / 1. Create NSTreadObject must be started by calling the start method, And only one argument can be passed: object NSThread * Thread = [[NSThread alloc] initWithTarget:self selector:@ (run: object:@)"test"];
    //    NSThread *thread = [[NSThread alloc] initWithBlock:^{}];
    thread.name = @"testThread"; thread.qualityOfService = NSQualityOfServiceUserInteractive; [thread start]; [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"test"]; // [NSThread detachNewThreadWithBlock:^{}]; / / 3. The implicit directly create / / [NSThread performSelectorInBackground: @ the selector (run) withObject: nil]; // NSLog(@"End: %@", [NSThread currentThread]); } - (void)run:(NSObject *)object {// [NSThread sleepForTimeInterval:5]; // abort the current thread // [NSThreadexit];
    NSLog(@"Child thread running: %@ %@ Priority: %d", [NSThread currentThread], object, [NSThread currentThread].qualityOfService);
}
Copy the code
  • Thread state

After a thread is started, it does not enter the execution state directly, nor is it always in the execution state. Because the thread is concurrent, the thread will repeatedly switch between running and ready. After creating a thread, the system allocates memory for it and initializes its member variables. Call – (void) start; Method, the thread is in a ready state, and the system creates a method call stack and program counter for it. At this time, the thread is not running, and when to run depends on the system scheduling.

  • Terminate child thread

Each thread has a certain priority, and the higher the priority, the more execution opportunities. The qualityOfService attribute is used to set threadPriority, which is deprecated due to semantic ambiguity.

NSQualityOfServiceUserInteractive: the highest priority, is mainly used to provide interactive UI operations, such as processing click event, draw the image on the screen NSQualityOfServiceUserInitiated: Higher priority, mainly for the need to immediately return mission NSQualityOfServiceDefault: the default priority, when there is no set priority, the default priority thread NSQualityOfServiceUtility: Ordinary priority, mainly used for tasks do not need to return immediately NSQualityOfServiceBackground: background priority, for not urgent taskCopy the code
  • disadvantages

Multi-threaded programming using NSThread is more complex, and it is necessary to control the synchronization and concurrency of multi-threads, as well as the termination and destruction of threads. It is easy to make mistakes if you are not careful, and it has high requirements for developers, so it is seldom used.

NSOperation


IOS also provides NSOperation and NSOperationQueue to achieve multi-threading, which is based on a higher level of ENCAPSULATION of GCD and is fully object-oriented. But GCD is easier to use and more readable code.

NSOperationQueue: Manages multiple NSOperations committed by the system and maintains a thread pool underneath. This is different from the FIFO(first in, first out) principle of scheduling queues in GCD. NSOperationQueue For operations added to the queue, the ready state is first entered (the ready state depends on the dependencies between the operations), and the start (non-end) execution order of the ready operations is determined by the relative priority of the operations (priority is an attribute of the operation object itself).

NSOperation: represents a multithreaded task.

  • Why use NSOperation, NSOPerationQueue?
  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.
  • Commonly used API
NSOperationQueue *queue; Queue = [NSOperationQueue currentQueue]; Queue = [NSOperationQueue mainQueue]; // Queue = [NSOperationQueue mainQueue]; Queue = [[NSOperationQueue alloc] init]; // Queue name = @"testOperationQueue"; / / maximum number of concurrent operation (system limited, even if the setting is very big, also automatically adjust) queue. MaxConcurrentOperationCount = 10; / / set the priority queue. QualityOfService = NSQualityOfServiceDefault; // If SEL and Block are null, The system will not join the specified queue NSInvocationOperation *invocationOperation = [[NSInvocationOperation Alloc] initWithTarget:self selector:@selector(run) object:nil]; NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"blockOperation"); }]; / / add dependencies, invocationOperation performed before execution blockOperation [blockOperation addDependency: invocationOperation]; / / / / added to the queue [queue addOperation: invocationOperation]; [queue addOperations:@[invocationOperation, blockOperation]waitUntilFinished:NO]; Queue addOperationWithBlock:^{}]; // Prints all nsOperationsfor(int i=0; i<queue.operationCount; i++) {
        NSLog(@"The %d NSOperation:%@ of queue %@", queue.name, i, queue.operations[i]); } // stop all nsoperations // queue cancelAllOperations; // all nsOperations are performed before the current thread is unblocked // [queue]waitUntilAllOperationsAreFinished];
Copy the code

GCD (Grand Central Dispatch)

  • The basic concept
  1. Queues: Queues are responsible for tasks submitted by developers, but different tasks have different execution times, and the first task may not be completed first. Queues can be serial or parallel, and the bottom of the queue maintains a thread pool to process tasks. Serial queues only need to maintain one thread, while parallel queues need to maintain multiple threads.
  2. Tasks: Units of work submitted by the user to the queue that are submitted to the thread pool maintained at the bottom of the queue.
  3. Asynchronous: Tasks can be executed in a new thread, but not necessarily a new thread. The Dispatch function returns immediately, and the Block executes asynchronously in the background.
  4. Synchronization: Tasks are executed in the current thread, no new threads are created. The dispatch function does not return until the Block function completes.

Note: The serialization and parallelism of queues determine how tasks are executed, and the asynchrony and synchronization of execution determine whether new threads need to be opened to process tasks.

  • The characteristics of
  1. GCD can be used for multi-core parallel computing;
  2. GCDS automatically utilize more CPU cores (e.g., dual-core, quad-core);
  3. GCD automatically manages the thread lifecycle (thread creation, task scheduling, thread destruction);
  4. Programmers only need to tell the CCP what tasks they want to perform without writing any thread management code;
  • Commonly used API
/** get queue */ / get global concurrent queue with specified priority (flag = 0, reserved parameter only) Other values may return null) dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); Dispatch_queue_t queue1 = dispatch_queue_create("testQueue1", DISPATCH_QUEUE_CONCURRENT); Dispatch_queue_t queue2 = dispatch_get_main_queue(); Dispatch_queue_t queue3 = dispatch_queue_create("testQueue3", DISPATCH_QUEUE_SERIAL); Dispatch_async (queue, ^{}); dispatch_async(queue, ^{}); // synchronously submit code blocks to custom concurrent queue dispatch_sync(queue1, ^{}); // Asynchronously submit code blocks to the serial queue, and the thread pool executes the code block at the specified time (actually 5 seconds later, but not necessarily immediately). Dispatch_after (dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5*NSEC_PER_SEC)), queue2, ^{}); // Asynchronously commit code to a custom serial queue. Synchronous functions, whether executed in a serial or parallel queue, are executed before they return, so to prevent thread blocking and deadlocks, time indicates the current number of times (if committed to a concurrent queue, Dispatch_apply (5, queue3, ^(size_t time) {}); Static dispatch_once_t onceToken; static dispatch_once_t onceToken; // The main thread dispatch_once(&onceToken, ^{}); // Dispatch_group_wait (group, DISPATCH_TIME_FOREVER); /** group_t group = dispatch_group_create(); /** group_t group = dispatch_group_create(); Dispatch_group_async (group, queue, ^{}); Dispatch_group_async (group, queue, ^{}); Dispatch_group_notify (group, queue, ^{}); 1 dispatch_async(queue, ^{// block 1}); Dispatch_async (queue, ^{// block 2}); 4 dispatch_barrier_async(queue, ^{}); // Dispatch_async (queue, ^{// dispatch_async); Dispatch_async (queue, ^{// block 4}); Dispatch_semaphore_t t = dispatch_semaphore_create(1); // dispatch_semaphore_signal(t); // Dispatch_semaphore_wait (t, DISPATCH_TIME_FOREVER); // Dispatch_semaphore_wait (t, DISPATCH_TIME_FOREVER) */ / dispatch_semaphoRE_t t1 = dispatch_semaphore_create(0); Void (^downloadTask)(void) = ^ {// Download image... . // Dispatch_semaphore_signal (t1); }; downloadTask(); // dispatch_semaphore_wait(t1, DISPATCH_TIME_FOREVER) until the semaphore count is 1; Dispatch_semaphore_t t2 = dispatch_semaphore_create(1); // If the semaphore has already been used by a thread, then it can only wait for dispatch_semaphore_wait(t2, DISPATCH_TIME_FOREVER); // Execute business code... . / / unlock dispatch_semaphore_signal (t2); Dispatch_semaphore_t t3 = dispatch_semaphore_create(10); / / residual operation again, is similar to the set of NSOperationQueue maxConcurrentOperationCount to 10Copy the code
  • The background

When the App enters the background, we should try to free up memory and save user data or status information. By default, should be in only five seconds to handle these work, we can through the UIApplication beginBackgroundTaskWithExpirationHandler ways to prolong processing time, up to ten minutes.

Application - (void) applicationDidEnterBackground: (UIApplication *) {/ / statement closing background task code block void (^endBackgroundTask)(UIBackgroundTaskIdentifier backgroudTask) = ^(UIBackgroundTaskIdentifier backgroudTask) { [[UIApplication sharedApplication] endBackgroundTask:backgroudTask]; backgroudTask = UIBackgroundTaskInvalid; }; / / open the background task __block UIBackgroundTaskIdentifier backgroudTask; BackgroudTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^ {/ / still not completed in ten minutes, EndBackgroundTask (backgroudTask)}]; EndBackgroundTask (backgroudTask); }Copy the code
  • The thread deadlock
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"% @", [NSThread currentThread]);
    });
}
Copy the code

Adding a Block of synchronized code to the main queue causes a deadlock. Because synchronization needs to be performed immediately and sequentially, in the above code, the methods in the Block need to be completed after viewDidLoad has finished, but viewDidLoad must finish the methods in the Block before it can finish, so they wait forever and cause a deadlock.

Does GCD cause circular references?

The dispatch_async block does not require _weak self. The dispatch_async block does not require _weak self.

  • Pay attention to
  1. Synchronous execution executes tasks in the current thread without the ability or necessity to create new threads. Also, synchronous execution must wait until the Block function completes before the dispatch function returns, blocking the execution of external methods in the same serial queue.
  2. The asynchronous dispatch function returns directly, and only the asynchronous execution is necessary to start a new thread, but the asynchronous execution does not necessarily start a new thread.
  3. To create new threads, tasks must be executed asynchronously. To create multiple threads, tasks must be executed asynchronously in parallel queues. The multi-layer combination of execution mode and queue type allows scheduling of code execution order to some extent.
  4. Synchronous + serial: no new thread is opened and the task is executed in serial; Synchronous + parallel: no new thread is opened, the task is executed in serial; Asynchronous + serial: open a new thread, serial execution of the task; Asynchronous + parallel: open up a number of new threads, parallel execution of tasks; Using the main queue to execute tasks synchronously in the main thread causes a deadlock.

Thread safety

Thread safety is mainly caused by the randomness of the system’s thread scheduling. Because it is multi-concurrent, multiple threads read and write a piece of data at the same time, another thread may write it when reading and performing the general, resulting in data abnormalities. Thread safety means keeping threads synchronized

  • Thread-safe class characteristics
  1. Objects of this class can be safely accessed by multiple threads.
  2. Every thread that calls any method of the object gets the correct result.
  3. After each thread calls any method of the object, the object remains in a reasonable state.
  • @synchronized is the encapsulation of a mutex recursive lock

To solve this problem, Objective-C’s multithreading support introduces synchronization, enabling @synchronized to modify code blocks, which can be referred to simply as synchronized code blocks in the following syntax

@synchronized (obj) {// synchronized block}Copy the code

Obj is the synchronization monitor. When a thread performs synchronization, it must first obtain the synchronization monitor lock. Only one thread can obtain the lock at any time, and the lock will be released after the completion of execution. It is generally recommended to use shared resources that can be accessed concurrently as synchronization monitors.

In the iOS lock


1. OSSpinLock

  • The thread waiting for the lock is in busy-wait state, occupying CPU resources.
  • It is no longer secure, and priority reversal may occur.
  • If the thread waiting for the lock has a higher priority, it will always occupy CPU resources, and the thread with a lower priority cannot release the lock.
  • The header file needs to be imported#import <libkern/OSatomic.h>
OSSpinLock lock = OS_SPINLOCK_INIT; // Try locking (if you need to wait, do not lock, return directlyfalse; Returns if there is no need to wait for the locktrue) bool resule = OSSpinLockTry(& Lock); / / lock OSSpinLock (& lock); / / unlock OSSpinLockUnlock (& lock);Copy the code

2. os_unfair_lock

  • Used to replace insecure OSSpinLock, supported starting with iOS10;
  • From underlying calls, threads waiting for os_UNFAIR_LOCKS are dormant, not busy, etc.
  • The header file needs to be imported#import <os/lock.h>
Os_unfair_lock LOCK = OS_UNFAIR_LOCK_INIT; Os_unfair_lock (&lock); / / lock os_unfair_lock_lock (& lock); / / unlock os_unfair_lock_unlock (& lock);Copy the code

3. pthread_mutex

The mutex

  • Mutex is called a “mutex,” and the waiting thread will sleep
  • The header file needs to be imported#import <pthread.h>
// Initialize the lock attribute pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_NORMAL); // initialize pthread_mutex_t mutex; pthread_mutex_init (&mutex,&attr); // Try to lock pthread_mutex_trylock (&mutex); // pthread_mutex_lock (&mutex); // unlock pthread_mutex_unlock (&mutex); Pthread_mutexattr_unlock (&attr); pthread_mutex_destroy(&mutex);Copy the code

Recursive locking

  • Recursive locking: Allows the same thread to lock a lock repeatedly
// Initialize the attribute pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE); // initialize the lock pthread_mutex_t mutex; pthread_mutex_init(mutex, &attr); Pthread_mutexattr_destroy (&attr);Copy the code

conditions

// initialize the lock pthread_mutex_t mutex; Pthread_mutex_init (&mutex, NULL); Pthread_cond_t cond; pthread_cond_init(&cond, NULL); // Wait condition (enter sleep, release mutex lock; Pthread_cond_wait (&cond, &mutex); Pthread_cond_signal (&cond); Pthread_mutex_destroy (&mutex); pthread_cond_destroy(&cond);Copy the code

4. NSLock, NSRecursiveLock

  • NSLock is a wrapper around a mutex normal lock
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

@interface NSLock : NSObject <NSLocking>
{
- (BOOL)tryLock;
- (BOOl)lockBeforeDate:(NSDate *)limit; } @end // initialize lock NSLock *lock = [[NSLock alloc] init];Copy the code
  • NSRecursiveLock is also a mutex recursive encapsulation, and the API is basically the same as NSLock.

5. NSCondition

  • NScondition is the encapsulation of mutex and COND
@interface NSCondition : NSObject <NSLocking>
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
Copy the code

6. NSConditionLock

  • NSConditionLock is a further encapsulation of NSCondition, and you can set specific conditional values
@interface NSConditionLock : NSObject <NSLocking> {
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
}
@end
Copy the code

7. dispatch_semaphore

  • A semaphore is called a semaphore;
  • The initial value of a semaphore that can be used to control the maximum number of concurrent accesses by a thread;
  • The initial value of the semaphore is 1, which means that only one thread is allowed to access the resource at the same time to ensure thread synchronization
Int value = 1; // initialize semaphore dispatch_semaphore semephore = dispatch_semaphore_creat(value); // If the semaphore value <=0, the current thread will sleep and wait (until the semaphore value >0) // If the semaphore value >0, then decrement 1, Dispatch_semaphore_wait (semaphore, DISPATCH_TIME_FOREVER); // add 1 dispatch_semaphore_signal(semaphore);Copy the code

8. dispatch_queue

  • It is also possible to achieve thread synchronization using serial queues directly from GCD
dispatch_queue_t queue = dispatch_queue_creat("lock_queue", DISPATCH_QUEUE_SERIAL); Dispatch_sync (queue, ^{// tasks})Copy the code