In the iOS multi-threading practice, it is common for child threads to perform time-consuming operations and then return to the main thread to refresh the UI. In iOS, every process starts with a main thread (UI thread), which is the parent thread of other threads. Since the child threads in iOS are independent of Cocoa Touch except for the main thread, only the main thread can update the UI. There are four iOS multi-threading development practices, namely Pthreads, NSThread, GCD and NSOperation. The following are their usage methods, as well as their advantages and disadvantages.

Pthread: cross-platform, applicable to a variety of operating systems, with strong portability, is a universal API of pure C language, and requires programmers to manage the life cycle of threads themselves, which is difficult to use, so it is usually not used in actual development. NSThread: AN API based on the OC language, making it easy to use and object-oriented. The thread declaration cycle is managed by the programmer and used occasionally in real development. GCD: A C-based API that takes full advantage of the device’s multi-core and aims to replace threading technologies such as NSThreads. The life cycle of threads is automatically managed by the system and is often used in actual development. NSOperation: Based on OC language API, the bottom is GCD, added some more simple and easy to use functions, the use of more object-oriented. The thread life cycle is automatically managed by the system and is often used in actual development.

Pthreads

The wikipedia library that implements the POSIX threading standard is often referred to as Pthreads and is commonly used on UNIX-like POSIX systems such as Linux and Solaris. But implementations on Microsoft Windows also exist, such as pThreads-W32, a third-party library implemented directly using the Windows API; With the Windows SFU/SUA subsystem, you can use some of the native POSIX apis provided by Microsoft.

In fact, this is a set of common multithreading API in many operating systems, so portability is very strong, based on C encapsulation of a set of thread framework, iOS is also applicable.

Pthreads creates a thread

- (void)onThread {// 1. Create thread: define pthread_t thread; Pthread_create (&thread, NULL, run, NULL); Set the state of the child thread to detached. After the thread finishes running, it will automatically release all resources pthread_detach(thread). } void * run(void *param) { NSLog(@"% @", [NSThread currentThread]);

    return NULL;
}
Copy the code

2018-03-16 11:06:12.298115+0800 OCgcd [13744:5710531] <NSThread: 0x1C026F100 >{number = 4, name = (null)}

‘pthread_create’ is invalid in C99 ‘#import

– pthread_create (& thread, NULL, run, NULL); The meanings of the parameters in the

  • The first parameter&threadIs a thread object, a pointer to a thread identifier
  • The second is the thread property, which can be assignedNULL
  • The thirdrunRepresents a pointer to a function (runThe corresponding function is the task that needs to be executed in the new thread.
  • The fourth is an assignable argument to the running functionNULL

Pthreads other related methods

  • pthread_create(): Creates a thread
  • pthread_exit(): Terminates the current thread
  • pthread_cancel(): interrupts another thread
  • pthread_join(): blocks the current thread until another thread finishes running
  • pthread_attr_init(): Initializes the properties of the thread
  • pthread_attr_setdetachstate(): Sets the exit property (which determines whether the thread can be joined when it terminates)
  • pthread_attr_getdetachstate(): Gets the out-of-state property
  • pthread_attr_destroy(): Deletes the attributes of the thread
  • pthread_kill(): sends a signal to the thread

Pthreads common functions and functions

  • pthread_t

Pthread_t is used to represent the Thread ID, which varies from implementation to implementation and can be a Structure, so it cannot be considered an integer.

  • pthread_equal

The pthread_equal function is used to compare whether two pthread_t are equal.

int pthread_equal(pthread_t tid1, pthread_t tid2)
Copy the code
  • pthread_self

The pthread_self function is used to get the thread ID of this thread.

pthread _t pthread_self(void);
Copy the code

Pthreads implements mutex

The mutex is the structure of pthread_mutex_t, and the macro is a structural constant. The locks can be initialized statically as follows: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; Int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr);

There are a total of 100 train tickets, open two threads, Beijing and Shanghai two Windows sell tickets at the same time, sell a ticket minus the inventory, use the lock, to ensure that the inventory of Beijing and Shanghai tickets is consistent. The implementation is as follows.

#import "ViewController.h"
#include <pthread.h>@interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. [self onThread]; } pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; NSMutableArray *tickets; - (void)onThread { tickets = [NSMutableArray array]; // Generate 100 ticketsfor(int i = 0; i < 100; i++) { [tickets addObject:[NSNumber numberWithInt:i]]; } // create thread1: define a variable of type pthread_t thread1; Pthread_create (&thread1, NULL, run, NULL); Set the state of child thread1 to detached. This thread will automatically release all resources pthread_detach(thread1) when it finishes running. Create thread2: define a variable of type pthread_t thread2; Pthread_create (&thread2, NULL, run, NULL); Set the state of child thread2 to detached. This thread will automatically release all resources pthread_detach(thread2) when it finishes running. } void * run(void *param) {while (truePthread_mutex_lock (&mutex);if (tickets.count > 0) {
            NSLog(@"Remaining tickets %ld, ticket window %@", tickets.count, [NSThread currentThread]); [tickets removeLastObject]; [NSThread sleepForTimeInterval: 0.2]; }else {
            NSLog(@"The tickets are sold out."); Pthread_mutex_unlock (&mutex);break; } // open the door so other tasks can execute pthread_mutex_unlock(&mutex); }return NULL;
}

@end
Copy the code

2018-03-16 11:47:01.069412+0800 ocgcd[13758:5723862] <NSThread: 0x1C0667600 >{number = 5, name = (null)} 2018-03-16 11:47:01.272654+0800 ocgcd[13758:5723863] 0x1C0672b40 >{number = 6, name = (null)} 2018-03-16 11:47:01.488456+0800 ocgcd[13758:5723862] 0x1C0667600 >{number = 5, name = (null)} 2018-03-16 11:47:01.691334+0800 ocgcd[13758:5723863] 0x1c0672b40>{number = 6, name = (null)} …………….. 2018-03-16 11:47:12.110962+0800 ocgcd[13758:5723862] 0x1C0667600 >{number = 5, name = (null)} 2018-03-16 11:47:12.316060+0800 ocgcd[13758:5723863] 0x1C0672b40 >{number = 6, name = (null)} 2018-03-16 11:47:12.529002+0800 ocgcd[13758:5723862] 0x1C0667600 >{number = 5, name = (null)} 2018-03-16 11:47:12.731459+0800 ocgcd[13758:5723863] 0x1c0672b40>{number = 6, name = (null)} …………….. 2018-03-16 11:47:21.103237+0800 ocgcd[13758:5723862] 0x1C0667600 >{number = 5, name = (null)} 2018-03-16 11:47:21.308605+0800 ocgcd[13758:5723863] 0x1c0672b40>{number = 6, Name = (NULL)} 2018-03-16 11:47:21.511062+0800 OCgCD [13758:5723862] 2018-03-16 11:47:21.511505+0800 Tickets for ocGCD [13758:5723863] are sold out

Lock operations include pthread_mutex_lock(), pthread_mutex_unlock(), and test pthread_mutex_trylock(). Pthread_mutex_trylock () has similar semantics to pthread_mutex_lock(), except that EBUSY is returned when the lock is already occupied rather than pending.

NSThread

