Multithreading is an important concept we will encounter in development and interview. Compared to other programming languages and platforms, iOS multithreading is more friendly and easy to use. However, the basic concepts of multithreading need to be taken seriously, which will help us explore pThreads, NSThreads, GCDS, and runloops.

Most of the content in this section is based on official Apple documentation. Documentation address: About Threaded Programming

Leading knowledge

  • POSIX

POSIX Threads, usually referred to as pthreads, is an execution model that exists independently from a language, as well as a parallel execution model. It allows a program to control multiple different flows of work that overlap in time. Each flow of work is referred to as a thread, and creation and control over these flows is achieved by making calls to the POSIX Threads API.

– the POSIX Threads, Wikipedia

Translation:

POSIX (Portable Operating System Interface of UNIX) threads, or PThreads, is a language-independent execution model, also called Parallel execution model. It allows a single program to control multiple overlapping different workflows. Each workflow is a thread that is created and controlled by calling the POSIX thread API.

– POSIX threads, Wikipedia

  • Detached and joinable

In Both Windows and Posix, the default relationship between the main thread and child threads is that all child thread execution terminates once the main thread exits, whether or not the child thread completes execution. Some threads remain in a terminated but not yet destroyed state, and the process must be destroyed after all its threads have been destroyed, in which case the process is dead. Exit, thread function has been completed or terminated in other very way, thread into the end state, but for the thread allocation of system resources may not release, perhaps, before the system restart has not released, the thread to terminate state, still exists as a thread entity in the operating system, when to destroy, depends on the thread attributes. In this case, the main thread and child threads typically define the following two relationships:

Joinable: In this relationship, the main thread needs to explicitly perform the wait operation. After the child thread ends, the main thread finishes the wait operation, and the child thread joins the main thread. Then the main thread continues to perform the next operation after the wait operation. The main thread must meet the converitable child threads. The wait function of the child thread object is called inside the thread function of the main thread. Even if the child thread can finish execution before the main thread and enter the termination state, it must perform the rendezvous operation. Otherwise, the system will never actively destroy the thread, and the system resources allocated to the thread will never be released.

(detached) : Said the child thread does not need to join the main thread, which is separated, in this case, the child thread once in termination status, commonly used in the case of a number of threads more this way, sometimes to the main thread by waiting for the child thread ends, or let the main thread arrangement each child threads waiting for the end of the order, it is difficult or impossible, so more threads and standeth upon in the case, This is often used.

At any point in time, the thread can be combined with (joinable) or is separable (detached), a thread can be can be combined with other threads recycling resources and kill, before by other threads recycling, its storage resources such as stack, is not released, on the contrary, a separate thread cannot be killed by other threads recycling or, Its memory resources are automatically released by the system when it terminates.

By default, threads are non-detached. In this case, the original thread waits for the created thread to terminate. The created thread terminates only when pthread_join returns, freeing up system resources. The detached thread is not waited for by other threads, and when it finishes running, the thread terminates, immediately freeing system resources.

Joinable and Detaced are relationships between main threads and child threads. After the detached app exits, the detached thread enters the terminated state. The stack space of the detached thread will be reclaimed by the system, but the Joinable thread will not be reclaimed. Therefore, you can use the Joinable thread (pThread) to save resources to the disk when the app exits. That is, the joinable thread will be processed before exiting the main thread.

First, thread exploration

1.1 Thread Definition

To understand a thread, we need to understand a process. For iOS, each app is actually a process, which is quite different from Android. With the exception of single processes, each app can only access the sandbox environment of each app before jailbreaking. Thanks to this design, iOS is the most secure operating system in the world (according to Apple 🐶).

The following is apple’s official definition of threads

Threads are a relatively lightweight way to implement multiple paths of execution inside of an application.

Threads are a relatively lightweight way to implement multiple paths of execution within an application.

