一、先說什么情況下線程是不安全的
在同一個進程里,資源是可以共享的;多個線程同時訪問相同資源,并且至少有一個線程在進行寫操作。
如以下情況:
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