Brief introduction
A thread is one of the methods of executing code concurrently in a program. While there are some new technologies (operations, GCD) that provide a more advanced and efficient concurrency implementation, OS X and iOS also provide an interface for creating and maintaining threads.
This will introduce thread-related packages and how to use them. It also introduces the synchronization of multithreaded code in the program.
About threading Development
Over the years, the maximum processing speed of a computer has been constrained by the processing speed of a single processor. When a single-core processor starts to reach their online, the chip market turns to multicore design so that the computer can handle multiple tasks at the same time. OS x uses these benefits when dealing with system tasks, and your own programs can use them.
What is a thread
Threads are a relatively lightweight way to implement concurrency in your programs. At the system level, programs are running, and the system allocates execution times according to the needs of each program. In each program, you can have one or more threads to execute, and they can handle different tasks at the same time. It is actually the system that manages the scheduling, execution, interruption, etc. of these threads.
Technically, a thread is a combination of the kernel-level and program-level data structures required to execute the code. The kernel-level data structures are used to distribute events to threads and to put threads on the available CPUs. Program-level data structures are used to process the call queue and the properties and states that the thread is using.
In a non-concurrent program, only one thread is executing. The start and end of this thread is accompanied by the program's Main method, during which the implementation is implemented one after the other. In contrast, a program that supports concurrency starts with a thread, and then creates a new thread when needed. Each new thread has their own start and runs independently of the main thread. There are two distinct advantages to multithreading in a program:
- Multithreading can optimize the program's corresponding time
- Multithreading optimizes the performance of programs on multicore processors.
If your program has only one thread, then this thread needs to do everything. It wants the corresponding event, updates the window, and all the behavior implemented in the program. The problem with single threading is that you can only do one thing at a time. So what if one thing takes a long time to finish? When the code is busy calculating the desired result, the program stops the corresponding user event and updates the window. If this behavior persists for a long time, the user may force the program to quit. If you move a custom calculation to another thread, the main thread of the program is free to the corresponding user event.
With the popularity of multicore processors, threading provides a way to optimize performance. Threads can handle different tasks at the same time on different cores, so that you can do more things at a given time.
threads, however, are not a panacea for optimizing performance. There are also potential problems with the benefits of threading. Multithreading can make your code complex. Each thread requires a nuclear other thread to work together to avoid a conflict. Because threads in the same program share the same piece of memory, they can access the same data structure. If two threads are maintaining one data at a time, one thread may overwrite the data in the other, which could break the structure. Although there is a proper protection mechanism, you also need to be aware that the compilation option may bring you small (possibly not so small) bugs
A similar scheme for multithreading
One problem with creating threads yourself is to increase the uncertainty of the code. Threading is a complex way of supporting concurrency in a program, relative to the underlying kernel. If you do not fully understand, you may encounter synchronization and time problems, which may lead to inconsistent program behavior and expected, or crash, or destroy the user's data.
Another question to consider is whether you really need multithreading and concurrency. Sometimes there may be a lot of things to do but it doesn't have to be multi-threading. Multithreading increases the memory and CPU overhead of the process. You may find that the scheduled task cost is too large or has a better alternative to implement.
Some of the similar scenarios for multithreading are listed below. Not only are there multi-line multithreading substitution schemes (such as operation and GCD), but there are some very effective similar schemes for a single thread.
Technology |
description |
operation |
|
grand Central Dispatch (GCD) |
GCD is another thing that keeps you focused on yourself. An alternative. With GCD, you only need to define the task you need to perform and add it to the work queue, and it will automatically schedule your task on the appropriate thread. The work queue looks at the number of cores in the CPU and the load to perform tasks more efficiently than using multi-threading for itself. |
idle message |
|
Async method |
The system provides many asynchronous methods for automating concurrency. These APIs use system processes or custom threads to perform tasks (the actual execution is controlled by the system). When designing a program, prioritize whether or not these async methods are available. |
timer |
You can use timers to handle trivial small things, but you need to check them regularly. |
Support Threads
OS x and iOS have several technologies to support multithreading. In addition, they provide a way to manage and synchronize threads. Here are some things you need to know to use multithreading.
Multithreading Technology
Technology |
Describe |
Cocoa Threads |
Cocoa uses nsthread for multithreading. Cocoa also provides a way to generate new lines Rountines accesses execute code on a wired thread in NSObject. |
POSIX Threads |
POSIX threads provides a C-based multithreaded interface. If you are not writing cocoa program, this is the best choice to implement multithreading. POSIX interfaces are relatively easy and flexible to use. |
At the application level, all threads are essentially the same. After the thread has started, the threads are mainly in three states: run, prepare, block. If the thread is not running, it either blocks the wait input or is ready to run but is not scheduled. Threads constantly switch between these states to know that they are running out.
When creating a new thread, you must develop an ingress method. The entry method contains the programs that you want to run on the thread. When the method returns, or you explicitly terminate it, the thread is permanently stopped and the system recycles it. Because multithreading is relatively memory-and CPU-intensive, it's important to make the Ingress method handle things very clearly or set up a run loop to handle recurring tasks.
Run Loops
Run loop is the infrastructure by which threads accept messages asynchronously. Run loop detects one or more events for a thread. When the event is triggered, the system wakes up the thread and distributes the event to the run loop, which is then distributed to the handler you specify. If there is no event to process, the run loop causes the thread to hang.
There is no need to create a run loop for each thread, but creating one can have a better user experience. Run loop allows the thread to survive for a long time with minimal resources. Because there's nothing left to do, the run loop lets the thread hang, eliminating the polling process. Polling is a waste of CPU and prevents the CPU from saving power and hibernation.
To configure the run loop, you only need to specify a run loop object at the beginning of the thread, set up the event handler, and let run loop do it. The system automatically creates a run loop for the main thread. If you want to create another long-standing thread, you can configure them with a run loop yourself.
Sync Tool
One of the biggest drawbacks of multithreading is competing for resources. If multiple threads are using or modifying a resource at the same time, the problem arises. One way to alleviate this problem is to avoid using common resources so that each thread has their own resources. Completely independent is not a good choice, you can also use locks, conditions, atomic operations and other technologies.
Locks provide a powerful way to ensure that a resource is accessed only by one thread. The most frequently used lock is called a mutex. When a thread wants to access a resource that is locked by another thread, it waits until another thread releases the lock. Many system frameworks provide mutexes, although the underlying technology is the same. In addition, cocoa provides a number of mutex extensions to support different types of behavior, such as recursion.
In addition to the lock, the system also provides conditions, which ensures that the task of the program species is executed in the correct order. Conditions is like a janitor, it blocks the thread until it reaches the running condition. When the condition is reached, condition will let the thread continue to run. Both the POSIX and foundation frameworks provide conditions. (If you are using operation, you can configure the Operation object to perform the task's dependencies, and the behavior of conditions is very similar)
Although locks and conditions are a common design in concurrency, atomic manipulation is another way to protect and synchronize access to resources. Atomic manipulation is a lightweight way of replacing locks when doing mathematical operations or logical operations of data in a table. Atomic operations use special hardware directives to ensure that other threads can access the modified variables when they are complete.
Inter-thread communication
Good design minimizes communication, but inter-thread communication is also necessary. The thread may need to initiate a new task request or report the results of the calculation to the main thread. In these cases, there is a need to have a way to support inter-thread communication. Fortunately, threads in the same process have many ways to communicate.
There are many ways to thread communication, each with advantages and disadvantages. Most of the communication mechanisms on OS X are listed below. (except for message queues and Cocoa distributed object, others are available on iOS). The following techniques are ordered incrementally in terms of complexity.
Communication mechanism
Mechanism |
Describe |
Send a message directly |
The Cocoa program supports executing selector directly on another thread. This means that one thread can execute the method directly on another thread. Because it is executed on another thread, the message sent in this way is serialized directly on the target thread |
Global variables, shared memory or objects |
Another way to communicate between threads is to use global variables to share objects or memory. Although sharing variables is quick and easy, it is more vulnerable than sending messages directly. In concurrent programs, global variables must be carefully protected with locks or other synchronization mechanisms. Not being protected may lead to contention for resources, data corruption, or even crash. |
Conditions |
Conditions is a synchronization tool that can control when a thread executes a piece of code. You can think of conditions as a gatekeeper and only let the thread run when the state is satisfied. |
Run Loop Resource |
The custom run loop is set on your thread to receive the specified message. Because it is event-driven, the run loop automatically suspends threads when nothing is done, which can improve the efficiency of the thread |
Ports and sockets |
Port-based communication between threads is a more complex approach and is also a reliable technology. More importantly, ports and sockets can communicate with external entities, such as other processes and services. For efficiency, the port is implemented based on the run loop resource, so the thread hangs when there is no data. |
Message Queuing |
Multithreaded service based on legacy defines a first-in-one-out queue for managing data. Although Message Queuing is simple and convenient, it is not as efficient as it is with other technologies. |
Cocoa Distributed Object |
Dirtributed objects is a top-level implementation based on port communication. Although it can be used for inter-thread communication, there is a lot of overhead. It is more applicable to interprocess communication because the overhead of interprocess communication is inherently large. |
Tips for Design
To ensure the correctness of the concurrency code, there are some hints that some are helpful in improving performance. For any updates, it is best to write code before, during, and after all to compare the performance of the code.
Avoid manually creating threads
Manually creating threads is cumbersome and error prone, and should be avoided as much as possible. OS x and iOS provide APIs that implicitly support concurrency. Asynchronous API,GCD and Operation objects are more recommended than creating threads for themselves. These technologies do thread-related work behind the scenes and can ensure that they work correctly. In addition, GCD and Operation objects can manage threads more efficiently, depending on the system load, relative to their own creation of threads.
Make the thread reasonably busy
If you need to create a management thread yourself, remember that threads consume valuable system resources. It is reasonable to ensure that the time and output consumed by thread-allocation tasks are justified. At the same time, the threads that do not work most of the time should be terminated. The thread consumes memory, freeing it not only to increase the available memory of the current program, but also to allow more memory to be used by other programs.
Note: It is a good idea to record the current performance of the program before releasing the idle thread. After the change, some performance is recorded to see if there is an optimization.
Avoid sharing data
The simplest way to avoid thread resource collisions is to give each thread a copy of the resources they need. Minimizing inter-thread communication and contention for resources can make them work better.
Even if you pay attention to the locking of shared resources, there may be other problems. For example, some data modifications need to be in a special order, this time can be used based on transaction code to solve the problem. Avoiding competing resources is the first thing to consider when designing multithreaded code.
Threading and user interface
If the program has a graphical user interface, it is recommended to receive user events and update interfaces in the main thread. This avoids thread synchronization issues for handling events and drawing windows. Some frameworks, such as cocoa, must be so, and for some that do not enforce such frameworks, it is recommended to do so and make logic simpler.
There are some exceptions to using another thread to handle graphics operations. For example, use another thread to process the image calculation. This will optimize performance.
Clear the behavior of thread exit
The process exits after all non-independent threads exit. By default, only the main thread of the program is non-independent, but you can also create a non-independent thread yourself. When the user exits the program, the system ends the other independent threads immediately, because what the standalone thread does is considered dispensable. If you are using a background thread to store data to a hard disk or other important work, use a non-independent thread to prevent data loss when the program exits.
Creating a non-independent thread requires some extra work. Because the upper-level threading technology does not create a non-independent thread by default, you need to use the POSIX API to create it. In addition, you need to add code to the main thread that ends with a non-independent thread.
If it is a cocoa program, you can use the Applicationshouldterminate: callback method to let the program delay exit. When the use of inertia ends, important threads should call the Replytoapplicationshouldterminate: method after completing their task.
Handling Exceptions
The exception handling mechanism makes the necessary cleanup based on the current call stack when an exception is thrown. Because each thread has its own call stack, each thread is responsible for catching its own exception. Exception capture failures for other threads are the same as the main thread capture failure, which causes the process that it belongs to be terminated. You cannot throw an exception to another thread.
If you need to prompt other threads (such as the main thread) for the exception state of the current thread, you should catch the exception and then just send the message to another thread to tell it what happened. Depending on your design and what you specifically want to do, the thread that has the exception can continue to execute (if you can), wait for instructions, or exit directly.
Note: In cocoa, the NSException object maintains itself, so it can be passed between threads after snapping.
In some cases, an exception is automatically caught, such as an implicit exception handling @synchronized in Objective-c.
Cleanup after thread end
The best way to exit a thread is to exit naturally, that is, to run to the end of its main entry. Although there are ways to get threads to terminate immediately, these methods are the last option. Terminating the thread before it ends naturally will make it impossible to clean up its own scene. If it has applications for memory, opens files or consumes other resources, other code may not be able to access these resources, or memory leaks, or cause other potential problems.
Thread Safety in the library
Although program development this own control program whether to use multithreading execution, the library developer cannot control. When developing a library, you should assume that the user will be multithreaded or will switch to multithreading at any time. Therefore, the key Code section must be locked.
For library developers, it is unwise to use a lock when using multiple threads. As long as it is possible to use a lock in the library, it is best to make an explicit call when initializing the library. Although you can also create a lock using the static initialization method of the library, try to use it when there is no other way. Initialization also takes an event, and it can affect performance.
Note: The lock and unlock of the mutex in the library must be paired. Be sure to remember to lock, rather than expect the user to use it in a secure, thread-safe environment.
If you are developing a cocoa library, you can register nswillbecomemultithreadednotification to know that the program has become multi-threaded. However, it is not possible to rely on this message and it may have been distributed before the library code was called.