From a technical standpoint, a thread is a combination of the kernel-level and application-level data structures needed to manage the execution of code. The kernel-level structures coordinate the dispatching of events to the thread and the preemptive scheduling of the thread on one of the available cores. The application-level structures include the call stack for storing function calls and the structures the application needs to manage and manipulate the thread’s attributes and state.

From a technical point of view, threads are the combination of kernel-level and application-level data structures needed to manage code execution. The kernel is responsible for the distribution of thread events and the scheduling of thread priorities, while the application layer is responsible for storing the state and properties of thread functions when they are interrupted for the next kernel switch to run from the storage place.

The definition of a thread can be summarized as follows:

  • A thread is the basic execution unit of a process, in which all tasks of a process are executed
  • In order for a process to perform a task, it must have threads, and the process must have at least one thread
  • By default, the program starts a thread, which is called the main thread or UI thread

Let’s look at the definition of a process:

  • A process is an application that is running in the system
  • Each process is independent of the other, and each process runs in its own dedicated and protected memory space

For iOS, an app is a process. Due to the sandbox mechanism, the process corresponding to each app can only access the memory space corresponding to the current app sandbox, which is independent of each other.

Our app only has one process, and threads, by default, only have one main thread. We can use multi-threading to start the thread and then start the thread to perform the task. The relationship between processes and threads can be summarized as follows:

  • Address space: Threads of the same process share the address space of the same process, while processes are independent of each other.
  • Resource ownership: Threads in the same process share the resources of the same process, such as memory, I/O, and CPU, but resources are independent between processes.
  • The crash of one thread in protected mode has no impact on other processes, but the crash of one thread can cause the crash of the entire process, so multi-process is more robust than multi-thread.
  • Process switchover consumes large resources and is inefficient. So when it comes to frequent context switches, threads are better than processes. Similarly, if concurrent operations are required and some variables are shared, only threads, not processes, can be used.
  • Execution process: Each independent process has a program run entry, sequential execution sequence, and program entry. However, threads cannot execute independently and must be dependent on the application, which provides multiple thread execution control.
  • Threads are the basic unit of CPU scheduling, processes are not.

1.2 Thread-related terminology

Before diving into threads and the technologies they support, it’s worth clarifying some basic terminology.

If you are familiar with UNIX systems, the term task refers to a running process, but it is not defined that way in this article.

The terms used in this article are as follows:

  • The termthreadUsed to refer to a separate execution path for code.
  • The termprocessUsed to refer to a running executable that can contain multiple threads.
  • The termtaskAn abstract concept used to refer to the work that needs to be performed.

1.3 Alternatives to threading

Manually creating threads introduces some uncertainty to our code. Threads are a relatively low level of abstraction and a cumbersome way for applications to support concurrency. If you’re not familiar with working directly with threads, it’s easy to run into thread synchronization and timing issues, which can range from minor issues to application crashes and user data corruption.

So, as you can see above, Apple officially offers the following thread alternatives:

  • Operation Objects: Task object (NSOpeartion) isOS X 10.5A feature that comes out. By encapsulating tasks executed in child threads, the details of underlying thread management are hidden, allowing developers to focus on task execution itself. Typically, task objects queue with task objects (NSOperationQueue) to implement the execution of a task on one or more threads.
  • GCD: OS 10.6Officially launched,GCDIs another technique that allows developers to focus on the task itself without knowing the details of the thread. Through the use ofGCD, you can define the task you need to perform and then add the task to a queueGCDSelect an appropriate thread to execute the task on. Queues take into account the number of available cores and the current load, allowing tasks to be performed more efficiently than using threads directly.
  • Idle-time notifications: idle-time notification. For tasks of relatively low priority and complexity, idle-time notification technology allows tasks to be executed when the application is idle.CocoauseNSNotificationQueueTo implement this technology. To get idle notifications, you need to useNSPostWhenIdleOption toNSNotificationQueueThe queue sends a notification, and then untilRunloopWhen idle, the queue will execute the specific task in the notification.

NSNotificationQueue Official definition

