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 oneA static variableBecause 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 incomingtheOnceToken static variableWhile 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. ByOs_atomic_load Specifies the task identifier V
  • 3. IfV DLOCK_ONCE_DONE, saidTasks performedOnly return is received
  • 4. IfDescription Locking failed after the task was executedThen go to the _dispatch_once_mark_done_if_quiesced function,I'm going to store it againThat will beThe identifier is set to DLOCK_ONCE_DONE.
  • 5. Otherwise, pass_dispatch_once_gate_tryenter Attempts to enter a taskIs unlocked, and then execute _dispatch_once_callout to perform the block callback
  • 6. If at this timeA mission is in progress.Another mission comes in,Via the _dispatch_once_wait functionLet the new assignment inInfinite wait.

When is a singleton block called

We know up hereA func is a task blockAnd theThe method for handling func is _dispatch_once_calloutIn front of,Check _dispatch_once_gate_tryenter UnlockLet’s look at the _dispatch_once_gate_tryenter method implementationIts source code is mainly throughThe underlying OS_atomic_cMPxCHG method is used for comparisonIf 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.onceTokenblock, 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 onceReturn.
  • 2. [Block call time] : if at this timeThe mission was never executed, will be at the bottomThrough C++ function comparisonThat will beThe task is locked, i.e.,The task status is set to DLOCK_ONCE_UNLOCKWith 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_DONEIn theThe next time it comes in, it's not going to execute, it's going to return
  • 3. [Impression of multi-threading] : if inIf 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 functiononlycontrolThe sameConcurrent queue
  • 2.Synchronous fence added to queueThe time,The current thread will be lockedUntil theSynchronized fence previous missionsandThe synchronization fence task itself is completeWhen 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 queuesIf I use thetaSerial queues or system-provided global concurrent queuestheThe 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 queueAnd,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 threadWill,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 tasksCan 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. IfGlobal queues are used in fence functionsRun,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 willCustom concurrent queue changed to serial queue, i.e., serial,Serial queues are themselves ordered synchronizationAt 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_syncSource 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_syncDetermine thread status.

Look at the below_dispatch_queue_try_acquire_barrier_syncimplementation Through the source code we found access_dispatch_queue_try_acquire_barrier_sync_and_suspendBack 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_beginrightForward 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 semaphoreAnd,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 semaphorethroughos_atomic_dec2othe--Operation, which is executed internally in C++atomic_fetch_sub_explicitMethods.

  • 1. IfValue is greater than or equal to 0Said,If the operation is invalid, the operation succeeds.
  • 2. IfThe value is equal to the LONG_MIN.The system will throw a crash.
  • 3. IfIf 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 invalidExecute successfully.
  • 2. If value is 0, enterLong waiting.

Among themos_atomic_dec2oThe 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_createThe main isInitialize the number limit.
  • 2.dispatch_semaphore_waitIs theThe semaphore value carries --, i.e.,Lock operation.
  • 3.dispatch_semaphore_signalIs 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_createThe 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 proceedsasynchronoushit
  • 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 executesAfter mayImplicitly execute leave, through breakpoint debugging, printing stack informationThrough the stack information, we see the execution_dispatch_client_calloutThen 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-leaveAs long asJust come in pairs.Before and after, distance(Same scope)
  • 2.dispatch_group_enterAt the bottom isThrough C++ functionsTo the value of the group--Operation (i.e. 0 -> -1)
  • 3.dispatch_group_leaveAt the bottom isThrough C++ functionsTo the value of the group++Operation (that is, -1 -> 0)
  • 4.dispatch_group_notifY at the bottom is mostly yCheck whether the group state is equal to 0whenEqual to zerowhenInform awaken
  • 5.Wake up the block task, you canThrough dispatch_group_leave, it can also beThrough dispatch_group_notify
  • 6.dispatch_group_asyncIts 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 eventThe triggerA high frequency, thenDispatch SourceThese will beThe responseIn order toADD the wayforThe cumulativeAnd thenWait for system idleWhen the finalTo deal withIf theTrigger frequencyTo comparescattered, thenDispatch SourceThese events will beResponse, respectively,.
  • 2.DISPATCH_SOURCE_TYPE_DATA_ORIs:The customBut it isThe way the ORforThe 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 countingWhen,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