目录ios
多个线程访问同一块资源的时候,很容易引起数据混乱问题。 一个你们都喜欢拿来举例子的就是买票demo
,今天我使用这个案例 假设有100张票,同时开5个窗口买票,5个窗口买票,咱们来看看结果
数组
//卖票演示 - (void)ticketTest{ self.ticketsCount = 50; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); for (NSInteger i = 0; i < 5; i++) { dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { [self sellingTickets]; } }); } } //卖票 - (void)sellingTickets{ int oldMoney = self.ticketsCount; sleep(.2); oldMoney -= 1; self.ticketsCount = oldMoney; NSLog(@"当前剩余票数-> %d", oldMoney); } 复制代码
正常状况下我有50张票,而后卖了50次,剩余票数应该是0,可是打印结果居然是3,因此这里就存在了线程安全问题。安全
出现线程安全的缘由bash
出现线程安全的缘由就是在同一个时间,多个线程同时读取一个值,像线程A和B同时读取了当前票数为10,等因而卖了两张票,可是总票数其实就减小了一张。服务器
解决方法markdown
使用线程同步技术,按照预约的前后次序依次进行,常见的线程同步技术就是加锁
并发
自旋锁(Spin lock)async
自旋锁与互斥锁有点相似,只是自旋锁不会引发调用者睡眠,若是自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁,"自旋"一词就是所以而得名。其做用是为了解决某项资源的互斥使用。由于自旋锁不会引发调用者睡眠,因此自旋锁的效率远 高于互斥锁。虽然它的效率比互斥锁高,可是它也有些不足之处: 一、自旋锁一直占用CPU,他在未得到锁的状况下,一直运行--自旋,因此占用着CPU,若是不能在很短的时 间内得到锁,这无疑会使CPU效率下降。 二、在用自旋锁时有可能形成死锁,当递归调用时有可能形成死锁,调用有些其余函数也可能形成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。 所以咱们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的状况下才真正须要,在单CPU且不可抢占式的内核下,自旋锁的操做为空操做。自旋锁适用于锁使用者保持锁时间比较短的状况下。函数
互斥锁oop
互斥锁属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要经过pthread_mutex_lock操做去获得一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就能够运行其余的任务(例如另外一个线程C)而没必要进行忙等待。而自旋锁则否则,它属于busy-waiting类型的锁,若是线程A是使用pthread_spin_lock操做去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到获得这个锁为止。
两种锁的加锁原理
互斥锁:线程会从sleep(加锁)——>running(解锁),过程当中有上下文的切换,cpu的抢占,信号的发送等开销。
自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。
对比 互斥锁的起始原始开销要高于自旋锁,可是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销形成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,可是随着持锁时间,加锁的开销是线性增加。
两种锁的应用
互斥锁用于临界区持锁时间比较长的操做,好比下面这些状况均可以考虑
至于自旋锁就主要用在临界区持锁时间很是短且CPU资源不紧张的状况下,自旋锁通常用于多核的服务器。
OSSpinLock叫作”自旋锁”,使用时须要导入头文件#import <libkern/OSAtomic.h>
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//加锁
OSSpinLockLock(&lock);
//解锁
OSSpinLockUnlock(&lock);
复制代码
demo
#import "OSSpinLockDemo.h" #import <libkern/OSAtomic.h> @interface OSSpinLockDemo() @property (assign, nonatomic) OSSpinLock ticketLock; @end @implementation OSSpinLockDemo - (instancetype)init { self = [super init]; if (self) { self.ticketLock = OS_SPINLOCK_INIT; } return self; } //卖票 - (void)sellingTickets{ OSSpinLockLock(&_ticketLock); [super sellingTickets]; OSSpinLockUnlock(&_ticketLock); } @end 复制代码
OSSpinLock
在iOS10.0之后就被弃用了,可使用os_unfair_lock_lock
替代。并且还有一些安全性问题,具体参考再也不安全的 OSSpinLock
os_unfair_lock
用于取代不安全的OSSpinLock
,从iOS10开始才支持 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并不是忙等 须要导入头文件#import <os/lock.h>
//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);
复制代码
demo
#import "os_unfair_lockDemo.h" #import <os/lock.h> @interface os_unfair_lockDemo() @property (assign, nonatomic) os_unfair_lock ticketLock; @end @implementation os_unfair_lockDemo - (instancetype)init { self = [super init]; if (self) { self.ticketLock = OS_UNFAIR_LOCK_INIT; } return self; } //卖票 - (void)sellingTickets{ os_unfair_lock_lock(&_ticketLock); [super sellingTickets]; os_unfair_lock_unlock(&_ticketLock); } @end 复制代码
mutex叫作”互斥锁”,等待锁的线程会处于休眠状态。须要导入头文件#import <pthread.h> 使用步骤
pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); /* * Mutex type attributes */ #define PTHREAD_MUTEX_NORMAL 0 #define PTHREAD_MUTEX_ERRORCHECK 1 #define PTHREAD_MUTEX_RECURSIVE 2 #define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL 复制代码
// 初始化锁
pthread_mutex_init(mutex, &attr);
复制代码
// 销毁属性
pthread_mutexattr_destroy(&attr);
复制代码
pthread_mutex_lock(&_mutex);
pthread_mutex_unlock(&_mutex);
复制代码
pthread_mutex_destroy(&_mutex);
复制代码
备注:咱们能够不初始化属性,在传属性的时候直接传NULL
,表示使用默认属性PTHREAD_MUTEX_NORMAL
。pthread_mutex_init(mutex, NULL);
具体代码
#import "pthread_mutexDemo.h" #import <pthread.h> @interface pthread_mutexDemo() @property (assign, nonatomic) pthread_mutex_t ticketMutex; @end @implementation pthread_mutexDemo - (instancetype)init { self = [super init]; if (self) { // 初始化属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); // 初始化锁 pthread_mutex_init(&(_ticketMutex), &attr); // 销毁属性 pthread_mutexattr_destroy(&attr); } return self; } //卖票 - (void)sellingTickets{ pthread_mutex_lock(&_ticketMutex); [super sellingTickets]; pthread_mutex_unlock(&_ticketMutex); } @end 复制代码
死锁 咱们稍微的修改一下代码
//卖票 - (void)sellingTickets{ pthread_mutex_lock(&_ticketMutex); [super sellingTickets]; [self sellingTickets2]; pthread_mutex_unlock(&_ticketMutex); } - (void)sellingTickets2{ pthread_mutex_lock(&_ticketMutex); NSLog(@"%s",__func__); pthread_mutex_unlock(&_ticketMutex); } 复制代码
上面的代码就会形成线程死锁
,由于方法sellingTickets
的结束须要sellingTickets2
解锁,方法sellingTickets2
的结束须要sellingTickets
解锁,相互引用形成死锁
可是pthread_mutex_t
里面有一个属性能够解决这个问题PTHREAD_MUTEX_RECURSIVE
PTHREAD_MUTEX_RECURSIVE
递归锁:容许同一个线程对同一把锁进行重复加锁。要考重点同一个线程
和同一把锁
- (instancetype)init { self = [super init]; if (self) { // 初始化属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 初始化锁 pthread_mutex_init(&(_ticketMutex), &attr); // 销毁属性 pthread_mutexattr_destroy(&attr); } return self; } 复制代码
对于上面的问题还有一个解决方案就是在方法sellingTickets2
中从新在建立一把新的锁,两个方法的锁对象不一样,就不会形成线程死锁了。
条件
// 初始化属性
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_cond_t condition
pthread_cond_init(&_cond, NULL);
// 等待条件
pthread_cond_wait(&_cond, &_mutex);
//激活一个等待该条件的线程
pthread_cond_signal(&_cond);
//激活全部等待该条件的线程
pthread_cond_broadcast(&_cond);
//销毁资源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
复制代码
使用案例:假设咱们有一个数组,里面有两个线程,一个是添加数组,一个是删除数组,咱们先调用删除数组,在调用添加数组,可是在数组为空的时候不调用删除数组。
#import "pthread_mutexDemo1.h" #import <pthread.h> @interface pthread_mutexDemo1() @property (assign, nonatomic) pthread_mutex_t mutex; @property (assign, nonatomic) pthread_cond_t cond; @property (strong, nonatomic) NSMutableArray *data; @end @implementation pthread_mutexDemo1 - (instancetype)init { if (self = [super init]) { // 初始化属性 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_cond_init(&_cond, NULL); self.data = [NSMutableArray array]; } return self; } - (void)otherTest { [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start]; } // 线程1 // 删除数组中的元素 - (void)__remove { pthread_mutex_lock(&_mutex); NSLog(@"__remove - begin"); if (self.data.count == 0) { // 等待 pthread_cond_wait(&_cond, &_mutex); } [self.data removeLastObject]; NSLog(@"删除了元素"); pthread_mutex_unlock(&_mutex); } // 线程2 // 往数组中添加元素 - (void)__add { pthread_mutex_lock(&_mutex); sleep(1); [self.data addObject:@"Test"]; NSLog(@"添加了元素"); // 激活一个等待该条件的线程 pthread_cond_signal(&_cond); pthread_mutex_unlock(&_mutex); } - (void)dealloc { pthread_mutex_destroy(&_mutex); pthread_cond_destroy(&_cond); } 复制代码
为了准确测试咱们能够在__add
中sleep(1)
NSLock是对mutex
普通锁的封装。pthread_mutex_init(mutex, NULL);
NSLock 遵循 NSLocking 协议。Lock 方法是加锁,unlock 是解锁,tryLock 是尝试加锁,若是失败的话返回 NO,lockBeforeDate: 是在指定Date以前尝试加锁,若是在指定时间以前都不能加锁,则返回NO
@protocol NSLocking - (void)lock; - (void)unlock; @end @interface NSLock : NSObject <NSLocking> { @private void *_priv; } - (BOOL)tryLock; - (BOOL)lockBeforeDate:(NSDate *)limit; @property (nullable, copy) NSString *name @end 复制代码
使用起来也是十分的简单
#import "LockDemo.h" @interface LockDemo() @property (strong, nonatomic) NSLock *ticketLock; @end @implementation LockDemo //卖票 - (void)sellingTickets{ [self.ticketLock lock]; [super sellingTickets]; [self.ticketLock unlock]; } @end 复制代码
NSRecursiveLock是对mutex
递归锁的封装,API跟NSLock基本一致
#import "RecursiveLockDemo.h" @interface RecursiveLockDemo() @property (nonatomic,strong) NSRecursiveLock *ticketLock; @end @implementation RecursiveLockDemo //卖票 - (void)sellingTickets{ [self.ticketLock lock]; [super sellingTickets]; [self.ticketLock unlock]; } @end 复制代码
NSCondition是对mutex
和cond
的封装,更加面向对象,咱们使用起来也更加的方便简洁
@interface NSCondition : NSObject <NSLocking> { - (void)wait; - (BOOL)waitUntilDate:(NSDate *)limit; - (void)signal; - (void)broadcast; @property (nullable, copy) NSString *name @end 复制代码
对于上面那个数组操做的案例咱们就能够变成这个样子了
// 线程1 // 删除数组中的元素 - (void)__remove { [self.condition lock]; if (self.data.count == 0) { // 等待 [self.condition wait]; } [self.data removeLastObject]; NSLog(@"删除了元素"); [self.condition unlock]; } // 线程2 // 往数组中添加元素 - (void)__add { [self.condition lock]; sleep(1); [self.data addObject:@"Test"]; NSLog(@"添加了元素"); // 信号 [self.condition signal]; [self.condition unlock]; } 复制代码
NSConditionLock是对NSCondition的进一步封装,能够设置具体的条件值
@interface NSConditionLock : NSObject <NSLocking> { - (instancetype)initWithCondition:(NSInteger)condition; @property (readonly) 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; @property (nullable, copy) NSString *name; @end 复制代码
里面有三个经常使用的方法
initWithCondition:
初始化Condition
,而且设置状态值lockWhenCondition:(NSInteger)condition:
当状态值为condition的时候加锁unlockWithCondition:(NSInteger)condition
当状态值为condition的时候解锁@interface NSConditionLockDemo() @property (strong, nonatomic) NSConditionLock *conditionLock; @end @implementation NSConditionLockDemo - (instancetype)init { if (self = [super init]) { self.conditionLock = [[NSConditionLock alloc] initWithCondition:1]; } return self; } - (void)otherTest { [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start]; } - (void)__one { [self.conditionLock lock]; NSLog(@"__one"); sleep(1); [self.conditionLock unlockWithCondition:2]; } - (void)__two { [self.conditionLock lockWhenCondition:2]; NSLog(@"__two"); [self.conditionLock unlockWithCondition:3]; } @end 复制代码
//表示最多开启5个线程
dispatch_semaphore_create(5);
// 若是信号量的值 > 0,就让信号量的值减1,而后继续往下执行代码
// 若是信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,而后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
复制代码
@interface dispatch_semaphoreDemo() @property (strong, nonatomic) dispatch_semaphore_t semaphore; @end @implementation dispatch_semaphoreDemo - (instancetype)init { if (self = [super init]) { self.semaphore = dispatch_semaphore_create(1); } return self; } - (void)otherTest { for (int i = 0; i < 20; i++) { [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start]; } } - (void)test { // 若是信号量的值 > 0,就让信号量的值减1,而后继续往下执行代码 // 若是信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,而后继续往下执行代码 dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); sleep(2); NSLog(@"test - %@", [NSThread currentThread]); // 让信号量的值+1 dispatch_semaphore_signal(self.semaphore); } @end 复制代码
咱们在运行代码打印的时候发现,每隔一秒出现一次打印。虽然咱们同时开启20个线程,可是一次只能访问一条线程的资源
使用GCD的串行队列也能够实现线程同步的
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ // 追加任务1 for (int i = 0; i < 2; ++i) { NSLog(@"1---%@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ // 追加任务2 for (int i = 0; i < 2; ++i) { NSLog(@"2---%@",[NSThread currentThread]); } }); 复制代码
@synchronized
是对mutex
递归锁的封装, @synchronized(obj)
内部会生成obj对应的递归锁,而后进行加锁、解锁操做
//卖票
- (void)sellingTickets{
@synchronized ([self class]) {
[super sellingTickets];
}
}
复制代码
对是实现底层咱们能够在objc4的objc-sync.mm文件中找到
synchronized
就是在开始和结束的时候调用了objc_sync_enter
&objc_sync_exit
方法。
objc_sync_enter
实现
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; } 复制代码
就是根据id2data
方法找到一个data
对象,而后在对data
对象进行mutex.lock()
加锁操做。咱们点击进入id2data
方法继续查找
#define LIST_FOR_OBJ(obj) sDataLists[obj].data static StripedMap<SyncList> sDataLists; 复制代码
发现获取data
对象的方法其实就是根据sDataLists[obj].data
这个方法来实现的,也就是一个哈希表。
pthread_rwlock常常用于文件等数据的读写操做,须要导入头文件#import <pthread.h>
iOS中的读写安全方案须要注意一下场景
//初始化锁
pthread_rwlock_t lock;
pthread_rwlock_init(&_lock, NULL);
//读加锁
pthread_rwlock_rdlock(&_lock);
//读尝试加锁
pthread_rwlock_trywrlock(&_lock)
//写加锁
pthread_rwlock_wrlock(&_lock);
//写尝试加锁
pthread_rwlock_trywrlock(&_lock)
//解锁
pthread_rwlock_unlock(&_lock);
//销毁
pthread_rwlock_destroy(&_lock);
复制代码
#import <pthread.h> @interface pthread_rwlockDemo () @property (assign, nonatomic) pthread_rwlock_t lock; @end @implementation pthread_rwlockDemo - (instancetype)init { self = [super init]; if (self) { // 初始化锁 pthread_rwlock_init(&_lock, NULL); } return self; } - (void)otherTest{ dispatch_queue_t queue = dispatch_get_global_queue(0, 0); for (int i = 0; i < 10; i++) { dispatch_async(queue, ^{ [self read]; }); dispatch_async(queue, ^{ [self write]; }); } } - (void)read { pthread_rwlock_rdlock(&_lock); sleep(1); NSLog(@"%s", __func__); pthread_rwlock_unlock(&_lock); } - (void)write { pthread_rwlock_wrlock(&_lock); sleep(1); NSLog(@"%s", __func__); pthread_rwlock_unlock(&_lock); } - (void)dealloc { pthread_rwlock_destroy(&_lock); } @end 复制代码
咱们能够发现读操做1s有可能出现屡次,可是写操做不会
这个函数传入的并发队列必须是本身经过dispatch_queue_cretate建立的 若是传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
//初始化 self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT); //读操做 dispatch_async(self.queue, ^{ }); //写操做 dispatch_barrier_async(self.queue, ^{ }); 复制代码
性能从高到低排序