Whereas a notification center distributes notifications when posted, notifications placed into the queue can be delayed until the end of the current pass through the run loop or until the run loop is idle. Duplicate notifications can be coalesced so that only one notification is sent although multiple notifications are posted.

Notifications received by the notification center are distributed to observers registered with the notification center, whereas notifications added to the NSNotificationQueue are distributed only when the current Runloop is about to exit or when the Runloop is idle. Adding notifications repeatedly to the NSNotificationQueue causes the same notifications to be merged, so that only one of the duplicate notifications is sent out when the time comes.

A notification queue maintains notifications in first in, first out (FIFO) order. When a notification moves to the front of the queue, the queue posts it to the notification center, which in turn dispatches the notification to all objects registered as observers.

A notification queue maintains notifications on a first-in, first-out basis. When a notification moves to the head of the queue, the queue sends the notification to the notification center, which dispatches the notification to all objects registered as observers.

Every thread has a default notification queue, which is associated with the default notification center for the process. You can create your own notification queues and have multiple queues per center and thread.

Each thread has a default notification queue and this notification queue is associated with the default notification center for the current process. However, you can create your own notification queues, with multiple notification queues per notification center and thread

For more low-level details on NSNotificationQueue, see this article for an overview of the iOS notification mechanism

  • Asynchronous functions: asynchronous function. System built inapiContains a number of functions that give you automatic concurrency. The implementation of these functions is not your concern, they rely on system daemons and processes or create child threads to perform the tasks you need.
  • Timers: Timer. You can use timers on the main thread of your application to perform periodic tasks that are too trivial to use threads, but still require regular maintenance.
  • Separate processes: Although heavier than threads, it can be useful to create a separate process in cases where the task is tangential only to the application. Processes can be used if the task requires a lot of memory or must be executed with root privileges. For example, you can use a 64-bit server process to compute large data sets while a 32-bit application displays the results to the user.

1.4 Thread Support

1.4.1 CocoaThread-related technologies in

As you can see in the figure above, this is apple’s official website for the threading technology available in Cocoa framework.

The simple translation is:

  • Cocoa Threads: CocoauseNSThreadTo implement threads. In addition to that,NSObjectA package of methods are also provided to spawn threads and perform tasks on existing threads.
  • POSIX threads: POSIXProvides a basis forCA set of language for creating threadsAPI. If you don’t createCocoaProgram, then this would be the best way to create threads.POSIXThe interface is simple to use and provides enough flexibility to configure threads
  • Multiprocessing Services: Multiprocessing service is from the older versionMac OSTransition to the application used based onCThe old interface. This technique is only available inOS XShould be avoided for any new development.

After the thread is started, it mainly runs in three states, which are:

  • Running state
  • The ready state
  • Blocking state

If a thread is not currently running, it is either blocked and waiting for input or ready to run, but has not yet planned to do so. The thread continues to move back and forth between these states until it finally exits and enters the termination state.

1.4.2 Runloop

A run loop is a piece of infrastructure used to manage events arriving asynchronously on a thread. A run loop works by monitoring one or more event sources for the thread. As events arrive, the system wakes up the thread and dispatches the events to the run loop, which then dispatches them to the handlers you specify. If no events are present and ready to be handled, the run loop puts the thread to sleep.

A run loop is a structure that handles asynchronous events received on a thread. A run loop manages one or more event sources on a thread. When the event arrives, the system wakes up the thread and assigns the event to the run loop, which then assigns it to the handler you specify. Running the loop puts the thread to sleep if there are no events or events waiting to be processed.

You are not required to use a run loop with any threads you create but doing so can provide a better experience for the user. Run loops make it possible to create long-lived threads that use a minimal amount of resources. Because a run loop puts its thread to sleep when there is nothing to do, it eliminates the need for polling, which wastes CPU cycles and prevents the processor itself from sleeping and saving power.