Nsthreads are object oriented and have the smallest encapsulation degree and the lightest weight. They are more flexible to use. However, manual management of thread life cycle, thread synchronization and thread locking is costly. The basic use of NSThread is relatively simple. You can dynamically create and initialize NSThread objects, set them, and then start them. You can also quickly create and start new threads using the static method of NSThreads. In addition, the NSObject base class object provides a performSelector family of class extension utility methods that implicitly create NSThread threads quickly. NSThread also provides static tool interfaces to control and retrieve information about the current thread.

NSThread creates a thread

There are three ways to create an NSThread:

  • initWithTargetTo create a thread object and then start it
// Create and start NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; // Set the thread name [thread]setName:@"thread1"]; // set the priority from 0 to 1,1 being the highest [threadsetThreadPriority: 0.9]; // start [thread start]; } - (void)run { NSLog(@"Current thread %@", [NSThread currentThread]);
}
Copy the code

2018-03-16 13:47:25.133244+0800 ocgcd[13811:5776836] Current thread <NSThread: 0x1C0264480 >{number = 4, name = thread1}

  • DetachNewThreadSelector explicitly creates and starts the thread
- (void)onThread {// Create thread with class method and automatically start thread [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; } - (void)run { NSLog(@"Current thread %@", [NSThread currentThread]);
}
Copy the code

2018-03-16 13:49:34.620546+0800 ocgcd[13814:5777803] Current thread <NSThread: 0x1C026A940 >{number = 5, name = (null)}

  • PerformSelectorInBackground implicit threads created and started
- (void) onThread {/ / the use of NSObject methods implicitly created and automatically start [self performSelectorInBackground: @ the selector (run) withObject: nil]; } - (void)run { NSLog(@"Current thread %@", [NSThread currentThread]);
}
Copy the code

2018-03-16 13:54:33.451895+0800 ocgcd[13820:5780922] Current thread <NSThread: 0x1C4460280 >{number = 4, name = (null)}

NSThread method

// get currentThread +(NSThread *)currentThread; + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument; // Whether threads are threaded + (BOOL)isMultiThreaded; // threadDictionary - (NSMutableDictionary *)threadDictionary; + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; // Cancel the thread - (void)cancel; // Start thread - (void)start; // Exit thread + (void)exit; // threadPriority + (double)threadPriority; + (BOOL)setThreadPriority:(double)p;
- (double)threadPriority NS_AVAILABLE(10_6, 4_0);
- (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0); // Call stack return address + (NSArray *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0); + (NSArray *)callStackSymbols NS_AVAILABLE(10_6, 4_0); // Set the thread name - (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0); - (NSString *)name NS_AVAILABLE(10_5, 2_0); - (NSUInteger)stackSize NS_AVAILABLE(10_5, 2_0); - (void)setStackSize:(NSUInteger)s NS_AVAILABLE(10_5, 2_0); // get mainThread + (NSThread *)mainThread; - (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); + (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main + (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0); // Initialize method - (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2 _0); - (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0); - (BOOL)isFinished NS_AVAILABLE(10_5, 2_0); // Whether to cancel the thread - (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0); - (void)cancel NS_AVAILABLE(10_5, 2_0); // Thread start - (void)start NS_AVAILABLE(10_5, 2_0); - (void)main NS_AVAILABLE(10_5, 2_0); / / thread body method @ the end / / multi-threaded inform FOUNDATION_EXPORT nsstrings * const NSWillBecomeMultiThreadedNotification; FOUNDATION_EXPORT NSString * const NSDidBecomeSingleThreadedNotification; FOUNDATION_EXPORT NSString * const NSThreadWillExitNotification; @ interface NSObject (NSThreadPerformAdditions) / / communicate with the main thread - (void) aSelector performSelectorOnMainThread: (SEL) withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; Equivalent to the first method with kCFRunLoopCommonModes // communicate with other child threads - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)waitNS_AVAILABLE(10_5, 2_0); // equivalent to the first method with kCFRunLoopCommonModes // Create and start thread implicitly - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg NS_AVAILABLE(10_5, 2_0);Copy the code

NSThread Indicates the status of a thread

  • Starting a thread
// thread start - (void)start;Copy the code
  • Blocking threads
+ (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti;Copy the code
  • The end of the thread
// End thread + (void)exit;
Copy the code

As for the question of cancel, when the cancel method is used, it only changes the status identification of the thread, but cannot end the thread, so we should use it together with isCancelled method.

- (void) onThread {/ / the use of NSObject methods implicitly created and automatically start [self performSelectorInBackground: @ the selector (run) withObject: nil]; } - (void)run { NSLog(@"Current thread %@", [NSThread currentThread]);
    
    for (int i = 0 ; i < 100; i++) {
        if(I == 20) {// Cancel thread [[NSThread currentThread] cancel]; NSLog(@"Cancel thread %@", [NSThread currentThread]);
        }
        
        if ([[NSThread currentThread] isCancelled]) {
            NSLog(@"End thread %@", [NSThread currentThread]); // End the thread [NSThread]exit];
            NSLog(@"This line of code will not print."); }}}Copy the code

2018-03-16 14:11:44.423324+0800 ocgcd[13833:5787076] Current thread <NSThread: 0x1C4466840 >{number = 4, name = (null)} 2018-03-16 14:11:44.425124+0800 ocgcd[13833:5787076] Cancel thread <NSThread: 0x1C4466840 >{number = 4, name = (null)} 2018-03-16 14:11:44.426391+0800 ocgcd[13833:5787076] 0x1c4466840>{number = 4, name = (null)}

The thread status is shown below:

1. Create: instantiate objects

2, ready: send a start message to the thread object, the thread object is added to the “schedulable thread pool” waiting for CPU scheduling; Detach method and performSelectorInBackground method can directly instantiate a thread object and add “schedulable thread pool”

3. Running: THE CPU is responsible for scheduling the execution of threads in the “schedulable thread pool”. Before the execution of threads is completed, the state may switch back and forth between “ready” and “running”

4, blocking: when a predetermined condition is met, you can use sleep or lock to block the execution of the thread, the impact of the methods are: sleepForTimeInterval, sleepUntilDate, @synchronized(self) thread lock; When a thread object becomes blocked, it is removed from the “schedulable thread pool” and the CPU no longer schedules it

5, death,

Manner of death:

Normal death: the thread completes execution. Abnormal death: the thread dies –>[NSThread exit]: after forcibly aborting, the subsequent code will not die outside the execution thread: [threadObj cancel]– > Notifies the thread object of cancellation. IsCancelled == YES is returned

The isFinished property of the thread object after death is YES; If a CANCle message is sent, the isCancelled property of the thread object is YES; StackSize == 0 after death, memory space is freed

NSThread Communication between threads

In development, we often do time-consuming operations in child threads and then go back to the main thread to refresh the UI. This involves communication between the child thread and the main thread. Take a look at the official thread communication method for NSThreads.

/ / perform operations on the main thread - (void) performSelectorOnMainThread: (SEL) aSelector arg withObject: (id)waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)waitmodes:(NSArray<NSString *> *)array; Equivalent to the first method with kCFRunLoopCommonModes // Perform the operation on the specified thread - (void)performSelector (SEL)aSelector onThread:(NSThread *)thr withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)waitNS_AVAILABLE(10_5, 2_0); // Perform the operation on the current thread, calling NSObject's performSelector: related method - (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;Copy the code

Below is a classic download image DEMO to show communication between threads. The detailed steps are as follows: 1. Start a sub-thread and download the picture in the sub-thread. 2. Go back to the main thread and refresh the UI to display the image in UIImageView.

