談?wù)刬OS的線程安全

一、先說什么情況下線程是不安全的

在同一個進程里,資源是可以共享的;多個線程同時訪問相同資源,并且至少有一個線程在進行寫操作。

如以下情況:
a、多個線程同時訪問一個變量:

單例的某個公共屬性(如:NSMutableDictionary),可能被某個線程在寫,也可能正在被另外線程在讀;此時包括NSMutableDictionary里面的key對應(yīng)的對象,以及對象里的屬性都不是安全的。

b、多個線程訪問沙盒里的某個文件:

一個線程正在下載該文件,另外一個可能正在讀取該文件;該文件就是不安全的。

總之,只要你在coding的過程里,有異步操作,且有讀寫某個全局型資源(變量、對象、文件等等),都需要考慮線程安全。

二、如何做到線程安全?

1、使用前進行完整性校驗

這個解決方案適用于有些場景無法加鎖的情況下。如,寫操作發(fā)生在OC代碼里,讀操作發(fā)生在某個sdk的C++代碼里,你沒法去改sdk的代碼。所以這個時候,就要做讀取前校驗,如通過md5校驗文件的完整性等。

2、鎖

介紹iOS鎖的文章很多,這里主要介紹兩種常見的鎖。

I、@synchronized,使用起來最簡單的鎖

@synchronized(obj) {
        // 需要加鎖的代碼塊
}
@synchronized 的作用是創(chuàng)建一個互斥鎖,保證此時沒有其它線程對obj對象進行修改;

這個是objective-c的一個鎖定令牌,防止obj對象在同一時間內(nèi)被其它線程訪問,起到線程的保護作用。

指令@synchronized()通過對一段代碼的使用進行加鎖。其他試圖執(zhí)行該段代碼的線程都會被阻塞,直到加鎖線程退出執(zhí)行該段被保護的代碼段,也就是說@synchronized()代碼塊中的最后一條語句已經(jīng)被執(zhí)行完畢的時候。

obj對象就是一個互斥信號量。

@synchronized(obj)使用起來很簡單,括號里的obj對象要注意以下事項:
a、obj對象的創(chuàng)建時機要早于多線程調(diào)用

一般在構(gòu)造方法(init)方法里就創(chuàng)建好。

b、obj對象的創(chuàng)建時機要早于多線程調(diào)用
c、obj對象的創(chuàng)建時機要早于多線程調(diào)用
d、不要使用@synchronized(self)

容易導(dǎo)致死鎖的出現(xiàn):

ClassA:

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

ClassB:

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

在ClassB中使用了ClassA的實例當(dāng)token,ClassA中使用self當(dāng)key,都是一個實例。兩個公共鎖交替使用同一個key,導(dǎo)致死鎖。

e、obj最好是當(dāng)前類內(nèi)部維護的一個NSObject對象,對外不可見
f、不同的數(shù)據(jù)使用不同的鎖,避免性能浪費
@synchronized (objA) {
    [arrA addObject:obj];
}

@synchronized (objB) {
    [arrB addObject:obj];
}
g、加鎖的范圍越小越好
@synchronized (tokenA) {
    [self doSomethingWithA:arrA];
}
- (void)doSomethingWithA {
    NSArray *arrA = [[NSArray alloc]init];
    [_arrB addObject:objB];
    [self doSomethingWithB];
}

其實我們只需要對[_arrB addObject:objB]這行代碼加鎖就可以。

||、dispatch_semaphore,性能最好的鎖

//創(chuàng)建信號量,參數(shù):信號量的初值,如果小于0則會返回NULL
dispatch_semaphore_create(信號量值)
 
//等待降低信號量
dispatch_semaphore_wait(信號量,等待時間)
 
//提高信號量
dispatch_semaphore_signal(信號量)

見YYCache的一段源碼,通過dispatch_semaphore實現(xiàn)set和get的安全訪問

static void _YYDiskCacheInitGlobal() {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _globalInstancesLock = dispatch_semaphore_create(1);
    });
}


static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) {
    if (path.length == 0) return nil;
    _YYDiskCacheInitGlobal();
    dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
    id cache = [_globalInstances objectForKey:path];
    dispatch_semaphore_signal(_globalInstancesLock);
    return cache;
}

static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
    if (cache.path.length == 0) return;
    _YYDiskCacheInitGlobal();
    dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
    [_globalInstances setObject:cache forKey:cache.path];
    dispatch_semaphore_signal(_globalInstancesLock);
}
參考文章

https://juejin.cn/post/6844903890056396814

https://blog.51cto.com/u_15316122/3210257

https://blog.csdn.net/u013756604/article/details/109510282

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容