Series of articles:OC Basic principle series.OC Basic knowledge series
The singleton
Speaking of singletons, we typically use GCD’s dispath_once to create singletonsFor singletons, you need to know the following two things:
- 1. Why singletons are executed only once and how are they controlled at the bottom level
- 2. When is a singleton block called
Let’s explore it
Why are singletons executed only once
Dispatch_once = dispatch_once = dispatch_once = dispatch_once
- 1. OnceToken, this is one
A static variable
Because ofStatic variables defined in different locations are different
, soStatic variables are unique
. - 2. Block back to
We see that dispatch_once_f is called, where val isThe incoming
theOnceToken static variable
While func is _dispatch_Block_invoke(block), let’s look at the underlying implementation of dispatch_once_f Through the above code, you can know that the bottom layer is mainly divided into the following steps
- 1.
Convert val, a static variable, to a variable of type L of dispatch_once_gate_t
- 2. By
Os_atomic_load Specifies the task identifier V
- 3. If
V DLOCK_ONCE_DON
E, saidTasks performed
Only return is received - 4. If
Description Locking failed after the task was executed
Then go to the _dispatch_once_mark_done_if_quiesced function,I'm going to store it again
That will beThe identifier is set to DLOCK_ONCE_DONE
. - 5. Otherwise, pass
_dispatch_once_gate_tryenter Attempts to enter a task
Is unlocked, and then execute _dispatch_once_callout to perform the block callback - 6. If at this time
A mission is in progress
.Another mission comes in
,Via the _dispatch_once_wait function
Let the new assignment inInfinite wait
.
When is a singleton block called
We know up hereA func is a task block
And theThe method for handling func is _dispatch_once_callout
In front of,Check _dispatch_once_gate_tryenter Unlock
Let’s look at the _dispatch_once_gate_tryenter method implementationIts source code is mainly throughThe underlying OS_atomic_cMPxCHG method is used for comparison
If theThere is no problem with comparison
,Unlocked, that is, the task identifier is set to DLOCK_ONCE_UNLOCKED
. Let’s look at the source of the _dispatch_once_callout methodThe above method is mainly divided into two steps:
- 1._dispatch_client_callout: Block callback execution
- 2._dispatch_once_gate_broadcast: dispatch_once_gate_broadcast: broadcasts
Let’s take a look at the _dispatch_client_callout method implementation
_dispatch_client_callout mainly performs callbacks, where F is the incoming _dispatch_Block_invoke(block), or asynchronous callback
Take a look at the _dispatch_once_gate_broadcast method implementation
Enter the _dispatch_once_gate_broadcast -> _dispatch_once_mark_done source code, which is to give dGO -> dGO_once a value, and then set the task identifier to DLOCK_ONCE_DONE, that is, unlock the task.
Singleton summary
Above we have explored singletons and solved the questions raised above. Here’s a summary:
- 1. [Singleton execution once principle] : There are two important parameters in GCD singleton.
onceToken
和block
, includingOnceToken is a static variable
, has theuniqueness
, at the bottom byEncapsulated as a variable l of type dispatch_once_gate_t
.L is mainly used to obtain the association of encapsulation of the underlying atom, that is, variable V, through which the status of the task can be queried
, if at this timeV DLOCK_ONCE_DONE
,The task has already been handled once
Return. - 2. [Block call time] : if at this time
The mission was never executed
, will be at the bottomThrough C++ function comparison
That will beThe task is locked
, i.e.,The task status is set to DLOCK_ONCE_UNLOCK
With the aim ofTo ensure the uniqueness of the current task
.Prevent multiple definitions elsewhere
. After locking, the block callback is executed.After the task is executed, the current task is unlocked
.Set the current task status to DLOCK_ONCE_DONE
In theThe next time it comes in, it's not going to execute, it's going to return
- 3. [Impression of multi-threading] : if in
If other tasks come in during the execution of the current task, an infinite number of waits are entered
, the reason isThe current task has acquired the lock
, has been locked,Other tasks are unable to acquire locks
.
Barrier function
In GCD we sometimes use the fence function to determine the order of tasks. There are two main types of fence tasks
- 1.
The synchronization fence function dispatch_barrier_sync
(executed in the main thread) : The previous task will not come here until it completes, but the synchronous fence function willPlug thread
, affecting the execution of subsequent tasks - 2.
The asynchronous fence function dispatch_barrier_async
: Will come here after the previous mission is completed
The most direct function of fence function is to control the execution sequence of tasks and ensure the execution sequence of tasks as planned.
There are several things to note about the fence function:
- 1. Fence function
only
controlThe same
Concurrent queue - 2.
Synchronous fence added to queue
The time,The current thread will be locked
Until theSynchronized fence previous missions
andThe synchronization fence task itself is complete
When the currentThe thread will open and proceed to the next line of code
. - In 3.
When you use the fence function. It makes sense to use custom queues
If I use thetaSerial queues or system-provided global concurrent queues
theThe fence function is equivalent to a synchronization function, which makes no sense
.
Asynchronous fence function
We know from printing that the asynchronous fence function does not block the main thread, it blocks the asynchronous column.
Synchronous fence function
The synchronous fence function blocks the main thread as well as the current thread.
Summary of fence function
- 1.
The asynchronous fence function blocks the queue
And,Must be a custom concurrent queue
.The execution of main thread tasks is not affected
. - 2.
The synchronization fence blocks the thread and is the main thread
Will,The execution of other tasks on the main thread is affected
.
Usage scenarios
The fence function is used in addition toControl the execution sequence of tasks
Can also be used inData security
.
Crash cause: Data is constantly retained and released. Realse has already started before the data is retained, which is equivalent to realse an empty data.
Now let’s add the fence function
The reason for the crash is the same as above, because the barrier function also blocks the global queue of the system, which is also used elsewhere in the system and crashes.
Then there will be no problems
In addition to using the fence function, use the mutex @synchronized (self) {}
Self is used because the lifetime of self is longer than that of I and mArray, ensuring that synchronized does not associate a destroyed object. Be careful with @synchronized(self), which can be crude and cause deadlocks.
Fence function pay attention to the problem
- 1. If
Global queues are used in fence functions
Run,Will collapse
, the reason isThe system also uses global concurrent queues
, the use ofThe fence will intercept the system at the same time, so it will crash
- 2. If you will
Custom concurrent queue changed to serial queue
, i.e., serial,Serial queues are themselves ordered synchronization
At this timeAdd a fence
.It wastes performance
. - 3.
The fence function blocks only once
.
Bottom analysis of asynchronous fence function
Enter theDispatch_barrier_async source code implementation
, itsThe underlying implementation is similar to dispatch_async
, here will not do the analysis, interested can explore the next
Bottom analysis of synchronous fence function
Enter thedispatch_barrier_sync
Source code, implementation as follows
Dispatch_barrier_sync call _dispatch_barrier_sync_f, then call _dispatch_barrier_sync_f_inline source code.
Let’s take a look at the _dispatch_barrier_SYNC_F_inline method implementationThe method is divided into the following steps:
- 1. By
_dispatch_tid_self Obtains the thread ID
. - 2. By
_dispatch_queue_try_acquire_barrier_sync
Determine thread status.
Look at the below_dispatch_queue_try_acquire_barrier_sync
implementation Through the source code we found access_dispatch_queue_try_acquire_barrier_sync_and_suspend
Back to the _dispatch_barrier_SYNC_F_inline method, look at line 1791: _dispatch_SYNc_RECURse method From the above we know:
- 1. By
_dispatch_sync_recurse
.Recursively find the target of the fence function
. - 2. By
_dispatch_introspection_sync_begin
rightForward information is processed
.
Back to the _dispatch_barrier_sync_f_inline method, look at line 1795: the _dispatch_lane_barrier_sync_INVOke_and_complete implementation
A semaphore
The function of a semaphore is generally to makeTask Synchronization
, similar to theThe mutex
, the user canControls the maximum number of concurrent GCDS as required
“Is commonly used in this way
Dispatch_semaphore_create create
The underlying implementation of this function is as follows, mainlyUsed to initialize semaphore
And,Set the maximum number of concurrent GCD requests
.The maximum number of concurrent requests must be greater than 0
.
Dispatch_semaphore_wait lock
The source code implementation of this function sees that its main function isDsema for semaphore
throughos_atomic_dec2o
the--
Operation, which is executed internally in C++atomic_fetch_sub_explicit
Methods.
- 1. If
Value is greater than or equal to 0
Said,If the operation is invalid, the operation succeeds
. - 2. If
The value is equal to the LONG_MIN
.The system will throw a crash
. - 3. If
If value is less than 0, the system enters the long wait
.
Substitute the specific value into zero
os_atomic_dec2o(dsema, dsema_value, acquire); os_atomic_sub2o(dsema, dsema_value, 1, m) os_atomic_sub(dsema->dsema_value, 1, m) _os_atomic_c11_op(dsema->dsema_value, 1, m, sub, -) _r = atomic_FETCH_SUB_explicit (dsema-> dsemA_value, 1), equivalent to dsema-> dsemA_value - 1Copy the code
_dispatch_semaphore_wait_slow
Enter the source code implementation of _dispatch_semaphoRE_wait_slow. When value is less than 0, different operations are performed according to wait event timeout.
Dispatch_semaphore_signal unlock
Os_atomic_inc2o specifies the ++ operation for value. Os_atomic_inc2o specifies the C++ atomic_fetch_add_explicit function.
- 1. If value is greater than 0, the operation is invalid
Execute successfully
. - 2. If value is 0, enter
Long waiting
.
Among themos_atomic_dec2o
The macro definition transformation is as follows Put the specific value into:
os_atomic_inc2o(dsema, dsema_value, release); os_atomic_add2o(dsema, dsema_value, 1, m) os_atomic_add(&(dsema)->dsema_value, (1), m) _os_atomic_c11_op((dsema->dsema_value), (1), m, add, +) _r = atomic_fetch_add_explicit(dsema->dsema_value, 1), Equivalent to dsema-> dsemA_value + 1Copy the code
Semaphore summary
- 1.
dispatch_semaphore_create
The main isInitialize the number limit
. - 2.
dispatch_semaphore_wait
Is theThe semaphore value carries --
, i.e.,Lock operation
. - 3.
dispatch_semaphore_signal
Is theThe semaphore value is ++
, i.e.,Unlock operation
.
Scheduling group (thread group)
Thread group usage
The scheduling group controls the execution sequence of tasks in the following ways
Dispatch_group_create Creates a group. Dispatch_group_async Task dispatch_group_notify Task completion Notification. Dispatch_group_wait Wait time for task execution // The incoming and outgoing groups must be used in pairs. Otherwise, problems may occur. Dispatch_group_enter Dispatch_group_leave Dispatch_group_leaveCopy the code
So let’s see how we can use it
Move dispatch_group_notify to the front
As can be seen from the above three figures, dispatch_group_notify moving forward will cause the dispatch group to become invalid. As can be seen from the third and fourth figures, dispatch_group_enter can exist separately. Dispatch_group_leave and dispatch_group_enter must be sent together. Otherwise, an error is reported. The delay is caused by async being concurrent.
Add dispatch_group_enter
In this case, notify will not be executed because a leave is missing, which makes notify wait forever.
The underlying source
Dispatch_group_create create a group
Create a group and set its property. The value of the group is 0.
To viewdispatch_group_create
The source code
The above method executes: Dispatch_group_create ->_dispatch_group_create_with_count (” dispatch_group_create_with_count “); Where n is 0.
Dispatch_group_enter into groups
Look at the dispatch_group_enter
Run the os_atomic_sub_ORIG2O command to perform — operations on dg->dg.bits to process values
Dispatch_group_leave out group
Look at the dispatch_group_leave source codeSource code for the following operations
- 1.-1 to 0, that is, the ++ operation
- 2. According to the status, the do-while loop wakes up the block task
- 3. If 0 + 1 = 1, enter-leave is unbalanced, that is, leave is called multiple times, and crashes
Now enter the source code of _dispatch_group_WAKEExecution process:
- 1. The do-while loop proceeds
asynchronous
hit - 2.
_dispatch_continuation_async Executes tasks
- 3.
_dispatch_wake_by_address Starts address release
- 4.
_dispatch_RELEase_n The reference is released
_dispatch_continuation_async
This step is consistent with the block callback of an asynchronous function, but not explained
Dispatch_group_notify notice
See dispatch_group_notify source code implementation
If old_state is equal to 0, leave can be awoken with _dispatch_group_wake, and dispatch_group_notify can be awoken with dispatch_group_notify.
Os_mpsc_push_update_tail specifies the macro definitionGet the status code of the dg
.
dispatch_group_async
View the dispatch_group_async source codeYou can see that the dispatch_group_async method does two main things:
- 1.
The packing task
- 2.
Asynchronous processing task
Take a look at _dispatch_continuation_group_async
The dispatch_group_enter method encapsulates the dispatch_group_enter group operation and then calls the _dispatch_continuation_async method, which is also called in the _dispatch_group_WAKE method in the leave execution. Both perform normal asynchronous function low-level operations.
guess: We know that aboveEnter and leave come in pairs
, soBlock executes
After mayImplicitly execute leave
, through breakpoint debugging, printing stack informationThrough the stack information, we see the execution_dispatch_client_callout
Then execute the destruction method_dispatch_call_block_and_release
. Let’s look at the source code of _dispatch_client_callout
“Dispatch_group_async” calls “enter-leave”
Scheduling Group Summary
- 1.
enter-leave
As long asJust come in pairs
.Before and after, distance
(Same scope) - 2.
dispatch_group_enter
At the bottom isThrough C++ functions
To the value of the group--
Operation (i.e. 0 -> -1) - 3.
dispatch_group_leave
At the bottom isThrough C++ functions
To the value of the group++
Operation (that is, -1 -> 0) - 4.
dispatch_group_notif
Y at the bottom is mostly yCheck whether the group state is equal to 0
whenEqual to zero
whenInform awaken
- 5.
Wake up the block task
, you canThrough dispatch_group_leave
, it can also beThrough dispatch_group_notify
- 6.
dispatch_group_async
Its at the bottom of theEnter and leave are called
dispatch_source
Dispatch_source definition
Definition: Dispatch_source is a basic data type used to coordinate the processing of specific underlying system events. It has a low CPU load, occupies few resources and has the advantage of connection.
Dispatch_source replaces asynchronous call-back functions to handle system-specific events. When configuring a Dispatch, you specify the events to monitor, the Dispatch queue, and the code (block or function) to handle the events. When an event occurs, the Dispatch source submits your block or function to the specified queue for execution.
The only reason to use a Dispatch Source instead of dispatch_async is to take advantage of joins.
Dispatch_source process
Call dispatch_source_merge_data on any thread and execute the Dispatch Source’s pre-defined handle (which can be simply interpreted as a block). This process is called Custom Event. User events are the kind of events that dispatch Source support handles.
Simply put: events are signals sent to you by calling the dispatch_source_merge_data function.
HANDLE: A pointer to a pointer that points to a class or structure that has a close relationship with the system. There is also a generic HANDLE called HANDLE. It has the following categories
- 1. Instance handle HINSTANCE
- 2. Bitmap handle HBITMAP
- 3. Device table handle HDC
- 4. Icon handle HICON
use
Create a dispatch
- 1.
type
: dispatch Indicates the event that the source can process - 2.
handle
: is understood as a handle, index, or ID. If you want to listen on a process, you need to pass in the process ID - 3.
mask
: Understand as description, provide more detailed description, let it know exactly what to listen for - 4.
queue
: a queue required by the custom source to handle all response handles
Dispatch the Source species
The type of type is:
Type | instructions |
---|---|
DISPATCH_SOURCE_TYPE_DATA_ADD | Custom event, variable increment |
DISPATCH_SOURCE_TYPE_DATA_OR | Custom event, variable OR |
DISPATCH_SOURCE_TYPE_MACH_SEND | MACH port sending |
DISPATCH_SOURCE_TYPE_MACH_RECV | MACH port reception |
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE | Memory pressure (note: available after iOS8) |
DISPATCH_SOURCE_TYPE_PROC | A process listens, such as the exit of a process, the creation of one or more child threads, or the receipt of UNIX signals by a process |
DISPATCH_SOURCE_TYPE_READ | IO operations, such as file operations, socket operations read response |
DISPATCH_SOURCE_TYPE_SIGNAL | Response when a UNIX signal is received |
DISPATCH_SOURCE_TYPE_TIMER | The timer |
DISPATCH_SOURCE_TYPE_VNODE | File status monitor, files are deleted, moved, renamed |
DISPATCH_SOURCE_TYPE_WRITE | IO operations, such as file operations, write responses to socket operations |
There are many types mentioned above, but we need to pay attention to two types:
- 1.
DISPATCH_SOURCE_TYPE_DATA_ADD
: whenAt the same time
.An event
The triggerA high frequency
, thenDispatch Source
These will beThe response
In order toADD the way
forThe cumulative
And thenWait for system idle
When the finalTo deal with
If theTrigger frequency
To comparescattered
, thenDispatch Source
These events will beResponse, respectively,
. - 2.
DISPATCH_SOURCE_TYPE_DATA_OR
Is:The custom
But it isThe way the OR
forThe cumulative
.
Commonly used functions
/ / pending queue dispatch_suspend (queue) / / dispatch source created by default in a suspended state, before the dispatch source assignment handler must restore dispatch_resume (source) / / send dispatch source event, it is important to note that You cannot pass a value of 0 (the event will not fire), and you cannot pass negative numbers either. Dispatch_source_merge_data // sets the block that responds to the dispatch source event, Dispatch_source_set_event_handler dispatch_source_get_data dispatch_source_get_data dispatch_source_get_data dispatch_source_get_data Uintptr_t dispatch_source_get_handle(dispatch_source_t source); Unsigned long dispatch_source_get_mask(dispatch_source_t source); // Cancel event handling of the Dispatch source -- that is, stop calling the block. If the call to dispatch_suspend simply suspends the dispatch source. void dispatch_source_cancel(dispatch_source_t source); Long dispatch_source_testCancel (dispatch_source_t source); //dispatch a block that is called when the source is cancelled, usually to close a file or socket, etc. Void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler); // Can be used to set up the dispatch source to call a block when it is started and to release the block when it is finished. This function can also be called at any time during a Dispatch source run. void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler);Copy the code
Usage scenarios
Often used forVerification code reverse counting
When,Because dispatch_source does not depend on Runloop
, butInteract directly with the underlying kernel
.Higher accuracy
.
Wrote last
In this paper, we analyze singleton, fence function, semaphore, dispatch group, and dispatch_source, mainly on singleton, fence function, semaphore, dispatch group implementation and view the underlying principle of its implementation. Thread source code is more difficult to understand, interested can go to the official download source code, their own operation to understand. The content is more, some places did not explain in detail, there are not rigorous places I hope you point out! Recently analyzed the underlying implementation of locks, will be written when time is available