func onThread() {
    let urlStr = "http://tupian.aladd.net/2015/7/2941.jpg"
    self.performSelector(inBackground: #selector(downloadImg(_:)), with: urlStr)} @objc func downloadImg(_ urlStr: String) {// Prints the current threadprint("Download picture thread", thread.current) // Get the image link guardlet url = URL.init(string: urlStr) else {return} // Download image binary data guardlet data = try? Data.init(contentsOf: url) else {return} // Set the image guardlet img = UIImage.init(data: data) else {return} // go back to the main thread and refresh UI self.performSelector(onMainThread:#selector(downloadFinished(_:)), with: img, waitUntilDone: false)} @objc func downloadFinished(_ img: UIImage) {// Prints the current threadprint("Refresh the UI thread"Imageview self.imageview. image = image}Copy the code

NSThread Thread security

Thread safety, also known as thread synchronization, mainly solves the problem of multiple threads competing for operational resources, such as train tickets, which are sold at multiple ticket offices across the country. How to ensure that the ticket pool of multiple places to keep the same, you need to use multi-thread synchronization technology to achieve.

NSThread safety is the same as GCD and NSOperation thread safety. The implementation method is nothing more than locking (various locks implementation), semaphore, GCD fence, etc. Specific implementation, you can see the iOS multithreading detail: concept thread synchronization section.

GCD

GCD (Grand Central Dispatch) is a C language concurrency technology framework proposed by Apple for multi-core parallel computing. GCDS automatically use more CPU cores; Automatically manages the thread lifecycle (thread creation, task scheduling, thread destruction, etc.) The programmer only needs to tell the COMMUNIST party how it wants to perform what tasks, without writing any thread management code.

GCD underlying implementation

The GCD API we use is C language functions, all contained in the LIBdispatch library, DispatchQueue through the structure and linked list is implemented as FIFO queue; FIFO queues are managed by blocks appended by functions such as dispatch_async; Instead of joining the FIFO queue directly, the Block joins the Dispatch Continuation structure before joining the FIFO queue, A Dispatch Continuation is used to remember the Dispatch Group to which a Block belongs and some other information (equivalent to context). Dispatch Queue can be set with dispatch_set_target_queue(). You can set the target of the Dispatch Queue that performs the processing of the Dispatch Queue. The target can be beaded with multiple connected Dispatch queues, but the Main Dispatch Queue must be set at the end of the connection string, Or Global Dispatch queues of various priorities, or Global Dispatch queues prepared for Serial Dispatch queues

Eight priorities of the Global Dispatch Queue:

.High priority .Default Priority .Low Priority .Background Priority .High Overcommit Priority .Default Overcommit Priority .Low Overcommit Priority .Background Overcommit Priority

The Global Dispatch Queue with Overcommit is used in the Serial Dispatch Queue to force a thread’s Dispatch Queue regardless of the system state. Each of the eight Global Dispatch queues uses one pthread_workqueue

  • The GCD initialization

During GCD initialization, the pthread_workqueue is generated using the pthread_workqueue_create_NP function. The pthread_workqueue is included in the API of PTHREADS provided by Libc, which uses the Bsthread_register and the workq_open system call to get the workqueue information after initializing the XNU kernel’s workqueue.

XNU has four types of workqueues:

WORKQUEUE_HIGH_PRIOQUEUE WORKQUEUE_DEFAULT_PRIOQUEUE WORKQUEUE_LOW_PRIOQUEUE WORKQUEUE_BG_PRIOQUEUE

These four workqueues have the same execution priorities as the Global Dispatch Queue

  • The Dispatch Queue executes a block

1. When a Block is executed in the Global Dispatch Queue, libDispatch fetts the Dispatch Continuation from the FIFO of the Global Dispatch Queue itself, Call the pthread_workqueue_additem_NP function, The arguments to the pthread_workqueue_additem_NP function are passed the Global Dispatch Queue, the workqueue information that matches its priority, and the callback function that executes the Dispatch Continuation.

Thread_workqueue_additem_np () uses the workq_kernRETURN system call to tell the workqueue to add items that should be executed.

3. According to this notification, The XUN kernel determines whether to generate threads based on the system state. In the case of an Overcommit priority Global Dispatch Queue, the workQueue always generates threads.

4. The thread of the workqueue executes pthread_workqueue(), which uses libDispatch’s callback to execute blocks added to Dispatch Continuatin.

5. After the execution of the Block, the Dispatch Group is notified to terminate, the Dispatch Continuation is released, and the next Block added to the Dispatch Continuation is prepared to execute.

GCD usage steps

The procedure for using GCD is actually very simple, only two steps.

1, create a queue (serial queue or concurrent queue) 2, add the task to the waiting queue, then the system will execute the task according to the task type (synchronous execution or asynchronous execution)

How to create/get queues

The iOS system already has two kinds of queues by default, primary queue (serial queue) and global queue (concurrent queue), so we can use the interface provided by GCD to create concurrent queue and serial queue.

About the concepts and differences of synchronous, asynchronous, serial and parallel, inIOS multithreading in detail: ConceptsIs explained in detail

  • Creating a serial queue
// Create a serial queuelet que = DispatchQueue.init(label: "com.jacyshan.thread")
Copy the code

Create queue initialization with DispatchQueue, serial queue by default. The first parameter is a unique identifier for the Queue, which is used for DEBUG and can be null. The name of the Dispatch Queue is recommended to use the reversed full domain name of the application ID.

  • Creating concurrent queues
// Create a concurrent queuelet que = DispatchQueue.init(label: "com.jacyshan.thread", attributes: .concurrent)
Copy the code

The second parameter. Concurrent indicates that a concurrent queue is created

  • Gets the main queue

The Main Dispatch Queue is a special serial Queue provided by the GCD and all tasks that are placed in the Main Queue are placed in the Main thread.

// Get the main queuelet que = DispatchQueue.main
Copy the code
  • Get global queue

GCD provides a Global Dispatch Queue by default.

// Get the global queuelet que = DispatchQueue.global()
Copy the code
How to create a task

The GCD provides sync and Async methods for synchronous task creation.

Que.sync {print("Task 1", thread.current)} que.async {print(Task 2 "", Thread.current)
}
Copy the code

There are two queues (serial queue/concurrent queue) and two ways to execute tasks (synchronous/asynchronous), so we have four different combinations. The four different combinations are:

Synchronous execution + concurrent queue 2. Asynchronous execution + concurrent queue 3. Synchronous execution + serial queue 4

The system also provides two special queues: global concurrent queue and primary queue. Global concurrent queues can be used as normal concurrent queues. But the main queue is a little bit special, so we have two more combinations. So there are six different combinations.

6. Asynchronous execution + primary queue

The six combination modes are shown as follows.

The difference between Concurrent queue Serial queues The home side column
Synchronous execution No new thread is started and the task is executed sequentially No new thread is started and the task is executed sequentially Main thread call: deadlock not executing other thread calls: no new thread is opened and the task is executed sequentially
Asynchronous execution Start a new thread and execute tasks concurrently Start a new thread (1 thread) to execute tasks in serial No new thread is started and the task is executed sequentially

GCD six combinations to achieve

Synchronous + concurrent queue

Tasks are executed in the current thread, in order.

    @IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
	    print("Code block ------begin") // Create a concurrent queuelet que = DispatchQueue.init(label: "com.jackyshan.thread", Attributes:.concurrent) // Add task 1 que.sync {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 1 "thread -", thread.current) // Print the current Thread}} // add task 2 que.sync {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 2 "thread -", Thread.current)
            }
        }
	    print("Code block ------end")}Copy the code

