Synchronized is a synchronization lock provided in OBJC that supports recursion. But it was removed in Swift and can be replaced with objc_sync.

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

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

doubt

Synchronized (synchronized)

1. Synchronized obJ is nil 2. Does synchronized do anything to OBj? 4. What does synchronized have to do with pthread_mutex? 5. What does synchronized have to do with objc_sync?Copy the code

To understand these issues, you also need to understand the underlying implementation of synchronized, so let’s look at specific implementations.

The principle of

First, analyze what synchronized did through assembly, and write a test code first.

//objc
- (void)testSync {
    @synchronized(self) {
    }
}
Copy the code

Xcode’s Assemble “octest.m” is then executed to get the following assembly code.

Lfunc_begin0: //...... is omitted here Ltmp0:.loc 2 15 5 prologue_end ## test/ octest. m:15:5 callq *_objc_retain@GOTPCREL(%rip) // here omitted...... Callq _objc_sync_enter Ltmp1:.loc 2 17 5 ## test/ octest. m:17:5 callq _objc_sync_exit:...... callq *_objc_release@GOTPCREL(%rip)Copy the code

Stripping away some of the extra code, we see that _objc_sync_enter and _objc_sync_exit are essentially still called.

We can also use Clang to compile the current file.

clang -x objective-c -rewrite-objc OCTest.m  
Copy the code

Open the compiled octest. CPP file and find the following c++ code.

static void _I_OCTest_testSync(OCTest * 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); } 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

On closer inspection, synchronized calls try catch and objc_sync_enter and objc_sync_exit, similar to the results of assembly analysis. How are these two functions implemented?

objc_sync_enter

With that in mind, we went through objC4’s source code and found objc_sync_Enter in objc-sync.mm.

// Begin synchronizing on 'obj'. 
// Allocates recursive mutex associated with 'obj' if needed.
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;
}
Copy the code

1. Start synchronization lock on obj. 2. Initialize recursive mutex and associate obJ if necessary. 3. If obj is nil, locking will not succeed.

objc_sync_exit

Also, objc_sync_exit is found in objc-sync.mm.

// End synchronizing on 'obj' 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

SyncData

So let’s see how does objc_sync_Enter lock?

* data = id2data(obj, ACQUIRE); / / lock data - > mutex. The lock ();Copy the code

What is SyncData? Keep looking.

//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; typedef struct { SyncData *data; unsigned int lockCount; // number of times THIS THREAD locked this block } SyncCacheItem; typedef struct SyncCache { unsigned int allocated; unsigned int used; SyncCacheItem list[0]; } SyncCache;Copy the code

SyncData is a structure, similar to a linked list.

  1. NextData: A pointer to SyncData, pointing to the next piece of data
  2. Object: locked object
  3. ThreadCount: Number of waiting threads
  4. Mutex: A mutex recursive lock used

recursive_mutex_t

Recursive_mutex_t is a mutex recursive lock that is also a encapsulation based on pthread_mutex_t. Open objc-os.h and find the following code:

//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

Pthread_mutex_t is a recursive version of pthread_mutex_t.

At this point, the principle of synchronized is clear.

  1. Inside for each oneobjAssign arecursive_mutexRecursive mutex.
  2. For eachobjAnd through thisrecursive_mutexRecursive mutex locks and unlocks.

Let’s take a look internally at how obJ and RECURsiVE_mutex are managed.

id2data

The id2data step manages the mapping relationship between OBJ and SyncData. It is mainly divided into five steps to obtain SyncData according to OBJ.

tls

The first step is to retrieve SyncData from the Thread Local Storage cache of the current Thread. Only one SyncData per Thread is available.

