Series of articles:OC Basic principle series.OC Basic knowledge series

Before we have a more in-depth understanding of multi-threaded GCD, in the use of GCD, we will use the lock in order to ensure thread safety, this article we will explore the use of the lock principle

Lock the overview

The performance of the lock

There is a famous lock performance diagram, here is a referenceFrom the figure above, we can know that the lock performance from low to high is as follows: OSSpinLock -> dispatch_semaphone -> pthread_mutex -> NSLock -> NSCondition -> NSRecursiveLock -> NSConditionLock -> synchronized

Lock the classified

Locks are divided into two main categories: mutex and spin locks. Let’s break down the lock shown above

The mutex

Definition: A mutex is a mechanism used in multithreaded programming to prevent two threads from reading or writing to the same common resource (such as a global variable) at the same time. This is achieved by cutting code into critical sections. Exclusive locks:

  • NSLock
  • pthread_mutex
  • @synchronized

spinlocks

Definition: In a spinlock, the thread checks whether a variable is available. This is a busy wait because the thread keeps executing consistently throughout the process. Once a spinlock is acquired, the thread holds it until it explicitly releases the spinlock. Spin-locks avoid the scheduling overhead of the process context and are therefore effective in situations where threads block only for short periods of time. For the iOS property modifier atomic, come with a spin lock:

  • OSSpinLock
  • atomic

Conditions for the lock

Definition: A conditional lock is a conditional variable that goes to sleep when certain resource requirements are not met by a process, that is, it is locked. Conditions of locks:

  • NSCondition
  • NSConditionLock

Recursive locking

Definition: A recursive lock is one that can be locked N times by the same thread without causing a deadlock. A recursive lock is a special mutex, that is, a mutex with recursive properties

  • pthread_mutex(recursive)
  • NSRecursiveLock

A semaphore

Definition: A semaphore is a more advanced synchronization mechanism. A mutex is a special case of a semaphore with a value of 0/1. The semaphore can have more value space for more complex synchronization than just the mutex between threads.

  • dispatch_semaphore

Read-write lock

Definition: A read/write lock is actually a special type of spin lock. The access to a shared resource is divided into readers and writers. Readers only read the shared resource, while writers write the shared resource. This type of lock improves concurrency over spin locks.

  • 1. ARead-write lockAt the same timeonlyThere areOne writer or multiple readers(It is related to the number of cpus), butYou can't have both readers and writersIn theRead/write lock hold durationIs alsoGrab the failure.
  • 2. IfRead/write locks currently have no readersAlso,No writer, thenwritercanGet read/write locks immediately.Otherwise it has to spin thereUntil theThere are no writers or readers. ifRead-write locks have no writers, thenThe reader can obtain the read/write lock immediately, otherwise,The reader has to spin thereUntil theThe writer releases the read-write lock.
  • 3.Only one thread at a timecanPossessive writing modetheRead-write lockBut there can beMultiple threads simultaneously hold read/write locks in read mode. Because of this property, whenA read-write lock is a write-lock stateWhen, in thisBefore the lock is unlockedAll,The thread attempting to lock the lockwillblocked. whenRead-write lockinRead lock statusWhen allTry to read modeFor itThe thread that performs the lockingCan beGain accessBut ifThreads want to be in write modeThis lockTo lock, itMust beuntilAll threads release locks.
  • 4. WhenRead/write locks are locked in read modeIf there areAnother thread attempted to lock in write mode.Read/write locks are usually blockedAnd the subsequentRead mode lock requestSo that theIt can avoid the long-term occupation of read lockAnd thePending write - mode lock requests are blocked for a long time.Read/write locks are suitable for reading data structures much more often than writing themBecauseIt can be shared when locked in read modeIn order toWrite mode means exclusive when locked, soRead/write locks are also called shared-exclusive locks.

Conditions for the lock

Definition: a conditional variable that goes to sleep when some of the process’s resource requirements are not met. When the resource is allocated, the conditional lock is opened and the process continues.