CurrentThread — <NSThread: 0x1C0078F00 >{number = 1, name = main} code block ——begin Task 1Thread– <NSThread: 0x1C0078f00 >{number = 1, name = main} 0x1C0078f00 >{number = 1, name = main} 0x1C0078F00 >{number = 1, name = main} Task 2Thread– <NSThread: 0x1C0078F00 >{number = 1, name = main} code block ——end

You can see that the task is executed on the main thread because the synchronization does not start a new thread. Because synchronization blocks threads, our interface’s clicks and swipes are ineffective when our task takes time. Because the UI operation is also on the main thread, but the duration of the task has blocked the thread, the UI operation is unresponsive.

Asynchronous + concurrent queue

Multiple threads are started and tasks are executed alternately.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Create a concurrent queuelet que = DispatchQueue.init(label: "com.jackyshan.thread", Attributes:.concurrent) // Add task 1 que.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 1 "thread -", thread.current) // Print current Thread}} // Add task 2 que.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 2 "thread -", Thread.current)
        }
    }
    print("Code block ------end")}Copy the code

CurrentThread — <NSThread: 0x1C0062180 >{number = 1, name = main} code block ——begin Code block —— End Task 1Thread– <NSThread: 0x1C02695C0 >{number = 4, name = (null)} 0x1C02694C0 >{number = 5, name = (null)} 0x1C02695C0 >{number = 4, name = (null)} Task 2Thread– <NSThread: 0x1C02694c0 >{number = 5, name = (null)}

As you can see, the task is executed on multiple new threads and not on the main thread, so the task does not affect UI operations while performing time-consuming operations. Asynchrony can start new threads, and concurrency can perform tasks of multiple threads. Because asynchrony does not block threads, the code block ——begin code block ——end executes immediately and is printed only after the other threads have completed the time-consuming operation.

Synchronous + serial queue

Tasks are executed in the current thread, in order.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin"// Create a serial queue. DispatchQueue defaults to a serial queuelet que = DispatchQueue.init(label: "com.jackyshan.thread"// Add task 1 que.sync {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 1 "thread -", thread.current) // Print the current Thread}} // add task 2 que.sync {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 2 "thread -", Thread.current)
        }
    }
    print("Code block ------end")}Copy the code

CurrentThread — <NSThread: 0x1C40658C0 >{number = 1, name = main} code block ——begin Task 1Thread– <NSThread: 0x1C40658C0 >{number = 1, name = main} 0x1C40658C0 >{number = 1, name = main} 0x1C40658C0 >{number = 1, name = main} Task 2Thread– <NSThread: 0x1C40658C0 >{number = 1, name = main} code block ——end

Similarly, when a synchronization task is executed sequentially, no new thread is started and the task is executed on the main thread. Time-consuming operations affect UI operations.

Asynchronous + serial queue

Start a new thread and execute tasks in the new thread, in order.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin"// Create a serial queue. DispatchQueue defaults to a serial queuelet que = DispatchQueue.init(label: "com.jackyshan.thread"// Add task 1 que.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 1 "thread -", thread.current) // Print current Thread}} // Add task 2 que.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 2 "thread -", Thread.current)
        }
    }
    print("Code block ------end")}Copy the code

CurrentThread — <NSThread: 0x1C407B700 >{number = 1, name = main} code block ——begin Code block —— End Task 1Thread– <NSThread: 0x1C0462440 >{number = 4, name = (null)} 0x1C0462440 >{number = 4, name = (null)} 0x1C0462440 >{number = 4, name = (null)} Task 2Thread– <NSThread: 0x1C0462440 >{number = 4, name = (null)}

From the print you can see that only one thread is opened (serial only opens one thread) and the tasks are executed sequentially on the new thread. The task is executed after the code block ——begin ——end (asynchronous does not wait for the task to complete)

The main queue is a special serial queue in which all tasks (asynchronous synchronization) are executed on the main thread.

Synchronization + main queue

Deadlocks occur when tasks are invoked in the main thread, but not in other threads.

  • Execute on the main threadSynchronization + main queue

The interface is stuck, all operations are unresponsive. Quests waiting for each other cause deadlocks.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Get the main queueletQue = dispatchqueue. main // add task 1 que.sync {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 1 "thread -", thread.current) // Print the current Thread}} // add task 2 que.sync {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 2 "thread -", Thread.current)
        }
    }
    print("Code block ------end")}Copy the code

CurrentThread — <NSThread: 0x1C00766C0 >{number = 1, name = main} Code block ——begin

As you can see, the code block ——begin stops executing, freezes, and crashes after a while.

Many articles about deadlock feeling is not very clear, in fact, the process is to wait for each other, simple explanation is as follows:

The reason is that onThread() is executed on the main thread, and task 1 is added to the main queue until the onThread() task completes. Task 1 is in the onThread() task. According to FIFO rules, onThread() is added to the main queue first and should be executed first, but task 1 is waiting for onThread() to complete. This creates a deadlock, waiting for each other to complete the task.

  • Execute in another threadSynchronization + main queue

The main queue does not start new threads, and tasks are executed sequentially on the main thread

override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, Task to be executed onThread taskinBackground: #selector(onThread), with: nil)
}

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Get the main queueletQue = dispatchqueue. main // add task 1 que.sync {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 1 "thread -", thread.current) // Print the current Thread}} // add task 2 que.sync {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 2 "thread -", Thread.current)
        }
    }
    print("Code block ------end")}Copy the code

CurrentThread — <NSThread: 0x1C0076a80 >{number = 4, name = (null)} Code block ——begin Task 1Thread– <NSThread: 0x1C406D680 >{number = 1, name = main} 0x1C406D680 >{number = 1, name = main} 0x1C406D680 >{number = 1, name = main} Task 2Thread– <NSThread: 0x1C406D680 >{number = 1, name = main} code block ——end

OnThread tasks are executed on other threads, are not added to the main queue, and do not wait for tasks 1 and 2 to complete, so there is no deadlock.

If the onThread task is executed in the same queue and the synchronization task is executed in the onThread, it will also cause deadlock. In a queue, there will be a phenomenon of waiting for each other, synchronization is not good to open a new thread, so it will deadlock.

Asynchronous + main queue

The main queue does not start new threads, and tasks are executed sequentially on the main thread

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Get the main queueletQue = dispatchqueue. main // add task 1 que.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 1 "thread -", thread.current) // Print current Thread}} // Add task 2 que.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2) // Simulate time-consuming operationsprint(Task 2 "thread -", Thread.current)
        }
    }
    print("Code block ------end")}Copy the code

CurrentThread — <NSThread: 0x1C4076500 >{number = 1, name = main} code block ——begin Code block —— End Task 1Thread– <NSThread: 0x1C4076500 >{number = 1, name = main} 0x1C4076500 >{number = 1, name = main} 0x1C4076500 >{number = 1, name = main} Task 2Thread– <NSThread: 0x1C4076500 >{number = 1, name = main}

As you can see, the onThread task completes without waiting for tasks 1 and 2 to complete (asynchronous execution immediately without waiting), so there is no deadlock. A primary queue is a serial queue in which tasks are executed one after another in sequence.