You do not need to use run loops for the threads you create, but using run loops can improve the user experience. Running a loop creates a resident thread that uses the least resources. Because running loops puts threads to sleep when there’s nothing to do, you don’t have to save energy by polling, which is an inefficient, CPU-consuming operation.

To configure a run loop, all you have to do is launch your thread, get a reference to the run loop object, install your event handlers, and tell the run loop to run. The infrastructure provided by OS X handles the configuration of the main thread’s run loop for you automatically. If you plan to create long-lived secondary threads, however, you must configure the run loop for those threads yourself.

To configure the run loop, all you have to do is start the thread, get a reference to the run loop object, install the event handler, and tell the run loop to run. The infrastructure provided by OS X will automatically handle the main thread running loop for you. However, if you plan to create long-lived helper threads, you must configure running loops for those threads yourself.

As described in the official documentation above, runloops are closely related to threads and allow child threads to live forever without being recycled by the system. Runloop also improves the user experience by repeatedly working on child threads rather than opening the same thread multiple times to perform the task.

1.4.3 Thread Synchronization

With multithreading, multiple threads can access the same resource at the same time, which can cause serious problems if they attempt to use or modify the resource at the same time. There are generally two ways to solve this problem. One is to eliminate shared resources and let each thread operate exclusively on its own resources. The second is through locks, conditions, atomic operations, and other techniques. Obviously, the second option is used more often.

1.4.4 Communication between threads

As shown in the figure above, Apple officially provides several ways to communicate between threads, which can be summarized as follows:

  • Direct messagingThrough:performSelectorA set of methods for executing tasks specified by one thread on another. Since the execution context of the task is the target thread, messages sent in this manner will be automatically serialized.
  • Global variables, shared memory blocks, and objects: Another simple way to pass information between two threads is to use global variables, shared objects, or shared memory blocks. Although shared variables are fast and easy, they are more fragile than direct messaging. Shared variables must be carefully protected using locks or other synchronization mechanisms to ensure correct code. Failure to do so may result in competitive conditions, data corruption, or crashes.
  • Conditional execution: A condition is a synchronization tool that can be used to control when a thread executes a particular part of code. You can treat a condition as a lock and let the thread run only when the specified condition is met.
  • Runloop sources: a customRunloop sourceConfiguration allows specific application messages to be received on a thread. Due to theRunloop sourceIs event-driven, so threads automatically go to sleep when there is nothing to do, increasing thread efficiency.
  • Ports and socketsPort-based communication is a more sophisticated way of communicating between two threads, but it is also a very reliable technique. More importantly, ports and sockets can be used to communicate with external entities, such as other processes and services. To improve efficiency, useRunloop sourceSo when there is no data waiting on the port, the thread will go to sleep.
  • The message queue: Traditional multiprocessing services define first-in, first-out (fifO)FIFO) queue abstraction for managing incoming and outgoing data. Although message queues are simple and convenient, they are not as efficient as some other communication technologies.
  • CocoaDistributed object: Distributed object is a kind ofCocoaTechnology that provides advanced implementations of port-based communication. Although it is possible to use this technique for interthread communication, it is strongly recommended not to do so because of the overhead involved. Distributed objects are better suited for communicating with other processes, although transactions between these processes are also expensive

1.5 Attention points for using threads

  • Avoid explicit creation threads

Writing thread-creation code by hand is tedious and error-prone, so avoid it if possible.

OS X and iOS provide implicit support for concurrency through other apis. Instead of creating a thread yourself, consider using an asynchronous API, GCD, or manipulation object to get the job done. These techniques do thread-related work for you at the bottom and are guaranteed to be done correctly. In addition, techniques such as GCD and action objects are designed to adjust the number of active threads based on the current system load to manage threads more efficiently than your own code.

  • Let the thread perform the task properly

If you decide to create and manage threads manually, keep in mind that threads consume valuable system resources. You should try to ensure that all tasks assigned to a thread work efficiently over time. At the same time, you don’t have to worry about terminating a thread that spends most of its free time. Threads occupy very little memory, and some of them are wired memory, so freeing up idle threads not only helps reduce the application’s memory footprint, but also frees up more physical memory for use by other system processes.