Lock the summary

There are two kinds of locks: recursive locks and spin locks.

The lock inquiry

OSSpinLock

OSSpinLock is a spinlock. OSSpinLock is a spinlock.It's deprecated in iOS10.OSSpinLock is no longer usedBecause it isIn some cases, it's already unsafeThis can refer to YY godOSSpinLock is no longer secure. We searched OSSpinLock on our website, and we willCable to os_unfair_lock_lockClick in, and at the end there are these words:

Os_unfair_lock_lock (Mutex)

Through the above official website can knowos_unfair_lock_lockIt’s an Apple officialAn alternative to OSSpinLock is recommended. But it’s inThis parameter can be invoked only for iOS10.0 or later.Os_unfair_lock is a mutex lock, itNot as busy as a spin lock, butThe waiting thread will sleep.

typedef struct os_unfair_lock_s {
	uint32_t _os_unfair_lock_opaque;
} os_unfair_lock, *os_unfair_lock_t;
void os_unfair_lock_lock(os_unfair_lock_t lock);
void os_unfair_lock_unlock(os_unfair_lock_t lock);
Copy the code

Dispatch_semaphone semaphore

Semaphore Introduction

  • A semaphoreIs based onA multithreaded synchronization mechanism for counters, which is used toManaging resourcestheConcurrent access.
  • The signal isIs an identifier that can be used to control the amount of access to a resource, set a semaphore atBefore thread access.Plus semaphore, thenTellable systemAccording to ourSpecifies the number of semaphorestoExecute multiple threads.

Dispatch_semaphone related functions

  • 1.dispatch_semaphore_t dispatch_semaphore_create(long value); Description:Create semaphoreAnd parameters:Initial semaphore valueIf theLess than zerowillReturns NULL.
  • 2.long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); Description: 1.Wait to lower the semaphore, receives a signal and time value (mostly DISPATCH_TIME_FOREVER). 2. If the signalThe semaphore is zeroAnd it willBlocking the current threadUntil theThe semaphore is greater than zeroorThe entered time value. If a 3.The semaphore is greater than zero, you will makeThe semaphore is reduced by 1And return,The program continues to execute.
  • 3.long dispatch_semaphore_signal(dispatch_semaphore_t dsema); Description:Boost semaphoreTo makeAdd one to the semaphoreAnd return

The code executed between dispatch_semaphore_WAIT and dispatch_semaphore_signal only allows a limited number of threads to enter at a time, effectively ensuring that only a limited number of threads can enter in a multi-threaded environment.

It can be used to deal with the problem of data error caused by multi-threading when multiple threads access common resources.

The semaphore low-level implementation was covered in the previous article and will not be covered here. Portal:Multithreading (4) GCD part II

NSLock

NSLock:Is the Foundation frameworkIn order toObjects formA lock that is exposed to developers, which the Foundation framework also providesNSConditionLock.NSRecursiveLock.NSCondition). NSLock is defined as follows:Description:

  • 1.Both tryLock and lock methods request a lockAnd the onlyThe difference is that trylock can continue to do some tasks and processing without acquiring the lock.
  • 2.The lockBeforeDate method acquires the lock before the limit point in timeReturn NO if you don’t get it, return YES if you get it.

The underlying implementation

We know from the breakpoint,The underlying implementation of NSLock is in the Foundation framework, not open source. But you can get throughSwift's open source framework FounfationLet’s examine the underlying implementation of NSLock, which works in much the same way as OCThrough the above source code can be seen, the bottom isThis is implemented through the pthread_mutex mutex, in theThe lock is initialized in init.

Use the extension

Look at the following code, what is the problem with running it

We can see that there are many current values = 10 and 9 before locking, which leads to data confusion. The main reason is caused by multi-threading.

We lock it as follows:

