Synchronized () {synchronized(); Is a synchronization lock provided in OBJC that supports recursion. But it was removed in Swift and can be replaced with objc_sync.

Read this article to learn how synchronized works

Let’s focus on the following questions today

  1. What happens if synchronized’s OBJ is nil
  2. Does synchronized affect OBJ
  3. Synchronized vs. pthread_mutex and objc_sync

Let’s verify nesting

Create a Person class with a Run method

- (void)run { @synchronized (self) { NSLog(@"s1"); @synchronized (self) { NSLog(@"s2"); }}}Copy the code

After execution, it is found that all prints are normal

Swift and OC are respectively implemented

///objc
@synchronized(self) {
    //action
}

///swift
objc_sync_enter(self)
//action
objc_sync_exit(self)
Copy the code

Generate runtime code to view the underlying implementation

Change the Person class to the following

- (void)run { @synchronized (self) { NSLog(@"s1"); }}Copy the code

Then turn Person into C++ code clang-x objective-c-rewrite-objc Person.m

Open the person. CPP file to find the following c++ code

static void _I_Person_run(Person * self, SEL _cmd) { { id _rethrow = 0; id _sync_obj = (id)self; objc_sync_enter(_sync_obj); try { struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {} ~_SYNC_EXIT() {objc_sync_exit(sync_exit); } id sync_exit; } _sync_exit(_sync_obj); NSLog((NSString *)&__NSConstantStringImpl__var_folders_1m_kzn6dnx501b1x94s5bwpxjrw0000gn_T_Person_e7a30b_mi_0); { id _rethrow = 0; id _sync_obj = (id)self; objc_sync_enter(_sync_obj); try { struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {} ~_SYNC_EXIT() {objc_sync_exit(sync_exit); } id sync_exit; } _sync_exit(_sync_obj); NSLog((NSString *)&__NSConstantStringImpl__var_folders_1m_kzn6dnx501b1x94s5bwpxjrw0000gn_T_Person_e7a30b_mi_1); } catch (id e) {_rethrow = e; } { struct _FIN { _FIN(id reth) : rethrow(reth) {} ~_FIN() { if (rethrow) objc_exception_throw(rethrow); } id rethrow; } _fin_force_rethow(_rethrow); } } } catch (id e) {_rethrow = e; } { struct _FIN { _FIN(id reth) : rethrow(reth) {} ~_FIN() { if (rethrow) objc_exception_throw(rethrow); } id rethrow; } _fin_force_rethow(_rethrow); }}}Copy the code

Synchronized calls try catch and internally calls objc_sync_enter and objc_sync_exit. How are these two functions implemented?

objc_sync_enter & objc_sync_exit

We went through the objC4 source code and found an implementation in objc-sync.mm

// Begin synchronizing on 'obj'. // Allocates recursive mutex associated with 'obj' if needed. // Returns OBJC_SYNC_SUCCESS once lock is acquired. int objc_sync_enter(id obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, ACQUIRE); assert(data); data->mutex.lock(); } else { // @synchronized(nil) does nothing if (DebugNilSync) { _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug"); } objc_sync_nil(); } return result; } // End synchronizing on 'obj'. // Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR int objc_sync_exit(id  obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, RELEASE); if (! data) { result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR; } else { bool okay = data->mutex.tryUnlock(); if (! okay) { result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR; } } } else { // @synchronized(nil) does nothing } return result; }Copy the code

If you look closely at the source code and comments for objc_sync_enter, it is clear what this function does: 1. Start synchronization lock on obj 2. Obj is nil, lock will not succeed 3. Obj is not nil, initialize recursive mutex and associate with OBj

Objc_sync_enter lock mode

From the underlying source code we see the lock method is to first get obJ associated synchronization data SyncData, and then lock

What is SyncData synchronizing data?

//objc-sync.mm typedef struct SyncData {// struct SyncData* nextData; // The object of the lock is DisguisedPtr<objc_object> object; // Number of waiting threads int32_t threadCount; // number of THREADS using this block recursive_mutex_t mutex; } SyncData;Copy the code

SyncData is a structure, similar to a linked list

NextData: a pointer to SyncData, pointing to the nextData object: the locked object threadCount: the number of threads to wait mutex: the recursive mutex to use

Recursive mutex recursive_mutex_t implementation

Recursive_mutex_t is an encapsulation based on pthread_mutex_t. Open objc-os.h to find the implementation

//objc-os.h using recursive_mutex_t = recursive_mutex_tt<DEBUG>; class recursive_mutex_tt : nocopy_t { pthread_mutex_t mLock; public: recursive_mutex_tt() : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) { } void lock() { lockdebug_recursive_mutex_lock(this); int err = pthread_mutex_lock(&mLock); if (err) _objc_fatal("pthread_mutex_lock failed (%d)", err); } //...... is omitted here }Copy the code

The principle of synchronized

Internally, each OBJ is assigned a RECURsive_mutex recursive mutex. For each OBJ, the RECURsiVE_MUtex recursive_mutex is used to lock and unlock the object

How are obJ and RECURsive_mutex managed internally

Here is not a deep study, interested can continue to see the source

conclusion

  1. Synchronized obJ is nil? The lock operation is invalid.

  2. Does Synchronized do anything to OBJ? The recursive spin lock is generated for OBj, and the association is established to generate SyncData, which is stored in the current thread’s cache or in the global hash table.

  3. What does synchronized have to do with pthread_mutex? A recursive mutex in SyncData, implemented using pthread_mutex.

  4. What does synchronized have to do with objc_sync? Synchronized calls objc_sync_enter() and objc_sync_exit().

Author: Li Changhong, Large front End R&D Center