1、多线程的安全隐患原因
主要是因为资源共享。当一块资源可能会被多个线程共享时,也就是多个线程可能会访问同一块资源(比如多个线程访问同一个对象、同一个变量、同一个文件),很容易引发数据错乱和数据安全问题。
2、解决方案
使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)
常见的线程同步技术是:加锁(加完锁之后记得要解锁)
gcd的串行队列也可以实现线程同步
以下都是可以保证线程安全的线程同步方案:
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
3、各种锁的说明
OSSpinLock
OSSpinLockLock是一种自旋锁(高级锁):在等待过程中不会睡眠,而是一直处于循环状态、忙等状态,等待过程中会占用cpu资源。
不过,现在OSSpinLockLock已经不推荐使用了(iOS10废弃)。
原因:可能会出现优先级反转问题。即:如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。
(假如有两个线程执行任务,线程1优先级高,线程2优先级低,当线程2先执行任务时,OSSpinLock加锁,cpu分配更多的资源执行线程1,导致线程1一直加锁失败,线程2一直无法解锁。)
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回ture)
bool result = OSSpinLockTry(&lock);
//加锁
OSSpinLock(&lock);
//解锁
OSSpinLockUnlock(&lock);
os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock。
os_unfair_lock是一种低级锁:在等待过程中会睡眠,这样的锁在唤起时候也会浪费很多的资源。
//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//尝试加锁,同OSSpinLock
bool result = os_unfair_lock_trylock(&lock);
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);
pthread_mutex
-
pthread_mutex是一个互斥锁。
当设置属性为PTHREAD_MUTEX_NORMAL时候,锁就是默认default为互斥锁。
//初始化锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
//初始化锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
//尝试加锁
pthread_mutex_trylock(&mutex);
//加锁
pthread_mutex_lock(&mutex);
//解锁
pthread_mutex_unlock(&mutex);
//销魂相关属性
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);
-
pthread_mutex:递归
当设置属性为PTHREAD_MUTEX_RECURSIVE,为递归锁(属于互斥锁的一种特例),允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//修改属性为递归锁
// 初始化锁
pthread_mutex_init(mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);
//销毁锁
pthread_mutex_destroy(mutex);
- pthread_mutex:条件
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
//初始化锁
pthread_mutex_init(&_mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);
//初始化条件
pthread_cond_t _cond;
pthread_cond_init(&_cond, NULL);
//等待条件(进入休眠,放开mutex锁,被唤醒后,会再次对mutex加锁)
pthread_cond_wait(&_cond, &_mutex);
//激活一个等待该条件的线程
pthread_cond_signal(&_cond);
//激活所有等待该条件的线程
pthread_cond_broadcast(&_cond);
//销毁资源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
NSLock、NSRecursiveLock、NSCondition
NSLock是对mutex普通锁的封装
NSRecursiveLock也是对mutex递归锁的封装
NSCondition是对mutex和cond的封装
//NSLock的API
NSLock *lock = [[NSLock alloc] init];//初始化锁
[lock lock];//上锁
[lock unlock];//解锁
//NSRecursiveLock的API
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];//初始化锁
[recursiveLock lock];//上锁
[recursiveLock unlock];//解锁
//NSCondition的API
self.condition = [[NSCondition alloc] init];//初始化
[self.condition wait];//等待条件
[self.condition signal];//激活一个等待该条件的线程
[self.condition broadcast]; //激活所有等待该条件的线程
[self.condition unlock];//解锁
NSConditionLock
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
//初始化
- (instancetype)initWithCondition:(NSInteger)condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
dispatch_queue (串行serial)
直接使用GCD的串行队列,也是可以实现线程同步的
因为GCD的串行队列访问同一资源的时候,本质就是一个一个按照顺序去执行的
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
});
dispatch_semaphore
semaphore叫做”信号量”
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
//初始化信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(long value);
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//让信号量的值+1
dispatch_semaphore_signal(semaphore);
@synchronized
@synchronized是对mutex递归锁的封装
@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
@synchronized (self) {
//任务
}
以上锁的使用实例子见github:https://github.com/ychen3022/lockDemo.git
4、总结
- <1>以上各种锁的性能怎么样?
os_unfair_lock //性能最好,但是iOS10才能用,是对os_unfair_lock的替代
OSSpinLock //自旋锁,不建议使用,已经废弃了
dispatch_semaphore //性能好,而且ios8、9都可以使用
pthread_mutex //互斥锁,推荐使用
dispatch_queue(DISPATCH_QUEUE_SERIAL)//gcd的串行队列
NSLock //互斥锁,是对mutex的封装
NSCondition//互斥锁
pthread_mutex(recursive) //递归锁,相比mutext普通锁,要耗性能一些
NSRecursiveLock//互斥锁
NSConditionLock//对NSCondition的进一步封装,互斥锁
@synchronized//性能最差,是NSLock的一种封装,牺牲了效率,简洁了语法。
<2>自旋锁和互斥锁的对比?
(1)、自旋锁:称之为高级锁,会忙等,不休眠。即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
(2)、互斥锁:称之为低级锁,会休眠。即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。什么情况使用自旋锁比较划算?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器什么情况使用互斥锁比较划算?
预计线程等待锁的时间较长
单核处理器
临界区有IO(文件读写)操作,因为IO操作都耗资源
临界区代码复杂或者循环量大
临界区竞争非常激烈