Other methods of the COMMUNIST Party

AsyncAfter delays execution

Many times we want to postpone a task, use DispatchQueue. At this time. The main asyncAfter is very convenient. This method is not immediately executed, nor is it absolutely accurate to delay execution. It can be seen that it appends the task to the main queue after the delay time. If there are other time-consuming tasks in the main queue, the delayed task will also wait for the completion of the task.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Main thread execution is delayedlet delay = DispatchTime.now() + .seconds(3)
    DispatchQueue.main.asyncAfter(deadline: delay) {
        print("asyncAfter---", Thread.current)
    }
}
Copy the code

CurrentThread — <NSThread: 0x1C407F900 >{number = 1, name = main} code block ——begin asyncAfter– <NSThread: 0x1c407f900>{number = 1, name = main}

DispatchWorkItem

DispatchWorkItem is a block of code that can be called on any queue, so the code inside it can be run in the background or on the main thread. It’s really easy to use, just a bunch of code that you can call directly instead of writing a block of code at a time like before. We can also use its notification to complete the callback task.

When you’re doing multi-threaded business, there’s often a need to send me a notification when we’re done when we’re doing time-consuming operations to tell me that the task is done.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin"// Create workItemlet workItem = DispatchWorkItem.init {
        for _ in0.. < 2 {print("Task workItem -". Thread.current)}} // Execute workItem dispatchqueue.global ().async {workitem.perform ()} // notify after execution workItem.notify(queue: DispatchQueue.main) {print("Task workItem completed --", Thread.current)
    }
    
    print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C4079300 >{number = 1, name = main} code block ——begin Code block —— End workItem– <NSThread: 0x1C42705C0 >{number = 5, name = (null)} workItem– <NSThread: 0x1C42705C0 >{number = 5, name = (null)} workItem completed — <NSThread: 0x1C4079300 >{number = 1, name = main}

You can see that we executed the workItem asynchronously using the global queue and were notified when the task was complete.

DispatchGroup queue group

Some complex services may require several queues to perform tasks and then put them all into a Group. When all the queues in the Group are complete, the Group sends a notification to return to the main queue to complete other tasks.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Create DispatchGroupletGroup = DispatchGroup() group.enter() // Global queue (concurrent queue) dispatchqueue.global ().async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint("Task 1 -- -- -- -- -- -", thread.current)// Print Thread} group.leave()} // can be used if the last queue is executed after completionwaitGroup.wait () group.enter() // Customize the task dispatchqueue.init (label:"com.jackyshan.thread").async {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint(Task 2 "-- -- -- -- -- -"} group. Leave ()} UI group. Notify (queue: dispatchqueue.main) {print("Mission completed ------", thread.current)// Print threads}print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C0261bc0 >{number = 1, name = main} Code block ——begin Task 1—— <NSThread: 0x1C046B900 >{number = 5, name = (null)} Task 1—— <NSThread: 0 x1c046b900 > {number = 5, name = (null)} block — — — — — – end task 2 NSThread: — — — — — — – 0x1C0476B00 >{number = 6, name = (null)} Task 2—— <NSThread: 0x1C0476B00 >{number = 6, name = (null)} Task completed —— <NSThread: 0x1C0261bc0 >{number = 1, name = main}

Two queues, one executing the default global queue and one executing its own custom concurrent queue, are notified when both queues are complete. If we comment out group.wait(), we see that the tasks in the two queues are executed alternately.

Dispatch_barrier_async Fence method

Dispatch_barrier_async is an implementation of oc, Swift is an implementation of que.async(flags:.barrier).

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Create a concurrent queuelet que = DispatchQueue.init(label: "com.jackyshan.thread", attributes:.concurrent) // Execute tasks asynchronously que.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint("Task 0 -- -- -- -- -- -", thread.current)// Print Thread}} // Execute task asynchronously que.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint("Task 1 -- -- -- -- -- -"}} que.async(flags:.barrier) {que.async(flags:.barrier) {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint(Task 2 "-- -- -- -- -- -", thread.current)// Prints the Thread} // executes the task behind the queue after execution} // executes the task asynchronously. Que.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint("Three tasks -- -- -- -- -- -", thread.current)// Print threads}}print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C4078a00 >{number = 1, name = main} Code block ——begin Code block —— End Task 0—— <NSThread: 0x1C427D5C0 >{number = 5, name = (null)} Task 1—— <NSThread: 0x1C0470f80 >{number = 4, name = (null)} Task 0—— <NSThread: 0x1C427D5C0 >{number = 5, name = (null)} Task 1—— <NSThread: 0x1C0470F80 >{number = 4, name = (null)} Task 2—— <NSThread: 0x1C0470F80 >{number = 4, name = (null)} Task 2—— <NSThread: 0x1C0470F80 >{number = 4, name = (null)} Task 3—— <NSThread: 0x1C0470F80 >{number = 4, name = (null)} Task 3—— <NSThread: 0x1C0470F80 >{number = 4, name = (null)}

As you can see, due to the barrier operations performed by task 2, task 0 and task 1 are executed alternately. Task 2 waits for the completion of task 0 and task 1, and task 3 waits for the completion of task 2. You can also see that no new thread is opened to run the task due to the barrier operation.

Quality Of Service (QoS) and priority

When using the GCD and dispatch queue, we often need to tell the system which tasks in the application are important and need to be performed at a higher priority. Of course, since the main queue is always used to handle the UI and interface responses, tasks executed on the main thread always have the highest priority. In either case, just give the system the necessary information and iOS will prioritize the queues and the resources they require (such as the CPU execution time required) according to your needs. While all tasks will eventually get done, the important difference is which tasks get done faster and which get done later.

The information used to specify the importance and priority of a task is called Quality of Service (QoS) in GCD. In fact, QoS is an enumerated type with several specific values, and we can initialize the queue with the appropriate QoS value based on the desired priority. If the QoS is not specified, the queue is initialized with the default priority. For a detailed understanding of the available QoS values, you can refer to this documentation. Make sure you read this documentation carefully. The following list summarizes the available values for Qos, also known as Qos classes. The first class code has the highest priority, and the last class represents the lowest priority:

  • userInteractive
  • userInitiated
  • default
  • utility
  • background
  • unspecified

