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. A
Read-write lock
At the same timeonly
There areOne writer or multiple readers
(It is related to the number of cpus
), butYou can't have both readers and writers
In theRead/write lock hold duration
Is alsoGrab the failure
. - 2. If
Read/write locks currently have no readers
Also,No writer
, thenwriter
canGet read/write locks immediately
.Otherwise it has to spin there
Until 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 there
Until theThe writer releases the read-write lock
. - 3.
Only one thread at a time
canPossessive writing mode
theRead-write lock
But there can beMultiple threads simultaneously hold read/write locks in read mode
. Because of this property, whenA read-write lock is a write-lock state
When, in thisBefore the lock is unlocked
All,The thread attempting to lock the lock
willblocked
. whenRead-write lock
inRead lock status
When allTry to read mode
For itThe thread that performs the locking
Can beGain access
But ifThreads want to be in write mode
This lockTo lock
, itMust be
untilAll threads release locks
. - 4. When
Read/write locks are locked in read mode
If there areAnother thread attempted to lock in write mode
.Read/write locks are usually blocked
And the subsequentRead mode lock request
So that theIt can avoid the long-term occupation of read lock
And 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 them
BecauseIt can be shared when locked in read mode
In 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 used
Because it isIn some cases, it's already unsafe
This can refer to YY godOSSpinLock is no longer secure. We searched OSSpinLock on our website, and we willCable to os_unfair_lock_lock
Click in, and at the end there are these words:
Os_unfair_lock_lock (Mutex)
Through the above official website can knowos_unfair_lock_lock
It’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 semaphore
Is based onA multithreaded synchronization mechanism for counters
, which is used toManaging resources
theConcurrent access
.- The signal is
Is an identifier that can be used to control the amount of access to a resource
, set a semaphore atBefore thread access
.Plus semaphore
, thenTellable system
According to ourSpecifies the number of semaphores
toExecute multiple threads
.
Dispatch_semaphone related functions
- 1.dispatch_semaphore_t dispatch_semaphore_create(long value); Description:
Create semaphore
And parameters:Initial semaphore value
If theLess than zero
willReturns 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 zero
And it willBlocking the current thread
Until theThe semaphore is greater than zero
orThe entered time value
. If a 3.The semaphore is greater than zero
, you will makeThe semaphore is reduced by 1
And return,The program continues to execute
. - 3.long dispatch_semaphore_signal(dispatch_semaphore_t dsema); Description:
Boost semaphore
To makeAdd one to the semaphore
And 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 framework
In order toObjects form
A 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 lock
And 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 time
Return 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 Founfation
Let’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 inAFNetworking
theAFURLSessionManager
In the,AFHTTPResponseSrializer
The 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
NSRecursiveLock
In YYKitYYWebImageOperation.m
In thedealloc
Method 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 source
In the
Objc_sync_enter analysis
Let’s open objc source and search for objc_sync_Enter
- 1. If
Obj is
,Through the ID2data method
Get the correspondingsyncData
forthreadCount
,lockCount
forincreasing
Operation. - 2. If
Obj does not exist
The callobjc_sync_ni
L,Symbol breakpoint learning
There is nothing done in this method,Direct return
.
Objc_sync_exit analysis
Search for objc_sync_exit in the objC source code
- 1. If
Obj is
The callid2data
Method to obtain the correspondingSyncData
forthreadCount
,lockCount
fordiminishing
Operation. - 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 first
tls
namelyThe thread cache
Look for- in
tls_get_direct
Methods in order toThe thread for the key
Through theKVC
The way to get with itThe binding of SyncData
, i.e.,Thread data
. One of theThe TLS (
Said),Local local thread cache
. - judge
Whether the obtained data exists
And judgmentData whether it can
findThe corresponding object
. - If you find them all, in
tls_get_direct
Method is obtained by KVClockCount
Is used to record objectsGot locked up a few times
(that is, the lockNumber of nested
). - If the data
threadCount
Less than or equal to 0, orLockCount is less than or equal to 0
The directcollapse
. - Through incoming
why
And judgment isOperation type
- If it is
ACQUIRE
Said,lock
, dolockCount++
And saved to TLS cache. - If it is
RELEASE
Said,The release of
, dolockCount--
And saved to TLS cache. ifLockCount equal to zero
, fromRemoves thread data from TLS
. - If it is
CHECK
, do nothing.
- If it is
- in
- 2. If
tls
If not, inCache Search in the cache
.- through
fetch_cache
methodsFind cache cache
Whether there are threads in - If yes, then
Traverses the total cache table
.read
Corresponding to the outgoing threadSyncCacheItem
- from
SyncCacheItem
Remove thedata
And thenThe subsequent steps are consistent with TLS
the
- through
- 3. If
There is no cache either
, i.e.,First time in
,Create SyncData
And,storage
To the correspondingThe cache
- If the
cache
Find the thread in, andWith the object is equal to
,For the assignment
, as well asthreadCount++
- If the
cache
Is not found, thenThreadCount is equal to 1
- If the
Therefore, the ID2data method is mainly divided into three situations:
- 1. First time in, no lock
threadCount = 1
lockCount = 1
- Stored in the
tls
- 2. Not for the first time, but for the same thread
- The TLS
There are data
,lockCount++
- Stored in the
tls
- The TLS
- 3. It is not the first time to enter, and it is a different thread
Global thread space
Find threadthreadCount++
lockCount++
- Stored in the
cache
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 table
Through the structureSyncList
Structure toAssembly multithreading
In the case - 2.
SyncData
throughThe list
In the form ofThe assembly
To recordCurrent reentrant status
- 3. Lower layer passes
TLS Thread cache
.Cache cache
To process - 3. There are two main things at the bottom:
lockCount
,threadCount
To solveRecursive mutex
To 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 release
Appear,Wild pointer
. So we generally use@synchronized (self)
Mainly becauseThe owner of mArray is self
.
- Overrelease: object
No longer exists
Theta, and release thetaMany times the release
- Wild pointer:
Pointer to
The object ofIt's been recycled
This pointer is called a wild pointer.
conclusion
- 1.
@synchronized
At the bottom of the package is a handfulRecursive locking
So the lock isRecursive mutex
- 2.
@synchronized
thereentrant
, i.e.,Can be nested
Mainly due tolockCount
和threadCount
The collocation of - 3.
@synchronized
useThe list
The reason is thatLinked lists facilitate insertion of the next data
- 4. But because
The underlying linked list query
,Cache lookup
As 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 now
The usage frequency of the lock
Is still veryhigh
Mainly becauseConvenient and simple
And,Don't need to unlock
- 6. Cannot be used
The object of OC
As aLock the object
Because the object parameter is id - 7.
@synchronized (self)
This applies toLess nesting
The scene. The object that’s locked hereIt's not always self
Note here - 8. If
The lock is nested more times
, that is, locking self too much will causeThe low-level lookups are time-consuming
Because itsAt the bottom are linked lists for lookup
, soPoor performance
Can be used at this timeNSLock, semaphore
Etc.
application
At present@synchronized
The applications in actual projects are respectivelySDWebImage
In theUIView+WebCacheOperation
theThe download method
andAFNetworking
In theisNetworkActivityOccurring
Properties 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 lock
Mainly in order to beTest conditions
whenSecure data source
.Perform tasks that are triggered by conditions
- 2.
Thread checker
mainlyDecides 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.
NSCondition
Is themutex
andcond
An encapsulation of (cond
Is used forAccess and operation
Of a specific type of dataPointer to the
) - 2.
wait
operationBlocking threads
And make it enterA dormant state
.Until the timeout
- 3.
signal
Operation isWake up the
aDormant waiting
The thread - 4.
broadcast
willWake 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.
NSConditionLock
isNSCondition
The encapsulation - 2.
NSConditionLock
canSet lock conditions
, condition value, andNSCondition
justSignal notification
Code validation
Verify NSConditionLock
Where self.myLock is NSCondition, we are inconditionLock
Part of the breakpoint, run (need inA:
Run on the simulatorIntel's instruction
The real machine is runningArm instruction
) Run to breakpoint, we openassembly
Mode, as shown below:
Where x0 is the receiver self and x1 is CMD
So let’s go throughregister read
Read register contentsWe are inobjc_msgSend
Place 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 current
lockWhenCondition:beforeDate:
The actual path is[conditionLock lockWhenCondition:1]
;
You can see from the assembly,X2 goes to x21
Here, we debug for two main purposes:NSCondition + lock
As well asThe condition and the value
The 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 value
And stored in thex8
In 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 on
NSConditionLock
Set upMeet the conditions
forCondition is equal to 2
- 2. Thread 1 (print task)
[conditionLock lockWhenCondition:1]
2. What it means:ConditionLock condition
Is that when theCondition is equal to 1
, which we set above during initializationConditions for 2
, soDoes not meet the
And at this pointEnter a waiting state
.Before entering the waiting state
willRelease the lock
- 3. Thread 2 (Print task)
[conditionLock lockWhenCondition:2]
At this timeThe condition is that
, soConditionLock conditionLock
And thenExecute the print thread 2 task
, the execution is completeunlock
That will beCondition is equal to 1
And thenSend the Boradcast signal
- 4. Thread 1 does
Received boradcast signal
Once again,Check whether the conditions are met
At 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 method
Depending on theThe modifier
callDifferent methods for
, includingThe last
willCall reallySetProperty uniformly
Methods, 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.
OSSpinLock
Abandoned, AppleRun os_UNfair_lock instead
- 2.
NSLock
,NSRecursiveLock
The bottom isEncapsulation of pthread_mutex
- 3.
NSCondition
andNSConditionLock
isConditions for the lock
Both at the bottomEncapsulation of pthread_mutex
whenSatisfies one of these conditions
In order tooperate
andThe semaphore dispatch_semaphore is similar
- 4.
@synchronized
inThe number of nesting
When,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 time
Cause, but because ofSimple to use
In theLess nesting of scenarios
In 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!