We know from the figure above that the result is only 10, because the process is waiting, and this is mainly because the nested recursive lock is locked inside the block, and the block is not unlocked, so the block is dormant, because the lock is released after the block is called, and the for loop is always going on, The result is that the lock is kept on being added, and then the lock is removed, but the lock is always added more than the lock is removed, and the lock is not completely unlocked until the last time, and 10 is printed once.

The above problem can be solved in the following way

  • 1. Move the lock position

  • 2. Using the @ synchronized

  • 3. Recursive locks: NSRecursiveLock

application

NSLock inAFNetworkingtheAFURLSessionManagerIn the,AFHTTPResponseSrializerThe applications are as follows:

pthread_mutex

Pthread_mutexes profile

Pthread_mutex: is a mutex lock. When the lock is occupied, other threads do not wait for the lock, but block the thread and sleep.

use

#import <pthread.h> // declare muthread_mutex_t _lock globally; // Initialize the mutex pthread_mutex_init(&_lock, NULL); / / lock pthread_mutex_lock (& _lock); Pthread_mutex_unlock (&_lock); Pthread_mutex_destroy (&_lock);Copy the code

Above NSLock we see that the underlying lock uses pthread_mutex

The underlying implementation is not currently found, should be not open source. If you find the underlying implementation later, add it.

application

Pthread_mutex is in YYCacheYYMemoryCacheHas been applied in

NSRecursiveLock recursive locking

This part is implemented in Foundation, the source code is not public, but take a look through Swift Foundation open sourceWe see NSRecursiveLock at the bottom as wellPthread_mutexes encapsulation.

If we compare NSLock to NSRecursiveLock, the underlying implementation is almost identical, except that when init, NSRecursiveLock has a flag PTHREAD_MUTEX_RECURSIVE, while NSLock is the default.

Recursive locking is primarily used to solve a form of nesting in which circular nesting is predominant.

application

NSRecursiveLockIn YYKitYYWebImageOperation.mIn thedeallocMethod useful :(delete irrelevant code error)

@synchronized

Open assembly debugging, run the codeLook at @synchronized in action

We see @synchronized using the objc_sync_enter and objc_sync_exit methods underneath.

We’re throughclang, view the C++ low-level implementationBased on theObjc_sync_enter method symbol breakpoint, look at the source code base where the underlying layer is located, found by breakpointsIn objc sourceIn the

Objc_sync_enter analysis

Let’s open objc source and search for objc_sync_Enter

  • 1. IfObj is,Through the ID2data methodGet the correspondingsyncDataforthreadCount,lockCountforincreasingOperation.
  • 2. IfObj does not existThe callobjc_sync_niL,Symbol breakpoint learningThere is nothing done in this method,Direct return.

Objc_sync_exit analysis

Search for objc_sync_exit in the objC source code

  • 1. IfObj isThe callid2dataMethod to obtain the correspondingSyncDataforthreadCount,lockCountfordiminishingOperation.
  • 2. If obj is nil and do nothing,Direct return.

Through the comparison of the above two implementation logics, it is found that they have one thing in common. When OBJ exists, they will obtain SyncData through id2data method. Let’s take a look at SyncData next

SyncData

SyncData is a structure used to represent a thread data, similar to a linked list structure, pointing to next, and encapsulating a recursive_mutex_t attribute, which confirms that @synchronized is indeed a recursive mutex.

Id2data analysis