static SyncData* id2data(id object, enum usage why) {
    spinlock_t *lockp = &LOCK_FOR_OBJ(object);
    SyncData 支那listp = &LIST_FOR_OBJ(object);
    SyncData* result = NULL;
    
	//1. Obtain SyncData from Thread Local Storage cache
	#if SUPPORT_DIRECT_THREAD_KEYS
	// Check per-thread single-entry fast cache for matching object
	bool fastCacheOccupied = NO;
	SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
	if (data) {
	    fastCacheOccupied = YES;
	    if (data->object = = object) {
	    	// Get the number of SyncData locks in the TLS cache of the current thread
          lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
           // Determine the current operation type
          switch(why) {
	          / / acquiring a lock
	          case ACQUIRE: {
	            	// Update the TLS cache of the current thread
	            	lockCount++;
	            	tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
	            	break;
	          }
	          / / releases the lock
	          case RELEASE:
	          	// Release the lock once to update the current thread's TLS cache
	            	lockCount--;
	            	tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
	            	if (lockCount = = 0) {
	                	// remove from fast cache
	                	tls_set_direct(SYNC_DATA_DIRECT_KEY.NULL);
	                	// atomic because may collide with concurrent ACQUIRE
	                	OSAtomicDecrement32Barrier(&result->threadCount);
	            	}
	            	break;
	          // omit some code..... here
	        }
	    	returnresult; }}#endif
Copy the code

fetch_cache

The second step is to retrieve the SyncCache structure from the current thread cache, which contains the SyncCacheItem array. One thread can correspond to multiple SyncCacheItem synchronization objects, that is, one thread can process multiple SyncData.

//Check per-thread cache of already-owned locks for matching object SyncCache *cache = fetch_cache(NO); if (cache) { unsigned int i; for (i = 0; i < cache->used; I++) {// compile the SyncCache SyncCacheItem list. SyncCacheItem *item = &cache->list[i]; if (item->data->object ! = object) continue; SyncCacheItem result = item->data; // Found a match. if (result->threadCount <= 0 || item->lockCount <= 0) { _objc_fatal("id2data cache is buggy"); } switch(why) {// case ACQUIRE: // lock item->lockCount++; break; Case RELEASE: item->lockCount--; if (item->lockCount == 0) { // remove from per-thread cache cache->list[i] = cache->list[--cache->used]; // atomic because may collide with concurrent ACQUIRE OSAtomicDecrement32Barrier(&result->threadCount); } break; // omit some code...... here return result; }}Copy the code

LIST_FOR_OBJ

Third, obj is used to find the list of SyncData in the global hash table sDataLists. Since sDataLists are shared globally, spinlock_t locks are used.

// Thread cache didn't find anything. // Walk in-use list looking for matching object // Spinlock prevents multiple threads from creating multiple // locks for the same new object. // We could keep the nodes in some hash table if we Find that there are // more than 20 or so distinct locks active, but we don't do that now. { SyncData* p; SyncData* firstUnused = NULL; For (p = *listp; p ! = NULL; p = p->nextData) { if ( p->object == object ) { result = p; // atomic because may collide with concurrent RELEASE OSAtomicIncrement32Barrier(&result->threadCount); goto done; } // Mark unused SyncData if ((firstUnused == NULL) && (p->threadCount == 0)) firstUnused = p; } / / no SyncData currently associated with the object / / not found SyncData if ((according to = = RELEASE) | | (according to = = CHECK)) goto the done. // An unused one is found, use it if (firstUnused! = NULL ) { result = firstUnused; result->object = (objc_object *)object; result->threadCount = 1; goto done; }}Copy the code

New SyncData

Step 4. If SyncData is not found in the previous three steps, create a new SyncData.

// malloc a new SyncData and add to list. // XXX calling malloc with a global lock held is bad practice, // might be worth releasing the lock, mallocing, and searching again. // But since we never free these guys we won't be stuck in malloc very often. result = (SyncData*)calloc(sizeof(SyncData), 1); result->object = (objc_object *)object; result->threadCount = 1; //new recursive_mutex_t(&result->mutex) recursive_mutex_t(); result->nextData = *listp; *listp = result;Copy the code

Save SyncData

Fifth, save the SyncData object.

Done: // Release the sDataLists lock lockp->unlock(); if (result) { // Only new ACQUIRE should get here. // All RELEASE and CHECK and recursive ACQUIRE are // handled by the //ACQUIRE need to save SyncData if (why == RELEASE) {// Probably some thread is no longer great // while the object is held by another thread. return nil; } if (why ! = ACQUIRE) _objc_fatal("id2data is buggy"); if (result->object ! = object) _objc_fatal("id2data is buggy"); SyncData #if SUPPORT_DIRECT_THREAD_KEYS if (! fastCacheOccupied) { // Save in fast thread cache tls_set_direct(SYNC_DATA_DIRECT_KEY, result); tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1); } else #endif #endif #endif #endif #endif cache) cache = fetch_cache(YES); cache->list[cache->used].data = result; cache->list[cache->used].lockCount = 1; cache->used++; }}Copy the code

At this point, the function of ID2data has been roughly clear. Use SyncData to manage relationships between OBJ, threads, and recursive mutex.

  1. First from the current threadtls fast cacheCache to retrieve a singleSyncDataObject.
  2. If 1SyncDataNot found, fetched from the current thread’s cacheSyncCacheTo traverse theSyncCacheItemArray, find the correspondingSyncData.
  3. If 2SyncDataNot found again from the global hash tablesDataListsLook forSyncCacheTo check whether other threads have been occupiedobj.
  4. If you still don’t find itSyncData, create a new oneSyncDataObject.
  5. The newSyncDataTo the current thread cache, or to the global hash tablesDataListsIn the.

review

I’m sure we all know the answers to these questions at the beginning.

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. 4. What does synchronized have to do with pthread_mutex? A recursive mutex in SyncData, implemented using pthread_mutex. 5. What does synchronized have to do with objc_sync? Synchronized calls objc_sync_enter() and objc_sync_exit().Copy the code

The Next

  1. Common locks in iOS
  2. iOS OSSpinLock
  3. Underlying analysis of iOS Synchronized
  4. IOS NSLock those keys
  5. IOS Atomic low-level analysis

Refer to the link

swift foundation opensource objc4