一、先说什么情况下线程是不安全的
在同一个进程里,资源是可以共享的;多个线程同时访问相同资源,并且至少有一个线程在进行写操作。
如以下情况:
a、多个线程同时访问一个变量:
单例的某个公共属性(如:NSMutableDictionary),可能被某个线程在写,也可能正在被另外线程在读;此时包括NSMutableDictionary里面的key对应的对象,以及对象里的属性都不是安全的。
b、多个线程访问沙盒里的某个文件:
一个线程正在下载该文件,另外一个可能正在读取该文件;该文件就是不安全的。
总之,只要你在coding的过程里,有异步操作,且有读写某个全局型资源(变量、对象、文件等等),都需要考虑线程安全。
二、如何做到线程安全?
1、使用前进行完整性校验
这个解决方案适用于有些场景无法加锁的情况下。如,写操作发生在OC代码里,读操作发生在某个sdk的C++代码里,你没法去改sdk的代码。所以这个时候,就要做读取前校验,如通过md5校验文件的完整性等。
2、锁
介绍iOS锁的文章很多,这里主要介绍两种常见的锁。
I、@synchronized,使用起来最简单的锁
@synchronized(obj) {
// 需要加锁的代码块
}
@synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对obj对象进行修改;
这个是objective-c的一个锁定令牌,防止obj对象在同一时间内被其它线程访问,起到线程的保护作用。
指令@synchronized()通过对一段代码的使用进行加锁。其他试图执行该段代码的线程都会被阻塞,直到加锁线程退出执行该段被保护的代码段,也就是说@synchronized()代码块中的最后一条语句已经被执行完毕的时候。
obj对象就是一个互斥信号量。
@synchronized(obj)使用起来很简单,括号里的obj对象要注意以下事项:
a、obj对象的创建时机要早于多线程调用
一般在构造方法(init)方法里就创建好。
b、obj对象的创建时机要早于多线程调用
c、obj对象的创建时机要早于多线程调用
d、不要使用@synchronized(self)
容易导致死锁的出现:
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的实例当token,ClassA中使用self当key,都是一个实例。两个公共锁交替使用同一个key,导致死锁。
e、obj最好是当前类内部维护的一个NSObject对象,对外不可见
f、不同的数据使用不同的锁,避免性能浪费
@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,性能最好的锁
//创建信号量,参数:信号量的初值,如果小于0则会返回NULL
dispatch_semaphore_create(信号量值)
//等待降低信号量
dispatch_semaphore_wait(信号量,等待时间)
//提高信号量
dispatch_semaphore_signal(信号量)
见YYCache的一段源码,通过dispatch_semaphore实现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