SyncData is obtained from ID2Data, and this method is used for both locking and unlocking. Let’s look at id2data source code:

  • 1. In the firsttlsnamelyThe thread cacheLook for
    • intls_get_directMethods in order toThe thread for the keyThrough theKVCThe way to get with itThe binding of SyncData, i.e.,Thread data. One of theThe TLS (Said),Local local thread cache.
    • judgeWhether the obtained data existsAnd judgmentData whether it canfindThe corresponding object.
    • If you find them all, intls_get_directMethod is obtained by KVClockCountIs used to record objectsGot locked up a few times(that is, the lockNumber of nested).
    • If the datathreadCountLess than or equal to 0, orLockCount is less than or equal to 0The directcollapse.
    • Through incomingwhyAnd judgment isOperation type
      • If it isACQUIRESaid,lock, dolockCount++And saved to TLS cache.
      • If it isRELEASESaid,The release of, dolockCount--And saved to TLS cache. ifLockCount equal to zero, fromRemoves thread data from TLS.
      • If it isCHECK, do nothing.
  • 2. IftlsIf not, inCache Search in the cache.
    • throughfetch_cachemethodsFind cache cacheWhether there are threads in
    • If yes, thenTraverses the total cache table.readCorresponding to the outgoing threadSyncCacheItem
    • fromSyncCacheItemRemove thedataAnd thenThe subsequent steps are consistent with TLSthe
  • 3. IfThere is no cache either, i.e.,First time in,Create SyncDataAnd,storageTo the correspondingThe cache
    • If thecacheFind the thread in, andWith the object is equal to,For the assignment, as well asthreadCount++
    • If thecacheIs not found, thenThreadCount is equal to 1

Therefore, the ID2data method is mainly divided into three situations:

  • 1. First time in, no lock
    • threadCount = 1
    • lockCount = 1
    • Stored in thetls
  • 2. Not for the first time, but for the same thread
    • The TLSThere are data,lockCount++
    • Stored in thetls
  • 3. It is not the first time to enter, and it is a different thread
    • Global thread spaceFind thread
    • threadCount++
    • lockCount++
    • Stored in thecache

TLS and cache table structure analysis

For TLS and cache, the underlying table structure is as follows:Let’s explain the above picture:

  • 1.Hash tableThrough the structureSyncListStructure toAssembly multithreadingIn the case
  • 2.SyncDatathroughThe listIn the form ofThe assemblyTo recordCurrent reentrant status
  • 3. Lower layer passesTLS Thread cache.Cache cacheTo process
  • 3. There are two main things at the bottom:lockCount,threadCountTo solveRecursive mutexTo solveNesting is reentrant

Use @synchronized to notice problems

  • 1. Look at the following code

Run we found, crashed

The main reason for the crash is that mArray becomes nil at a certain moment. As we know from the underlying process of @synchronized, if the locked object becomes nil, it cannot be locked, which is equivalent to the following situation: retain and release inside the block continuously, the last one will not be released at a certain moment. The next one is ready to release, which will result in wild Pointers.

Now let’s verify the above code and do the following operations to see if there are zombie objectsRun the code again and the result is as followsWe found that it didExcessive releaseAppear,Wild pointer. So we generally use@synchronized (self)Mainly becauseThe owner of mArray is self.

  • Overrelease: objectNo longer existsTheta, and release thetaMany times the release
  • Wild pointer:Pointer toThe object ofIt's been recycledThis pointer is called a wild pointer.

conclusion

  • 1.@synchronizedAt the bottom of the package is a handfulRecursive lockingSo the lock isRecursive mutex
  • 2.@synchronizedthereentrant, i.e.,Can be nestedMainly due tolockCountthreadCountThe collocation of
  • 3.@synchronizeduseThe listThe reason is thatLinked lists facilitate insertion of the next data
  • 4. But becauseThe underlying linked list query,Cache lookupAs well asrecursive, it isVery memory intensive and very performance intensive,The performance is low, so in the previous article, this lock ranks last
  • 5. But for nowThe usage frequency of the lockIs still veryhighMainly becauseConvenient and simpleAnd,Don't need to unlock
  • 6. Cannot be usedThe object of OCAs aLock the objectBecause the object parameter is id
  • 7.@synchronized (self)This applies toLess nestingThe scene. The object that’s locked hereIt's not always selfNote here
  • 8. IfThe lock is nested more times, that is, locking self too much will causeThe low-level lookups are time-consumingBecause itsAt the bottom are linked lists for lookup, soPoor performanceCan be used at this timeNSLock, semaphoreEtc.

