mac 개발 시리즈 31: 스레드 동기화 잠금 @synchronized 원본 이해

5482 단어
오늘 Crash를 만났는데 창고를 이용하여 초보적인 판단 원인은'다선정 쓰기 DB'이다. 문제 코드는 대체로 다음과 같다.
        NSMutableArray *arr;
        @synchronized(arr) { 
        arr = [self func]; // func     DB    
        if(arr == nil) { 
            arr = [NSMutableArray array]; 
        }
    }

그런데 여기 분명히 동기화 자물쇠 @synchronized를 사용했는데 왜 여러 라인이 동시에 Block에 들어갑니까?기존의 방식대로 다시 쓰면 다음과 같은 C++ 구현이 가능합니다.
    static void _I_Demo_synchronizedTest(Demo * self, SEL _cmd) { 
    NSMutableArray *arr; 
    {
      id _sync_obj = (id)arr;
      objc_sync_enter(_sync_obj); //      ,   arr 
        try { 
            struct _SYNC_EXIT {
                _SYNC_EXIT(id arg) : sync_exit(arg) {}  
                ~_SYNC_EXIT() {objc_sync_exit(sync_exit); //      ,   arr 
  } 
            id sync_exit;
        }   _sync_exit(_sync_obj);//           ,   arr 
          } catch (id e) { 
      } 
   }
}

추가, objc 보기sync_enter와 objcsync_exit의 원본 코드는 다음과 같습니다.
        int objc_sync_enter(id obj)
    { 
              int result = OBJC_SYNC_SUCCESS;
              if (obj) {
            //   obj     SyncData  ,id2data        
            SyncData* data = id2data(obj, ACQUIRE);//    
            result = recursive_mutex_lock(&data->mutex); } 
        else 
          { // @synchronized(nil) does nothing 
    }
       return result;
    }

다음:
               int objc_sync_exit(id obj)
                {   int result = OBJC_SYNC_SUCCESS;
                    if (obj) { 
                        SyncData* data = id2data(obj, RELEASE); //     
                        result = recursive_mutex_unlock(&data->mutex); 
                    } else {
               // @synchronized(nil) does nothing 
                  } 
                  return result;
            } 

위의 원본에서 알 수 있듯이 1. @synchronized는 귀속 자물쇠를 사용합니다(즉 같은 라인은 다시 들어갈 수 있고 자물쇠가 사라지지 않습니다).2. @synchronized(nil)는 잠기지 않습니다. 이어서 다음과 같은 관건적인 데이터 구조를 보십시오. 분명히 SyncList는 단일 체인 테이블이고 SyncData는 단일 체인 테이블 노드이며 전체 저장은'지퍼법 해시 테이블'입니다.
        typedef struct SyncData {
             struct SyncData* nextData; //      SyncData     
             DisguisedPtr object; // @synchronized   obj
             int32_t threadCount; // number of THREADS using this block   
             recursive_mutex_t mutex; //    
          } SyncData;

        struct SyncList {
               SyncData *data; //        
               spinlock_t lock; //              
               SyncList() : data(nil) { }
        };

define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
define LIST_FOR_OBJ(obj) sDataLists[obj].data
  static StripedMap sDataLists; //    ,key:obj,value:   

      //   obj     SyncData  static SyncData* id2data(id object, enum usage why)
      { 
          spinlock_t *lockp = &LOCK_FOR_OBJ(object); // SyncList 
         SyncData **listp = &LIST_FOR_OBJ(object); // obj   SyncData     
        SyncList SyncData* result = NULL;//        cache   

        lockp->lock(); 
        {
            SyncData* p; 
            SyncData* firstUnused = NULL;
       //       
            for (p = *listp; p != NULL; p = p->nextData) { 
                  if ( p->object == object ) {
              //   obj   SyncData   
                  result = p; 
                // SyncData         1  
                 OSAtomicIncrement32Barrier(&result->threadCount); 
                  goto done; 
        }
    // SyncData               ,    ,               
      if ( (firstUnused == NULL) && (p->threadCount == 0) ) 
                    firstUnused = p; 
            }
     //       obj   SyncData  ,       SyncData  
    // an unused one was found, use it
             if ( firstUnused != NULL ) {
                  result = firstUnused;
                  result->object = (objc_object *)object;
                  result->threadCount = 1;
                  goto done;
            }
        }
//       obj   SyncData  ,        SyncData  
       result = (SyncData*)calloc(sizeof(SyncData), 1);
       result->object = (objc_object *)object;
       result->threadCount = 1;
       new (&result->mutex) recursive_mutex_t();
//    SyncData         
       result->nextData = *listp;
       *listp = result;
 done:
       lockp->unlock();
       return result;}

}
    template
    class StripedMap {
    #if TARGET_OS_EMBEDDED 
        enum { StripeCount = 8 };
      #else
        enum { StripeCount = 64 };#endif 
        static unsigned int indexForPointer(const void *p) {
        //  obj           index 
          uintptr_t addr = reinterpret_cast(p);
          return ((addr >> 4) ^ (addr >> 9)) % StripeCount; 
}
     public: 
      T& operator[] (const void *p) { 
            return array[indexForPointer(p)].value; 
      }
  };

@synchronized의 원본 코드가 실현되었는지 확인하고 crash를 돌아보면 문제는 주로 두 가지가 있다. 첫째,arr가 초기화되지 않았을 때nil이고 동기화 자물쇠가 효력이 발생하지 않았으며block은 임계 구역이 아니다.2. arr가 수정되었다. 즉, 메모리 주소와 비상량이다. 라인 1은 arr에 대응하는 주소가addr1이고 Block에 들어간다.루틴 2에서arr에 대응하는 주소는addr2로 받을 수 있으며, 마찬가지로 Block에 들어갈 수 있으며, 루틴 1이 Block을 실행하기를 기다리지 않습니다.
참조 링크:https://opensource.apple.com/source/objc4/objc4-680/runtime/objc-sync.mm https://github.com/opensource-apple/objc4/blob/master/runtime/objc-private.h

좋은 웹페이지 즐겨찾기