Create two queues with priority userInteractive and look at the effect:

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Create concurrent queue 1let que1 = DispatchQueue.init(label: "com.jackyshan.thread1", qos:.userinteractive, attributes:.concurrent) // Create concurrent queue 2let que2 = DispatchQueue.init(label: "com.jackyshan.thread2", qos: .userInteractive, attributes: .concurrent)
    
    que1.async {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint("Task 1 -- -- -- -- -- -", thread.current)// Print Thread}} que2.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint(Task 2 "-- -- -- -- -- -", thread.current)// Print threads}}print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C0073680 >{number = 1, name = main} Code block ——begin Code block —— End Task 1—— <NSThread: 0x1C047CD80 >{number = 5, name = (null)} Task 2—— <NSThread: 0x1C0476C40 >{number = 3, name = (null)} Task 1—— <NSThread: 0x1C047CD80 >{number = 5, name = (null)} Task 2—— <NSThread: 0x1C0476c40 >{number = 3, name = (null)}

The two queues have the same priority and the tasks are executed alternately, as we predicted.

Queue1 = background; queue1 = background;

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Create concurrent queue 1let que1 = DispatchQueue.init(label: "com.jackyshan.thread1", qos:.background, attributes:.concurrent) // Create concurrent queue 2let que2 = DispatchQueue.init(label: "com.jackyshan.thread2", qos: .userInteractive, attributes: .concurrent)
    
    que1.async {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint("Task 1 -- -- -- -- -- -", thread.current)// Print Thread}} que2.async {for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint(Task 2 "-- -- -- -- -- -", thread.current)// Print threads}}print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C006AFC0 >{number = 1, name = main} Code block ——begin Code block —— End Task 2—— <NSThread: 0x1C4070180 >{number = 5, name = (null)} Task 1—— <NSThread: 0x1C006D400 >{number = 6, name = (null)} Task 2—— <NSThread: 0x1C4070180 >{number = 5, name = (null)} Task 1—— <NSThread: 0x1C006D400 >{number = 6, name = (null)}

As you can see, queue1’s priority has been reduced to background, so queue2’s tasks take precedence.

There are other priorities, from high to low, that don’t compare to each other.

DispatchSemaphore semaphore

A GCD Semaphore is a Dispatch Semaphore, a signal that holds counts. It’s like crossing a toll rail on a highway. Open the railing when it is possible to pass, and close the railing when it is not possible to pass. In Dispatch Semaphore, count is used to accomplish this function, and a count of 0 waits and cannot pass. If the count is 1 or greater than 1, the count is reduced by 1 without waiting.

  • DispatchSemaphore(value: ): used to create a semaphore. You can specify the initial semaphore value, which defaults to 1.
  • semaphore.wait(): determines the semaphore. If it is 1, proceed. If it is 0, wait.
  • semaphore.signal(): indicates that the operation is finished, and the semaphore is increased by 1. The waiting task will continue to be executed at this time.

You can use DispatchSemaphore to implement thread synchronization and ensure thread safety.

If we have a pool of tickets and several threads are selling tickets at the same time, we want to make sure that each thread gets the same pool of tickets. DispatchSemaphore and DispatchWorkItem, and let’s see what happens.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin"Var tickets = [Int]()for i in0.. <38 {tickets.append(I)} // creates a signal with an initial count of 1let semaphore = DispatchSemaphore(value: 1)
    
    let workItem = DispatchWorkItem.init {
        semaphore.wait()
        
        if tickets.count > 0 {
            Thread.sleep(forTimeInterval: 0.2)// Time-consuming operationprint("Remaining votes", ticket.count, thread.current) ticket.removelast ()}else {
            print("There are no tickets in the pool."Semaphore.signal ()} // Create a concurrent queue 1let que1 = DispatchQueue.init(label: "com.jackyshan.thread1", qos:.background, attributes:.concurrent) // Create concurrent queue 2let que2 = DispatchQueue.init(label: "com.jackyshan.thread2", qos: .userInteractive, attributes: .concurrent)
    
    que1.async {
        for _ in0.. <20 { workItem.perform() } } que2.async {for _ in0.. <20 { workItem.perform() } }print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C407A6C0 >{number = 1, name = main} code block ——begin Code block —— End 38 <NSThread: 0x1C44706C0 >{number = 8, name = (null)} 37 <NSThread: 0x1C0264c80 >{number = 9, name = (null)} 36 <NSThread: 0x1c44706c0>{number = 8, name = (null)} ……………. 0x1C0264C80 >{number = 9, name = (null)} 0x1C44706C0 >{number = 8, name = (null)} 17 <NSThread: 0x1C0264c80 >{number = 9, name = (null)} 0x1c44706c0>{number = 8, name = (null)} ……………. <NSThread: 0x1C44706C0 >{number = 8, name = (null)} 0x1C0264C80 >{number = 9, name = (null)} There is no ticket in the ticket pool

You can see that our resources have no data clutter caused by resource contention. Semaphores well realize the function of multi-thread synchronization.

DispatchSource

DispatchSource provides an interface for monitoring low-level system objects such as Mach ports, Unix descriptors, Unix signals, and VFS nodes for activity and submitting event handlers to dispatch queues for asynchronous processing when such Activity Occurs. DispatchSource provides a set of interfaces for submitting Handers to monitor low-level events, including Mach Ports, Unix Descriptors, Unix signals, and VFS Nodes.

Swift is a protocol oriented language. This class is a factory class that implements various sources. For example, DispatchSourceTimer (itself a protocol) represents a timer.

  • DispatchSourceProtocol

Basic protocol, implemented by all DispatchSource applications. This protocol provides public methods and attributes: since different sources use different attributes and methods, only a few public methods are listed here

  • Activate / / activation
  • Suspend / / hung
  • Resume / / continue
  • Cancel // Cancel (cancel asynchronously, which ensures that the current eventHander execution is complete)
  • SetEventHandler // Event handling logic
  • SetCancelHandler // Clear logic when cancelling
  • DispatchSourceTimer

In Swift 3, it’s easy to create a Timer with GCD (new feature). DispatchSourceTimer is itself a protocol. For example, write a timer, execute it after 1 second, and then automatically cancel it after 10 seconds, allowing an error of 10 milliseconds

PlaygroundPage.current.needsIndefiniteExecution = true

public letThe timer. The timer = DispatchSource makeTimerSource (). SetEventHandler {/ / pay attention to the circular reference here, [the weak self]in
    print("Timer fired at \(NSDate())")
}

timer.setCancelHandler {
    print("Timer canceled at \(NSDate())"Timer. ScheduleRepeating (deadline:.now() +.seconds(1), interval: 2.0, leeway:.microseconds(10))print("Timer resume at \(NSDate())")

timer.resume()

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute:{
    timer.cancel()
})
Copy the code

Deadline means start time, leeway means tolerance.

DispatchSourceTimer can also be called once.

func scheduleOneshot(deadline: DispatchTime, leeway: DispatchTimeInterval = default)
Copy the code
  • UserData

DispatchSource UserData part is also a powerful tool, this part includes two protocols, the change of the two protocols are used to merge data, only one is in accordance with the + (plus), one is in accordance with the | (with).

DispatchSourceUserDataAdd DispatchSourceUserDataOr

Using both sources, GCD helps us automatically merge these changes and then call back to EventHandler when appropriate (target queue idle) to avoid excessive CPU usage due to frequent callbacks.

let userData = DispatchSource.makeUserDataAddSource()

var globalData:UInt = 0

userData.setEventHandler {
    let pendingData = userData.data
    globalData = globalData + pendingData
    print("Add \(pendingData) to global and current global is \(globalData)")
}

userData.resume()

let serialQueue = DispatchQueue(label: "com")

serialQueue.async {
    for var index in1... 1000 { userData.add(data: 1) }for var index in1... 1000 { userData.add(data: 1) } }Copy the code

Add 32 to global and current global is 32 Add 1321 to global and current global is 1353 Add 617 to global and current global is 1970 Add 30 to global and current global is 2000

NSOperation

NSOperation is an abstract base class based on GCD that encapsulates threads into operations to be performed. It does not need to manage thread lifecycle and synchronization, but is more controllable than GCD. For example, you can join operation depends on (addDependency), set up operation queue maximum number of concurrent execution of operations (setMaxConcurrentOperationCount), cancel the operation (cancel), etc. As an abstract base class, NSOperation does not encapsulate our operations, so we need to use two entity subclasses: NSBlockOperation and a custom derived NSOperation subclass. NSOperation needs to work with NSOperationQueue to implement multithreading.

NSOperation Indicates the operation procedure

The custom Operation

Create a class that inherits Operation and override the main method. When start is called, tasks in main are executed at the appropriate time.

class ViewController: UIViewController {
    
    @IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
        print("Code block ------begin")
        
        let op = JKOperation.init()
        op.start()
        
        print("Code block ------ end")
    }
}


class JKOperation: Operation {
    override func main() {
        Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task -", Thread.current)
    }
}
Copy the code

CurrentThread — <NSThread: 0x1C007A280 >{number = 1, name = main} code block ——begin Task — <NSThread: 0x1C007A280 >{number = 1, name = main} code block —— End

You can see the custom JKOperation. After initialization, the start method is called. The task inside the main method is executed, and it is executed on the main thread. Because we did not use OperationQueue, no new threads were created.

Using BlockOperation

After initializing BlockOperation, call the start method.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin")
    
    let bop = BlockOperation.init {
        Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task -", Thread.current)
    }
    bop.start()
    
    print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C4070640 >{number = 1, name = main} code block ——begin Task — <NSThread: 0x1C4070640 >{number = 1, name = main} code block —— End

Implemented with OperationQueue

After initializing the OperationQueue, call addOperation and the code block will execute automatically. The call mechanism execution is automatically implemented in the OperationQueue. The addOperation method actually generates a BlockOperation object and executes the start method for that object.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin")
    
    OperationQueue.init().addOperation {
        Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task -", Thread.current)
    }
    
    print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C006A700 >{number = 1, name = main} code block ——begin Code block —— End the task — <NSThread: 0x1c0469900>{number = 5, name = (null)}

As you can see the OperationQueue is initialized, by default a concurrent queue is generated and an asynchronous operation is performed, so the thread that prints the job is not in the main thread.

How to create/get queues

OperationQueue does not implement a serial queue, nor does it implement a global queue as GCD does. Only concurrent queue implementation and primary queue fetching.

  • Creating concurrent queues

Concurrent queue tasks are executed concurrently (almost simultaneously) to maximize the advantages of multiple CPU cores. See others said through setting concurrent maxConcurrentOperationCount realized serial number 1. In fact is wrong, by setting the priority can control the queue task execution alternately, when it comes to maxConcurrentOperationCount will implement code below.

// Create a concurrent queuelet queue = OperationQueue.init()
Copy the code

The OperationQueue is initialized, and the default implementation is a concurrent queue.

  • Gets the main queue

Our main queue is a serial queue where tasks are executed one after another.

// Get the main queuelet queue = OperationQueue.main
Copy the code

The task of obtaining the main queue is performed asynchronously.

@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Get the main queuelet queue = OperationQueue.main
    
    queue.addOperation {
        Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task 1 -", Thread.current)
    }

    queue.addOperation {
        Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint(Task 2 - "", Thread.current)
    }
    
    print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C0064F80 >{number = 1, name = main} Code block ——begin Code block —— End Task 1– <NSThread: 0x1C0064F80 >{number = 1, name = main} Task 2– <NSThread: 0x1C0064F80 >{number = 1, name = main}

How to create a task
  • Create tasks using BlockOperation
BlockOperation.init {
    Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task 1 -", Thread.current)
}.start()
Copy the code
  • Create a task through the OperationQueue
// Create a concurrent queuelet queue = OperationQueue.init()

queue.addOperation {
    Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task -", Thread.current)
}
Copy the code

NSOperation related methods

Maximum number of concurrent operation: maxConcurrentOperationCount

MaxConcurrentOperationCount by default to 1, said not to limit, can be carried out concurrently. MaxConcurrentOperationCount this value should not exceed limit system (64), even if you set the value of a large, the system will automatically adjust for min {set itself value, the system set the default maximum}.

  • Set up themaxConcurrentOperationCountfor1, realizing serial operation.
@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Create a concurrent queueletQueue = OperationQueue. The init () / / set the maximum number of concurrent for 1 queue. MaxConcurrentOperationCount = 1let bq1 = BlockOperation.init {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task 1 -", Thread.current)
        }
    }
    
    let bq2 = BlockOperation.init {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint(Task 2 - "", Thread.current)
        }
    }

    queue.addOperations([bq1, bq2], waitUntilFinished: false)
    
    print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C0067880 >{number = 1, name = main} Code block ——begin Code block —— End Task 1– <NSThread: 0x1C046AD40 >{number = 4, name = (null)} Task 1– <NSThread: 0x1C046AD40 >{number = 4, name = (null)} Task 2– <NSThread: 0x1C046AD40 >{number = 4, name = (null)} Task 2– <NSThread: 0x1C046AD40 >{number = 4, name = (null)}

You can see from the print that the tasks in the queue are executed sequentially. This is because the tasks in the queue have the same priority, and when there is only one number of concurrent queues, the tasks are executed in order.

  • Set up themaxConcurrentOperationCountfor1To implement concurrent operations.
@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Create a concurrent queueletQueue = OperationQueue. The init () / / set the maximum number of concurrent for 1 queue. MaxConcurrentOperationCount = 1let bq1 = BlockOperation.init {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task 1 -", Thread.current)
        }
    }
    bq1.queuePriority = .low
    
    let bq2 = BlockOperation.init {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint(Task 2 - "", Thread.current)
        }
    }
    bq2.queuePriority = .high
    
    let bq3 = BlockOperation.init {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Three tasks -", Thread.current)
        }
    }
    bq3.queuePriority = .normal

    queue.addOperations([bq1, bq2, bq3], waitUntilFinished: false)
    
    print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C4261780 >{number = 1, name = main} code block ——begin Code block —— End Task 2– <NSThread: 0x1C0279340 >{number = 4, name = (null)} Task 2– <NSThread: 0x1C0279340 >{number = 4, name = (null)} Task 3– <NSThread: 0x1C0279340 >{number = 4, name = (null)} Task 3– <NSThread: 0x1C0279340 >{number = 4, name = (null)} Task 1– <NSThread: 0x1C0279340 >{number = 4, name = (null)} Task 1– <NSThread: 0x1C0279340 >{number = 4, name = (null)}