application

At present@synchronizedThe applications in actual projects are respectivelySDWebImageIn theUIView+WebCacheOperationtheThe download methodandAFNetworkingIn theisNetworkActivityOccurringProperties of theGetter method(This is part of it)

NSCondition

NSCondition definition: is a conditional lock, which is rarely used in daily development. Similar to semaphore, thread 1 needs to meet the condition to proceed, otherwise it will block waiting until the condition is met. The classic model is the production-consumer model.

The NSCondition object actually acts as a lock and a thread inspector.

  • 1.The lockMainly in order to beTest conditionswhenSecure data source.Perform tasks that are triggered by conditions
  • 2.Thread checkermainlyDecides whether to continue running the thread based on the condition, i.e.,Whether the thread is blocked

usage

NSCondition *condition = [[NSCondition alloc] init] NSCondition *condition = [NSCondition alloc] init] [condition lock]; [condition lock]; // Use condition unlock with lock; // make the current thread wait [condition wait]; // the CPU sends a signal to the thread to stop waiting and continue executing [condition signal];Copy the code

Analysis of the underlying

View the underlying implementation of NSCondition through Swift’s Foundation source code

The underlying layer is also the encapsulation of pthread_mutex by the underlying layer

  • 1.NSConditionIs themutexandcondAn encapsulation of (condIs used forAccess and operationOf a specific type of dataPointer to the)
  • 2.waitoperationBlocking threadsAnd make it enterA dormant state.Until the timeout
  • 3.signalOperation isWake up theaDormant waitingThe thread
  • 4.broadcastwillWake up all waiting threads

NSConditionLock

NSConditionLock definition: conditional lock. Once one thread has acquired the lock, the other threads must wait. The essence of this is NSCondition + Lock.

NSCondition is more complicated to use than NSConditionLock, so it is recommended to use NSConditionLock.

Method of use

Its use is as follows

NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2]; ConditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock conditionLock ConditionLock lock [conditionLock]; // Indicates that if no other thread has acquired the lock, but the condition inside the lock is not equal to condition A, it still cannot acquire the lock and is still waiting. If the internal condition is equal to the condition A, and no other thread acquires the lock, the code area is entered, while it is set to acquire the lock, and any other thread will wait for its code to complete until it unlocks. ConditionLock lockWhenCondition:A condition; / / lock is released, at the same time, the internal condition is set to A condition [conditionLock unlockWithCondition: A condition]. // indicates that if the thread is locked (the lock is not acquired) and the time is exceeded, the thread is no longer blocked. Return = [conditionLock whencondition :A conditional beforeDate:A time]; conditionLock whencondition :A conditional beforeDate:A time];Copy the code

The underlying implementation

Through the underlying source code can know

  • 1.NSConditionLockisNSConditionThe encapsulation
  • 2.NSConditionLockcanSet lock conditions, condition value, andNSConditionjustSignal notification

Code validation

Verify NSConditionLock