PS: Memory usage in the Mac can be divided into four broad categories

  • Wired: Occupied by the system core and never expelled from the system’s physical memory.
  • Active: Indicates that the memory data is in use or has just been used.
  • Inactive: Indicates that data in memory is active but has not been used recently.
  • Free: Indicates that the data in this memory is invalid, that is, the amount of memory left.

A set of baseline measurements of the current performance of your application should always be recorded before starting to terminate idle threads. After you try the change, make additional measurements to confirm that the change is actually improving performance, not hurting it.

  • Avoid sharing data structures

The simplest and easiest way to avoid thread-related resource conflicts is to provide each thread in your program with its own copy of the required data. Parallel code works best when you minimize communication and resource contention between threads.

  • Threads and user interfaces

If your application has a graphical user interface, it is highly recommended that you receive user-related events and initiate interface updates on the main thread of your application. This approach helps avoid synchronization issues associated with handling user events and drawing window content. Some frameworks, such as Cocoa, often require this behavior, but even for those that don’t, keeping this behavior on the main thread has the advantage of simplifying the logic for managing the user interface.

Of course, there are cases where performing graphical operations on non-main threads can help improve performance. For example, you can use child threads to create and process images and perform other image-related computational operations. However, when faced with uncertain graphical operations, it is best to perform them on the main thread.

  • Note the problem with thread exit

The process runs until all non-detached threads have exited. By default, only the main thread of the application is created as non-detached. Of course, you can also create non-detached child threads. When a user exits an application, it is generally considered appropriate to terminate all separate threads immediately, because the work done by separate threads is considered optional. However, if your application is using background threads to save data to disk or perform other critical tasks, you may want to create these threads as non-detached threads to prevent data loss when the application exits.

Creating threads as non-detached threads (also known as joinable threads) requires additional work on your part. Since most advanced threading technologies do not create joinable threads by default, you may have to use POSIX apis to create threads. In addition, you must add code to the main thread of your application to join the non-detached threads when they finally exit.

  • Handle exceptions

When an exception is thrown, the exception-handling mechanism relies on the current call stack to perform any necessary cleanup. Because each thread has its own call stack, each thread is responsible for catching its own exceptions. Failure to catch an exception in the worker thread has the same consequence as failure to catch an exception in the main thread: process termination. You cannot throw uncaught exceptions to another thread for processing.

If you need to notify another thread (such as the main thread) of an exception in the current thread, catch the exception and simply send a message to that other thread indicating what happened. Depending on your model and what you want to do, the thread that catches the exception can continue processing (if possible), wait for instructions, or simply exit.

In some cases, an exception handler may be automatically created for you. For example, the @synchronized directive in Objective-C contains an implicit exception handler.

  • Exit the thread completely

The best way to exit a thread is naturally to let the thread reach the end of its main entry point routine. Although there are immediate thread termination capabilities, these should only be used as a last resort. Terminating a thread before it reaches its natural endpoint prevents the thread from cleaning itself. If a thread has allocated memory, opened a file, or acquired other types of resources, your code may not be able to reclaim those resources, resulting in memory leaks or other potential problems.

  • Thread safety in libraries

Although an application developer can control whether an application executes using multiple threads, a library developer cannot. When developing the library, you must assume that the calling application is multithreaded, or can switch to multithreaded at any time. Therefore, you should always use locks on key parts of your code.

It is unwise for library developers to create locks only when the application becomes multithreaded. If you need to lock code at some point, create a Lock object early in the library’s use, preferably through some explicit call to initialize the library. Although you can also use static library initialization functions to create such locks, try to do so only if there is no other way. Performing the initialization function increases the time required to load the library and can adversely affect performance.

Always remember to balance mutex locking and unlocking in your library. You should also remember to lock data structures in the library rather than rely on calling code to provide a thread-safe environment.

