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

  • ifobcExecute if it is emptyobjc_sync_nilOn further inspection, nothing was executed
  • obcGets objects that are not nullSyncData* datathedata->mutextlock

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,objThe correspondingSyncDataJump todoneperform
  • If you don’t find itobjThe correspondingSyncDataBut if an empty node is found, thenobjAssign to an empty node
  • If none is found, create an insertSyncListThe head of the

id2dataSimple 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 key
  • tls_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

  • SyncCacheItemStructure contains
    • SyncDataPointer to the
    • Number of current thread accesseslockCount
  • SyncCacheStructure contains
    • storageSyncCacheItemAn array oflist
    • The length of the arrayallocated
    • Number of elements currently storedused

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 availablepersonThe address is A0 and locked
  • T2 is availablepersonThe IP address is also A0, waiting to be unlocked
  • After T1personThe address becomes A1, A0 is released and unlocked, and T2 is accessible
  • So t2 is donepersonThe 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 me1andendWho 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 me1andendWho 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 me1andendWho 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