By setting the queuePriority, the tasks in the queue can be executed alternately.

  • Set up themaxConcurrentOperationCountfor11To implement concurrent operations.
@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Create a concurrent queueletQueue = OperationQueue. The init () / / set the maximum number of concurrent for 1 queue. MaxConcurrentOperationCount = 11let bq1 = BlockOperation.init {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task 1 -", Thread.current)
        }
    }
    
    let bq2 = BlockOperation.init {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint(Task 2 - "", Thread.current)
        }
    }

    queue.addOperations([bq1, bq2], waitUntilFinished: false)
    
    print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C407A200 >{number = 1, name = main} Code block ——begin Code block —— End Task 2– <NSThread: 0x1C42647C0 >{number = 4, name = (null)} Task 1– <NSThread: 0x1C04714c0 >{number = 3, name = (null)} Task 2– <NSThread: 0x1C42647C0 >{number = 4, name = (null)} Task 1– <NSThread: 0x1C04714c0 >{number = 3, name = (null)}

MaxConcurrentOperationCount is greater than 1, realized the concurrent operation.

Wait for execution to complete: waitUntilFinished

WaitUntilFinished blocks the current thread until the operation is complete. Can be used for sequential synchronization of threads.

For example, implementing two concurrent queues to execute in sequence.

    @IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
        print("Code block ------begin") // Create concurrent queue 1let queue1 = OperationQueue.init()

        let bq1 = BlockOperation.init {
            for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task 1 -", Thread.current)
            }
        }
        
        let bq2 = BlockOperation.init {
            for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint(Task 2 - "", Thread.current)
            }
        }

        queue1.addOperations([bq1, bq2], waitUntilFinished: true) // Create concurrent queue 2let queue2 = OperationQueue.init()
        
        let bq3 = BlockOperation.init {
            for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Three tasks -", Thread.current)
            }
        }
        
        let bq4 = BlockOperation.init {
            for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task 4 -", Thread.current)
            }
        }
        
        queue2.addOperations([bq3, bq4], waitUntilFinished: true)
        
        print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C407D1C0 >{number = 1, name = main} code block ——begin Task 1– <NSThread: 0x1C0467D40 >{number = 4, name = (null)} Task 2– <NSThread: 0x1C0460a00 >{number = 3, name = (null)} Task 1– <NSThread: 0x1C0467D40 >{number = 4, name = (null)} Task 2– <NSThread: 0x1C0460a00 >{number = 3, name = (null)} Task 3– <NSThread: 0x1C0467D40 >{number = 4, name = (null)} Task 4– <NSThread: 0x1C0460a00 >{number = 3, name = (null)} Task 3– <NSThread: 0x1C0467D40 >{number = 4, name = (null)} Task 4– <NSThread: 0x1C0460A00 >{number = 3, name = (null)} Code block —— End

