IOS multithreading can cause competition for shared resources, and locking is a good way to solve this problem. There are many kinds of iOS locks, and @synchronized seems to have no competition for performance
But why do we use this even, because use simple !!!!
@synchronized (person) {
//do something
}
Copy the code
Source code analysis
Open always Show Disassembly to run the code
Verify that you can see @synchronized and object strong references with objc_retain and objc_release
int main(int argc, char * argv[]) {
JPerson *person = [[JPerson alloc] init];
NSLog(@"--%lu",CFGetRetainCount((__bridge CFTypeRef)(person)));
@synchronized (person) {
NSLog(@"%@",person);
NSLog(@"--%lu",CFGetRetainCount((__bridge CFTypeRef)(person)));
}
NSLog(@"--%lu",CFGetRetainCount((__bridge CFTypeRef)(person)));
return 0;
}
Copy the code
objc_sync_enter
Search for objc_sync_Enter in objC4-781.2
- if
obc
Execute if it is emptyobjc_sync_nil
On further inspection, nothing was executed obc
Gets objects that are not nullSyncData* data
thedata->mutext
lock
The focus here should be on SyncData and ID2Data
SyncData
This is a singly linked list node
- NextData: Pointer to the next node
- Object: the wrapped OBj
- ThreadCount: How many threads access obj
- Mutex: Recursive mutex of type RECURsive_mutext_T
id2data
There’s a lot of code, there’s a lot of focus and it’s easy to get distracted, so let’s just focus on the non-cached case and how does the system handle that
Click in and see
Now the focus is on StripedMap
You can see that the StripedMap is an array structure used to store SyncList
- If we find it,
obj
The correspondingSyncData
Jump todone
perform - If you don’t find it
obj
The correspondingSyncData
But if an empty node is found, thenobj
Assign to an empty node - If none is found, create an insert
SyncList
The head of the
id2data
Simple summary
As shown in figure: There is a global 8 length array StripedMap (this array does not need to be expanded), which stores the SyncList pointer, which stores the pointer to the SyncData node, and the SyncData node stores the pointer to the next SyncData node, which forms a single linked list structure.
To speed up access, Apple has designed two levels of caching, TLS and SyncCache. Let’s take a look
TLS(Thread Local Storage) Local Storage by threads
TLS can be thought of as thread private storage
tls_get_direct
: Reads the value from the TLS dictionary according to the keytls_set_direct
: Stored in TLS dictionary according to key and value
As you can see, if the result is not retrieved from the cache TLS, the code executed after done will also store a copy
SyncCache
The operation is very simple, is to go through the number of matches object
SyncCacheItem
Structure containsSyncData
Pointer to the- Number of current thread accesses
lockCount
SyncCache
Structure contains- storage
SyncCacheItem
An array oflist
- The length of the array
allocated
- Number of elements currently stored
used
- storage
fetch_cache
_objc_pthread_data structure
_objc_fetch_pthread_data
Tls_get and TLs_set create _objC_pthread_data structures that are also stored in TLS
objc_sync_exit
Only one unlock operation was performed, and the SyncData created was not deleted
Pay attention to
We operate on SyncData by passing in the OBJ address as an identifier, which can cause problems if the OBJ address changes, for example
If two threads, t1 and T2, want to access Person, the initial value of person is A0
- T1 is available
person
The address is A0 and locked - T2 is available
person
The IP address is also A0, waiting to be unlocked - After T1
person
The address becomes A1, A0 is released and unlocked, and T2 is accessible - So t2 is done
person
The address changes to A2, and A0 is released again, causing a crash
So we generally pass self to the object of the attribute, which is somewhat thread-safe, but extends the scope
Add two small examples to deepen understanding
1, could you please tell me1
andend
Who output first?
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); dispatch_async(globalQueue, ^{ NSObject *obj = [NSObject new]; @synchronized (obj) {dispatch_async(globalQueue, ^{ @synchronized (obj) { NSLog(@"1--%@",[NSThread currentThread]); }}); sleep(3); NSLog(@"end--%@",[NSThread currentThread]); }});Copy the code
The answer is “end” and “1”, because both threads must lock obj, and the second thread must wait for the first thread to unlock
2, modify: excuse me1
andend
Who output first?
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); dispatch_async(globalQueue, ^{ NSObject *obj = [NSObject new]; @synchronized (obj) {dispatch_sync(globalQueue, ^{sync(obj) {sleep(3); NSLog(@"1--%@",[NSThread currentThread]); }}); NSLog(@"end--%@",[NSThread currentThread]); }});Copy the code
The same thread can lock the same syncData multiple times. The current thread executes task 1 first because it executes synchronously
3, modify: excuse me1
andend
Who output first?
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); dispatch_async(globalQueue, ^{ NSObject *obj = [NSObject new]; @synchronized (obj) {obj1 NSObject *obj1 = [NSObject new]; @synchronized (obj1) { NSLog(@"obj1"); } dispatch_sync(globalQueue, ^{syncdata.synchronized (obj) {sleep(3); NSLog(@"1--%@",[NSThread currentThread]); }}); NSLog(@"end--%@",[NSThread currentThread]); }});Copy the code
Because there is synchronous execution, obj1 must be printed first, and 1 is still printed before end
Refer to the article
Performance aside, talk about why you shouldn’t use @synchronized
Analysis of @synchronized principle of iOS multi-thread lock
ios-@synchronized detailed explanation
OSSpinLock is no longer secure