Where self.myLock is NSCondition, we are inconditionLockPart of the breakpoint, run (need inA:Run on the simulatorIntel's instructionThe real machine is runningArm instruction) Run to breakpoint, we openassemblyMode, as shown below:

Where x0 is the receiver self and x1 is CMD

So let’s go throughregister readRead register contentsWe are inobjc_msgSendPlace a breakpoint at the point where the code runs and is read againRegister x0(register read x0)

ConditionLock lockWhenCondition:2

X1 (register read x1), and find that it cannot read, because x1 stores sel, not object type, can read SEL by strong cast

Let’s add a sign breakpoint-[NSConditionLock lockWhenCondition:],-[NSConditionLock lockWhenCondition:beforeDate:]And then check for bl, B, etc

  • Read registers X0, X2 are currentlockWhenCondition:beforeDate:The actual path is[conditionLock lockWhenCondition:1];

You can see from the assembly,X2 goes to x21Here, we debug for two main purposes:NSCondition + lockAs well asThe condition and the valueThe value of the match

NSCondition + Lock authentication

Continue execution and stop at BL

Read register X0, then jump to NSCondition

Read x1, Po (SEL) 0x00000001C746e484

So you can verify that NSConditionLock calls the lock method of NSCondition at the bottom.

Condition matches the value of value

Go ahead and skip to LDRYou can see from the figure above that the compiler gets it through a methodCondition2 property valueAnd stored in thex8In the

  • register read x19
  • Po (SEL) 0x0000000282CA5790 -- x19 address +0x10

Register read x8, where x8 stores 2.

CMP x8, x21, which means x8 matches x21, which means 2 matches 1, but it doesn’t match

ConditionLock (condition :2) conditionLock (condition :2) conditionLock (condition :2) conditionLock (condition :2) conditionLock (condition :2) conditionLock (condition :2) conditionLock (condition :2)

conclusion

Using the diagram above and the previous explanation, we conclude NSConditionLock

  • 1. During initialization, we are onNSConditionLockSet upMeet the conditionsforCondition is equal to 2
  • 2. Thread 1 (print task)[conditionLock lockWhenCondition:1]2. What it means:ConditionLock conditionIs that when theCondition is equal to 1, which we set above during initializationConditions for 2, soDoes not meet theAnd at this pointEnter a waiting state.Before entering the waiting statewillRelease the lock
  • 3. Thread 2 (Print task)[conditionLock lockWhenCondition:2]At this timeThe condition is that, soConditionLock conditionLockAnd thenExecute the print thread 2 task, the execution is completeunlockThat will beCondition is equal to 1And thenSend the Boradcast signal
  • 4. Thread 1 doesReceived boradcast signalOnce again,Check whether the conditions are metAt this time,meet.lock.Executing a print task, the printing task is completeunlock
  • 5.Thread 3 has no condition, soThe first to perform

As explained above, the printed result is3 - > 2 - > 1 Consistent with analysis results

Other lock

Pthread_rwlock read-write lock

Read/write locks are described in more detail above, but only the usage is added here

usage

// read lock pthread_rwlock_rdlock(&rwlock); / / unlock pthread_rwlock_unlock (& rwlock); // write lock pthread_rwlock_wrlock(&rwlock); / / unlock pthread_rwlock_unlock (& rwlock);Copy the code

pthread_mutex(recursive)

The pthread_mutex lock also supports recursion; simply set PTHREAD_MUTEX_RECURSIVE

usage

pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
Copy the code

Atomic lock

Atomic applies to the modifier of an ATTRIBUTE in OC. It comes with a spin lock, but this is rarely used. Instead, nonatomic is used

Low-level simple exploration

We know thatA setter methodDepending on theThe modifiercallDifferent methods for, includingThe lastwillCall reallySetProperty uniformlyMethods, among themAtomic and non-atomic operations

Os_unfair_lock (spinlock_T) is used to lock atomic attributes

Getter methods treat atomic in much the same way as setters do

conclusion

The above analysis of various locks, let’s make a summary of the above content:

  • 1.OSSpinLockAbandoned, AppleRun os_UNfair_lock instead
  • 2.NSLock,NSRecursiveLockThe bottom isEncapsulation of pthread_mutex
  • 3.NSConditionandNSConditionLockisConditions for the lockBoth at the bottomEncapsulation of pthread_mutexwhenSatisfies one of these conditionsIn order tooperateandThe semaphore dispatch_semaphore is similar
  • 4.@synchronizedinThe number of nestingWhen,The performance is low, mainly because nesting causes itThe underlying query is in the linked list.Cache lookup recursion.Memory consumption.Waste a lot of timeCause, but because ofSimple to useIn theLess nesting of scenariosIn theHigh frequency of use

Wrote last

Write more content, because my ability is limited, some places may explain the problem, please be able to point out, at the same time to lock questions, welcome everyone message. I hope that we can communicate with each other, explore and make progress together!