If you are developing the Cocoa library, so if you want to be notified when the application into a multithreaded, can register as NSWillBecomeMultiThreadedNotification observer. However, you should not rely on receiving this notification, as it may be sent before the library code is called.

Second, thread management

2.1 Thread Overhead

As shown above:

  • Kernel layer data structure: cost 1KB. This section is used to store thread data structures and properties, much of which is allocated as wired memory and therefore cannot be paged to disk.
  • Stack space: the main thread overhead for iOS applications is 1MB, the main thread overhead for macOS applications is 8MB, and the child thread overhead is 512KB.
  • Creation time: Approximately 90ms, reflecting the time between the initial call to create the thread and the start of execution of the thread’s entry point routine. These numbers were determined by analyzing the average and median generated when threads were created on an Intel-based iMac with a 2GHz dual-core processor and a 1GB RAM running OSX V10.5.

Because of its underlying kernel support, Operation objects are usually faster to create threads. Instead of creating threads from scratch each time, they use thread pools that already reside in the kernel to save allocation time.

2.2 Creating a Thread

Once a thread is created, it must perform tasks to make sense. Here are a few ways to create a thread.

2.2.1 Creating a Thread using NSThread

There are two ways to create threads with NSThreads:

  • usedetachNewThreadSelector:toTarget:withObject:Class method to spawn a new thread.
  • To create aNSThreadInstance object, and then callstartMethods.

Both techniques create detached threads in your application. Detached threads mean that when a thread exits, the system automatically reclaims resources for that thread. This also means that your code does not have to be explicitly joined to the thread later.

Here are two ways to use them in practice:

[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];

NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                        selector:@selector(myThreadMainMethod:)
                                        object:nil];
[myThread start];  // Actually create the thread
Copy the code

One thing to note here is that by instantiating an NSThread object, the underlying thread is created only after the start method is called.

Using initWithTarget: selector: object: method of another method is to write NSThread subclassing and its main method. You can use a rewritten version of this method as the main entry point for a thread.

If you have an already created and NSThread in running a thread object, you can use the performSelector: onThread: withObject: waitUntilDone: method of sending a message to the thread. This method is in the class of NSObject, which means that almost any object can be used. Messages sent using this method will be executed directly by another thread as part of its normal running loop processing. Of course, this means that the target thread must run in its run loop). In this process you may also need to apply locks for thread synchronization, which is, of course, easier than applying port-based inter-thread synchronization.

Although performSelector: onThread: withObject: waitUntilDone: method is very simple to use, but for thread communication between the frequent or sensitive type of task execution time, please do not use this method.

2.2.2 Creating a Thread using POSIX

OS X and iOS provide C-based support for creating threads using POSIX threads apis. This technique can be used for virtually any type of application (including Cocoa and Cocoa Touch applications), and may be more convenient if you are writing software for multiple platforms. The POSIX routine used to create the thread is appropriately called pthread_CREATE.

Here’s a simple use example of pthread_create:

#include <assert.h>
#include <pthread.h>
 
// The task to be performed by the thread
void* PosixThreadMainRoutine(void* data)
{
    // Do some work here.
 
    return NULL;
}
 
