This is the 22nd day of my participation in the August More Text Challenge

Lock performance analysis

In order to test the performance of locks, all locks are unlocked for 100,000 times, and the time consumed is as follows:

* The test device was iPhone 12

The lock performance, in descending order, is:

  • OSSpinLock(deprecated)
  • os_unfair_lock
  • pthread_mutex_t
  • dispatch_semaphore_t
  • NSCondition
  • NSlock
  • NSRecursiveLock
  • pthread_mutex_t
  • synchronized
  • NSConditionLock

Test data of different devices, real machines and simulators may differ;

Synchronized is used a lot in iOS development, so let’s take a look at how synchronized works;

Principle analysis of synchronized

Analysis of synchronized implementation

Before examining the principles of synchronized, let’s take a look at some code that simulates a ticketing system:

- (void)viewDidLoad { [super viewDidLoad]; self.ticketCount = 20; [self startSaleTicket]; } - (void)startSaleTicket{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; }}); dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; }}); dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i < 3; i++) { [self saleTicket]; }}); dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i < 10; i++) { [self saleTicket]; }}); } - (void)saleTicket{ if (self.ticketCount > 0) { self.ticketCount--; Sleep (0.1); NSLog(@" remaining tickets: %lu ",(unsigned long)self.ticketCount); }else{NSLog(@" current tickets sold out "); }}Copy the code

Let’s run this test code and see what it prints:

By printing the results, we found that the number of remaining tickets was in disorder, which would cause the problem of data insecurity. So how to solve this problem? We can lock the object to ensure that only one thread is accessing the object at a time, so that the data security can be ensured, so that the number of remaining votes can be correctly displayed;

Use @synchronized to lock the ticketing operation.

As you can see, the votes are displayed correctly;

@synchronized has the following effects:

  • Locking effect;
  • Recursion reentrant; (can be in@synchronizedI’m going to add more@synchronized, lock on lock, recursive lock)

As you may have noticed, we pass @synchronized an argument, self, so what does that argument do? Can we pass nil? What did @synchronized do? What kind of structure is it?

So where do we start as an entry point? According to our previous experience, it can be analyzed by assembly or CLang.

Let’s take a look at the CPP file using clang. To avoid unnecessary code, use @synchronized in the main.m file:

Next, we generate a CPP file for main.m; Find the main function, and compare with the original method of main, we can analyze the core content of @synchronized as the code in the red box;

After formatting the code, it looks like this:

It is clear that the catch and the code that follows it are not our concern, so the only code we need to focus on is:

Then the _SYNC_EXIT structure is a structure that can be led out; _synC_obj is appDelegateClassName and that leaves the code:

objc_sync_enter(_sync_obj);
_sync_exit(_sync_obj);
Copy the code

Since _sync_exit will eventually call the destructor of the _sync_exit structure ~ _sync_exit(), that is, objc_sync_exit(sync_exit) will be called; ; So ultimately @synchronized is the equivalent of these two lines of code:

objc_sync_enter(_sync_obj);
objc_sync_exit(_sync_obj);
Copy the code

You can also locate objc_sync_Enter by breaking the point at @synchronized and then viewing the assembly;

Next, we add the symbolic breakpoint objc_sync_Enter to the project and run the project:

Find that objc_sync_Enter comes from libobjc.a.dylib, the source code for objc; Go to download (here we analyze the source code with version 818)

Knowing the source of objc_sync_enter method, we can use this method as an entrance to analyze @synchronized. In objC source code, we locate objc_sync_Enter implementation as follows:

In the implementation of the objc_sync_enter method, we see that if obj does not exist, the else branch is executed, and the objc_sync_nil() method is executed, which is essentially the @synchronized(nil) argument passed as nil;

Synchronized (nil) does nothing; synchronized(nil) does nothing;

So what is the implementation of the objc_sync_nil() method? Click Discover to jump to here:

So what is BREAKPOINT_FUNCTION? We searched and found that it was an encapsulation of a macro definition:

#   define BREAKPOINT_FUNCTION(prototype)                             \
    OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \
    prototype { asm(""); }
Copy the code

From the macro definition, we know that the final code:

BREAKPOINT_FUNCTION(
    void objc_sync_nil(void));Copy the code

In essence:

void objc_sync_nil(a) {}Copy the code

That is, objc_sync_nil() does nothing;

This solves our first question – can arguments be passed nil?

@synchronized can pass nil, but passing nil will do nothing;

In addition to the objc_sync_enter method, we know that synchronized has an objc_sync_exit method, which we locate in the source code:

  • objc_sync_enterIn theobjGenerates one if it existsSyncDataType of data:SyncData* data = id2data(obj, ACQUIRE);And calleddata->mutex.lock();Method to lock;
  • objc_sync_enterIn theobjIt also generates one when it existsSyncDataType of data:SyncData* data = id2data(obj, RELEASE);If this datadataIf it exists, it is calleddata->mutex.tryUnlock();Method to unlock;

By comparing objc_sync_enter and objc_sync_exit, we can see that the two methods are very similar, and that both methods use id2data to get SyncData. One lock and the other unlock. The core content of synchronized is in ID2data;

So what does the ID2data method do? What kind of data structure is SyncData? Please listen to the next decomposition, to be continued……