By setting waitUntilFinished on the queue to true, you can see that queue2’s tasks start to execute concurrently only after queu1’s tasks finish executing concurrently. And all execution takes place between the end of the code block ——begin and the end of the code block ——. Queue1 and Queue2 block the main thread.

Operation dependency: addDependency
@IBAction func onThread() {// Prints the current threadprint("currentThread---", Thread.current)
    print("Code block ------begin") // Create a concurrent queuelet queue = OperationQueue.init()

    let bq1 = BlockOperation.init {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Task 1 -", Thread.current)
        }
    }
    
    let bq2 = BlockOperation.init {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint(Task 2 - "", Thread.current)
        }
    }
    
    let bq3 = BlockOperation.init {
        for _ in0.. <2 { Thread.sleep(forTimeInterval: 0.2)// Perform time-consuming operationsprint("Three tasks -", Thread.current)
        }
    }
    
    bq3.addDependency(bq1)
    
    queue.addOperations([bq1, bq2, bq3], waitUntilFinished: false)
    
    print("Code block ------ end")}Copy the code

CurrentThread — <NSThread: 0x1C0065740 >{number = 1, name = main} Code block ——begin Code block —— End Task 1– <NSThread: 0x1C0660300 >{number = 5, name = (null)} Task 2– <NSThread: 0x1C4071340 >{number = 6, name = (null)} Task 1– <NSThread: 0x1C0660300 >{number = 5, name = (null)} Task 2– <NSThread: 0x1C4071340 >{number = 6, name = (null)} Task 3– <NSThread: 0x1C0660300 >{number = 5, name = (null)} Task 3– <NSThread: 0x1C0660300 >{number = 5, name = (null)}

Do not add operation dependencies

CurrentThread — <NSThread: 0x1C4072C40 >{number = 1, name = main} Code block ——begin Code block —— End Task 1– <NSThread: 0x1C0270C80 >{number = 7, name = (null)} Task 2– <NSThread: 0x1C447DF00 >{number = 4, name = (null)} Task 3– <NSThread: 0x1C0270cc0 >{number = 8, name = (null)} Task 1– <NSThread: 0x1C0270c80 >{number = 7, name = (null)} Task 2– <NSThread: 0x1C447DF00 >{number = 4, name = (null)} Task 3– <NSThread: 0x1C0270cc0 >{number = 8, name = (null)}

You can see that task 3 adds an operation dependency to task 1, and the execution waits for task 1 to complete.

Priority: queuePriority

NSOperation provides the queuePriority property, which applies to operations in the same operation queue but not to operations in different operation queues. By default, all the operation of the newly created object is NSOperationQueuePriorityNormal priority. However, we can change the execution priority of the current operation in the same queue by using the setQueuePriority: method.

public enum QueuePriority : Int {

    case veryLow

    case low

    case normal

    case high

    case veryHigh
}
Copy the code
  • When all dependencies for an operation have been completed, the operation object is usually in a ready state, waiting to execute.

  • The queuePriority attribute determines the order of start execution between operations that enter the ready state. And priorities are no substitute for dependencies.

  • If a queue contains both high-priority and low-priority operations, and both operations are ready, the queue performs the high-priority operation first. For example, if op1 and OP4 have different priorities, the operation with higher priority will be performed first.

  • If a queue contains both ready and unready operations, the unready operations have a higher priority than the ready operations. Then, the ready operation will be executed first, even though it is of low priority. Priorities are no substitute for dependencies. If you want to control the startup sequence between operations, you must use dependencies.

NSOperation Common attributes and methods
  • Cancel operation method

Open func Cancel () cancels the operation, marking isCancelled status.

  • Method to determine operation status

Open var isExecuting: Bool {get} Checks whether the operation is running.

Open var isFinished: Bool {get} Checks whether the operation is complete.

Open var isConcurrent: Bool {get} Checks whether the operation is serial.

Open var isAsynchronous: Bool {get} Determines whether the operation is concurrent.

Open var isReady: Bool {get} checks whether an operation isReady. The value is dependent on the operation.

Open var isCancelled: Bool {get} Determines whether the operation has been marked as cancelled.

  • Synchronous operation

Open func waitUntilFinished() blocks the current thread until the operation ends. Can be used for sequential synchronization of threads.

open var completionBlock: (() -> Swift.Void)? Executes the completionBlock when the current operation completes.

Open func addDependency(_ op: Operation) Adds dependencies so that the current Operation depends on the completion of Operation op.

Open func removeDependency(_ op: Operation) Removes dependencies from Operation op.

Open var Dependencies: [Operation] {get} An array of Operation objects that are executed before the current Operation begins.

Open var queuePriority: Operation.QueuePriority sets the priority of the current Operation in the queue.

Common properties and methods of NSOperationQueue
  • Cancel/pause/resume action

Open func cancelAllOperations() cancels all operations on the queue.

Open var isSuspended: Bool checks whether the queue isSuspended. True: pause status, false: resume status. You can set the pause and resume of an operation. True indicates the pause queue and false indicates the resume queue.

  • Synchronous operation

Open func waitUntilAllOperationsAreFinished () blocks the current thread until the queue operations performed completely.

  • Add/get operations

Open func addOperation(_ block: @escaping () -> swift.void) Adds an operation object of type NSBlockOperation to the queue.

Open func addOperations(_ OPS: [Operation], waitUntilFinished Wait: Bool) Adds an array of operations to the queue.

Open var Operations: [Operation] {get} Array of operations currently in the queue (an Operation is automatically cleared from this array after completion).

Open var operationCount: Int {get} The operand in the current queue.

  • Access to the queue

open class var current: OperationQueue? {get} gets the current queue, and returns nil if the current thread is not running on NSOperationQueue.

Open class var main: OperationQueue {get} Gets the primary queue.

Pay attention to my

Welcome to follow the public account: Jackyshan, technical dry goods first wechat, the first time push.