1. 程式人生 > >iOS 中關於@synchronized的一點思考

iOS 中關於@synchronized的一點思考

參考 :

https://www.cnblogs.com/jukaiit/p/5570056.html

https://www.cnblogs.com/CoderAlex/p/5257339.html

http://www.cocoachina.com/ios/20161205/18279.html

http://www.jianshu.com/p/1e59f0970bf5

首先我們知道@synchronized 的作用是建立一個互斥鎖,保證此時沒有其它執行緒對鎖物件進行修改。這個是objective-c的一個鎖定令牌,防止鎖物件在同一時間內被其它執行緒訪問,起到執行緒的保護作用。

指令@synchronized()通過對一段程式碼的使用進行加鎖。其他試圖執行該段程式碼的執行緒都會被阻塞,直到加鎖執行緒退出執行該段被保護的程式碼段

相關術語:

執行緒同步:多天執行緒在同一條線上執行(按順序的執行任務)

互斥鎖就是使用了執行緒同步技術

下面看互斥鎖使用格式

 @synchronized (鎖物件) {
         加鎖程式碼
        }

鎖定一份程式碼只能用一把鎖,多把鎖是無效的。

優點:防止多執行緒操作時資源競爭導致的資料安全問題

缺點:需要消耗大量的CPU資源

互斥鎖的使用前提是:多條執行緒使用同一份資源。

檢視該段的彙編程式碼可知主要使用了兩個函式

 _objc_sync_enter
  _objc_sync_exit
在Objective-C 中檢視原始碼
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;
}

檢視SyncData

typedef struct SyncData {
    struct SyncData* nextData;
    DisguisedPtr<objc_object> object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;

可見在SyncData 維護了一個遞迴鎖,所以如下程式碼就不會死鎖。
@synchronized (obj) {
    NSLog(@"13qw");
    @synchronized (obj) {
        NSLog(@"2asda");
    }
}

但是並不意味著synchronized不會死鎖,類似下面的程式碼還是會導致死鎖的

@synchronized (self) {
    [_sharedLock lock];
    NSLog(@"code in class A");
    [_sharedLock unlock];
}

正確使用@synchronized還需要注意粒度控制  
@synchronized (token) {
    [arrA addObject:obj];
}
@synchronized (token) {
    [arrB addObject:obj];
}

使用同一個token來同步arrA和arrB的訪問,雖然arrA和arrB之間沒有任何聯絡。傳入self的就更不對了。

應該是不同的資料使用不同的鎖,儘量將粒度控制在最細的程度。上述程式碼應該是:

@synchronized (tokenA) {
    [arrA addObject:obj];
}
@synchronized (tokenB) {
    [arrB addObject:obj];
}

@synchronized(nil)不起任何作用,所以傳參時還是需要做好判斷


注意內部的函式呼叫

@synchronized (tokenA) { [arrA addObject:obj]; [self doSomething:arrA]; } 在 doSomething:中可能有更多的函式呼叫,這樣就會導致@synchronized更慢,所以建議@synchronized內加鎖的程式碼還是儘量簡單的好, 如果無法避免,還是要做好維護工作