// Start the thread
void LaunchThread()
{
    // Thread properties
    pthread_attr_t  attr;
    // Thread object
    pthread_t       posixThreadID;
    / / the return value
    int             returnVal;
 
    // Initialize thread propertiesreturnVal = pthread_attr_init(&attr); assert(! returnVal);// Set the thread to detach statereturnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); assert(! returnVal);// Create a thread, passing in attributes and tasks to execute
    int     threadError = pthread_create(&posixThreadID, &attr, &PosixThreadMainRoutine, NULL);
    
    // Destroy attributesreturnVal = pthread_attr_destroy(&attr); assert(! returnVal);if(threadError ! =0)
    {
         // Report an error.}}Copy the code

For details on how to use pthreads, please refer to this article: PThreads for iOS

2.2.3 Spawning threads with NSObject

All inherited from NSObject object can through performSelectorInBackground: withObject: to perform a task on the child thread.

This method is actually called NSThread bottom class methods detachNewThreadSelector: toTarget: withObject: to create a thread and perform the task.

2.2.4 inCocoaUsed in applicationsPOSIXthread

While nsThreads are the main way to create threads in Cocoa, POSIX threads can be used when needed. However, the following points need to be observed:

  • Protecting the Cocoa Frameworks: Protect the Cocoa Frameworks

    • For multithreaded applications,CocoaFrameworks use locks and other forms of internal synchronization to ensure that they behave correctly. However, to prevent these locks from degrading performance in the case of single threads, they are used in the applicationNSThreadBefore the class generates its first new thread,CocoaThese synchronized elements are not created. If you only usePOSIXThreading routines generate threads,CocoaYou will not receive notifications that you need to know that the application is now multithreaded. When this happens, involveCocoaThe actions of the framework can destabilize the application or crash.
    • To make theCocoaKnowing that you’re going to use a thread, you just useNSThreadClass generates a thread and causes it to exit immediately. Your thread entry point doesn’t need to do anything. useNSThreadThe act of generating a thread is sufficient to ensure thatCocoaThe locks required for the frame are placed in place.
    • If you’re not sureCocoaIf you think your application is multithreaded, you can use itNSThreadisMultiThreadedMethod to check.
  • Mix the Cocoa locks with POSIX locks

    • Mix and match in the same applicationPOSIXCocoaThe lock is secure.CocoaLocks and conditional objects are essentially justPOSIXMutex and condition object wrapper. However, for a given lock, the same interface must always be used to create and manipulate the lock. In other words, it can’t be usedCocoaNSLockObject to operate onpthread_mutex_initFunction creates a mutex object and vice versa.

2.3 Configuring Thread Attributes

Before or after a thread is created, you may want to configure some thread-specific properties as follows:

2.3.1 Configuring the stack space occupied by threads

For each new thread you create, the system allocates a specific amount of memory in the process space to act as a stack for that thread.

The stack manages the stack frame, where local variables declared internally by the thread are stored.

To set the amount of stack space occupied by a thread, you can only specify it before the thread is created.

  • Cocoa

The stack space size is set by instantiating the NSThread object and then calling setStackSize: before calling the start method.

  • POSIX

Create a pthread_attr_t struct object, pass it to the pthread_attr_setSTACKSize method to set the stacksize, and pass pthread_attr_t to the pthread_CREATE function to create POSIX threads.

2.3.2 configure TLS

Each thread maintains a dictionary of key-value pairs that store something during thread execution. This dictionary

Cocoa and POSIX store thread dictionaries differently, so you can’t mix and match calls to the two technologies. However, as long as you stick to one technique in threaded code, the end result should be similar. In Cocoa, you can get TLS using the threadDictionary method of the NSThread object, where you can add any key needed by the thread. In POSIX, the pthread_setSpecific and pthread_getSpecific functions are used to set and get TLS.

2.3.3 Setting the thread separation status

Threads created with NSThreads are separable by default. When the application exits, this type of thread exits, so you need to create joinable threads to perform tasks such as saving data when the application exits. The current solution is POSIX only, with the pthread_attr_setdetachState method to set the attributes of the pthread_t to achieve a non-detached effect. Of course, if you want to change the state of a thread, you can set the thread to be detached via pthread_attr_setdetachState.

2.3.4 Setting the Thread Priority

The threads you create have a default priority associated with them, and when the kernel schedules threads, threads with a higher priority are more likely to execute than threads with a lower priority. But high-priority threads are not guaranteed to run for a fixed time, they are just more likely to be scheduled.

It is generally a good idea to leave the priorities of your threads at their default values. Increasing the priorities of some threads also increases the likelihood of starvation among lower-priority threads. If your application contains high-priority and low-priority threads that must interact with each other, the starvation of lower-priority threads may block other threads and create performance bottlenecks

It is usually best to leave the thread priority as default. Increasing the priority of some threads also increases the likelihood of starvation between low-priority threads. If your application contains high-priority and low-priority threads that must interact with each other, the hunger of low-priority threads can block other threads and cause performance bottlenecks.

This is reminiscent of OSSpinLock, which is no longer secure. For details, see YYKit author’s blog OSSpinLock is no longer secure

If you do want to change thread priority, for Cocoa threads, you can use the setThreadPriority class method of NSThread to set the priority of the currently running thread. For POSIX threads, use the pthread_setschedparam function.

2.4 Write thread entry methods

For the most part, the entry point routines for threads in OS X are structured the same as on other platforms. You can initialize the data structure, do some work or optionally set up the run loop, and clean up after the threaded code has finished. Depending on your design, you may need to take some additional steps when writing an input routine.

  • Create an automatic release pool

Applications linked in an Objective-C framework must generally create at least one auto-release pool in each of their threads. If your application uses ARC, the auto-release pool captures all objects that are automatically released from that thread.

The following code demonstrates the need to create an automatic release pool in the thread entry method under MRC

- (void)myThreadMainRoutine
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
 
    // Do thread work here.
 
    [pool release];  // Release the objects in the pool.
}
Copy the code

Because the top-level auto-release pool does not release objects until the thread exits, resident threads need to create additional auto-release pools for frequent cleanup. For example, a thread using a run loop might create and release an automatic release pool each time it passes through the run loop. Releasing objects frequently prevents your application from becoming too memory intensive and causing performance problems. However, as with any performance-related behavior, you should measure the actual performance of your code and adjust your use of auto-release pools appropriately.

  • Setting exception Handling

If an exception is caught and thrown in your application, it needs to be handled in the thread’s entry method. If an exception is thrown and not caught inside the thread, it will cause the application to exit. You can handle this using C++ and OC style final-try&catch blocks.

  • Set the RunLoop

When executing a task on a child thread, there are two choices: one is that the thread automatically exits after execution, or the other is that the thread is expected to live forever to process the task. The first method requires no additional operations, while the second method requires runloop coordination. Each thread in iOS and macOS has a corresponding Runloop object. After the main thread of the app is started, the corresponding main running loop will also be opened automatically, but for the child thread, it needs to be opened manually.

2.5 Terminating a Thread

The recommended way to exit a thread is to let it exit its entry method normally. Although Cocoa, POSIX, and Multiprocessing Services provide routines that directly kill threads, they are strongly discouraged. Killing the thread directly prevents it from cleaning up on its own, potentially leaking thread-allocated memory and causing any other resources the thread is currently using to not clean up properly, potentially causing problems later.

If you expect to terminate a thread during an operation, you should design the thread from the beginning to respond to cancellation or exit messages. For long-running operations, this might mean periodically stopping work and checking to see if this message is received. If a message does require the thread to exit, the thread will have the opportunity to perform the required cleanup and exit normally; Otherwise, it can simply return to work and process the next block of data.

Here is the code that uses runloop and threadDictionary to periodically check whether to exit a thread:

- (void)threadMainRoutine
{
    // Is there more work to be done
    BOOL moreWorkToDo = YES;
    // Whether to exit the thread
    BOOL exitNow = NO;
    // Get the runloop object corresponding to the current thread
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
 
    // Store exitNow into threadDictionary
    NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
    [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];
 
    // Set the input source for runloop
    [self myInstallCustomInputSource];
 
    while(moreWorkToDo && ! exitNow) {// Specific work to be done
        // Change the moreWorkToDo value when done
 
        // Let runloop run, but timeout immediately if there is no input source to fire.
        [runLoop runUntilDate:[NSDate date]];
 
        // Check whether the input source handler has changed the exitNow value
        exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue]; }}Copy the code

Third, summary

The resources

Threading Programming Guide – Apple

Detached vs. Joinable POSIX threads – StackOverflow

The difference between thread join and detach in c++11