多线程编程中,应该尽量避免资源在线程之间共享,以减少线程间的相互作用。 但是总是有多个线程相互干扰的情况(如多个线程访问一个资源)。在线程必须交互的情况下,就需要一些同步工具,来确保当它们交互的时候是安全的。
我们在声明属性的时候 一个名词用的很频繁 atomic(原子性操作) 、 nonatomic(非原子性操作)
atomic
提供多线程安全 会有加锁操作 平时的操作因为不会涉及到多线程资源争夺 所以 用atomic
显的有些浪费 所以我们都用nonatomic
没有任何限制
下面我们来看实例吧
dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"线程1 ----------%@",[NSThread currentThread]);
self.price = @"1000";
NSLog(@"你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张");
NSLog(@"你好 现在杭州到北京的票价%@",self.price);
NSLog(@"好的帮我预订一张");
NSLog(@"好的先生我现在帮你处理订单");
sleep(2);
NSLog(@"操作好了请用支付宝或微信支付");
NSLog(@"您已支付完毕 您购买的机票金额为%@元",self.price);
});
dispatch_async(queue, ^{
NSLog(@"线程2 ----------%@",[NSThread currentThread]);
sleep(2);
self.price = @"1200";
});
线程1 ----------<NSThread: 0x600001fe55c0>{number = 4, name = (null)}
线程2 ----------<NSThread: 0x600001fdaa00>{number = 3, name = (null)}
你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张
你好 现在杭州到北京的票价1000
好的帮我预订一张
好的先生我现在帮你处理订单
操作好了请用支付宝或微信支付
您已支付完毕 您购买的机票金额为1200元
平时我们买飞机票 可以通过不同途径购买 飞机票的价格也是随着剩余的票数 决定打折程度 上面发送的问题是 票价为1000的机票 实际支付完成的时候确实1200
通过代码我们发现 异步并发的两个子线程 都是操作price 价格 导致了 一个线程在使用price 的时候发生价格变动 这显然是违背我们的主观意愿的 为了防止这种变动 我们需要用到线程锁来处理
iOS的线程锁有很多种 接下来我们来逐一介绍
@synchronized
@synchronized (self) {
//需要加锁的写在里面
}
synchronized中传入的object的内存地址,被用作key,通过hash map对应的一个系统维护的递归锁。不管是传入什么类型的object,只要是有内存地址,就能启动同步代码块的效果
@synchronized是最方便的 但是它是最慢的
dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"线程1 ----------%@",[NSThread currentThread]);
@synchronized (self) {
self.price = @"1000";
NSLog(@"你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张");
NSLog(@"你好 现在杭州到北京的票价%@",self.price);
NSLog(@"好的帮我预订一张");
NSLog(@"好的先生我现在帮你处理订单");
sleep(2);
NSLog(@"操作好了请用支付宝或微信支付");
NSLog(@"您已支付完毕 您购买的机票金额为%@元",self.price);
}
});
dispatch_async(queue, ^{
NSLog(@"线程2 ----------%@",[NSThread currentThread]);
//保证线程2 在线程1后加锁
sleep(2);
@synchronized (self) {
self.price = @"1200";
NSLog(@"当前杭州到北京的最新价格为%@",self.price);
}
});
你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张
你好 现在杭州到北京的票价1000
好的帮我预订一张
好的先生我现在帮你处理订单
操作好了请用支付宝或微信支付
您已支付完毕 您购买的机票金额为1000元
当前杭州到北京的最新价格为1200
NSLock
遵循 NSLocking
协议 所以具备 lock(加锁) unlock(解锁) 两个要成对使用
lock 会阻塞当前线程 直到加锁成功
dispatch_async(queue, ^{
NSLog(@"线程1 ----------%@",[NSThread currentThread]);
[lock lock];
self.price = @"1000";
NSLog(@"你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张");
NSLog(@"你好 现在杭州到北京的票价%@",self.price);
NSLog(@"好的帮我预订一张");
NSLog(@"好的先生我现在帮你处理订单");
sleep(2);
NSLog(@"操作好了请用支付宝或微信支付");
NSLog(@"您已支付完毕 您购买的机票金额为%@元",self.price);
[lock unlock];
});
dispatch_async(queue, ^{
NSLog(@"线程2 ----------%@",[NSThread currentThread]);
//保证线程2 在线程1后加锁
sleep(2);
[lock lock];
self.price = @"1200";
NSLog(@"当前杭州到北京的最新价格为%@",self.price);
[lock unlock];
});
尝试加锁 加锁成功返回YES 需要解锁 加锁失败 返回NO
- (BOOL)tryLock;
dispatch_async(queue, ^{
NSLog(@"线程2 ----------%@",[NSThread currentThread]);
sleep(2);
if ([lock tryLock]) {
self.price = @"1200";
NSLog(@"当前杭州到北京的最新价格为%@",self.price);
}else {
NSLog(@"当前票价没变化");
}
});
当前票价没变化
输出 当前票价没变化
之后方法执行完毕 没有尝试继续加锁 不会阻塞当前线程
在指定时间内尝试加锁
- (BOOL)lockBeforeDate:(NSDate *)limit;
dispatch_async(queue, ^{
NSLog(@"线程2 ----------%@",[NSThread currentThread]);
sleep(2);
if ([lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
self.price = @"1200";
NSLog(@"当前杭州到北京的最新价格为%@",self.price);
}else {
NSLog(@"当前票价没变化");
}
});
当前杭州到北京的最新价格为1200
会阻塞当前线程 并且在指定时间内 反复尝试加锁
NSConditionLock
条件锁 多个condition
//初始化 条件为0
NSConditionLock * lock = [[NSConditionLock alloc] initWithCondition:50];
dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"线程1 ----------%@",[NSThread currentThread]);
NSLog(@"1号犯人越狱中");
[lock lockWhenCondition:1];
NSLog(@"1号犯人越狱成功");
[lock unlock];
});
dispatch_async(queue, ^{
NSLog(@"线程2 ----------%@",[NSThread currentThread]);
NSLog(@"2号犯人越狱中");
[self archiveKey:lock];
});
dispatch_async(queue, ^{
NSLog(@"线程3 ----------%@",[NSThread currentThread]);
NSLog(@"3号犯人越狱中");
[lock lockWhenCondition:3];
NSLog(@"3号犯人越狱成功");
[lock unlockWithCondition:4];
});
dispatch_async(queue, ^{
NSLog(@"线程4 ----------%@",[NSThread currentThread]);
NSLog(@"4号犯人越狱中");
[lock lockWhenCondition:4];
NSLog(@"4号犯人越狱成功");
[lock unlockWithCondition:1];
});
-(void)archiveKey:(NSConditionLock *)lock {
NSLog(@"2号犯人开始越狱");
NSInteger r = arc4random_uniform(100);
if ([lock tryLockWhenCondition:r]) {
NSLog(@"2号犯人反复尝试后终于拿到了监狱的钥匙 越狱成功");
[lock unlockWithCondition:3];
}else {
NSLog(@"2号犯人越狱失败");
[self archiveKey:lock];
}
}
2号犯人反复尝试后终于拿到了监狱的钥匙 越狱成功
3号犯人越狱成功
4号犯人越狱成功
1号犯人越狱成功
条件锁 condtion
初始化
可以默认一个值 加锁 、解锁
成功 都可以给condtion
指定一个值 加锁失败 、不设置
则condtion
不变
通过 condition 我们可以实现线程依赖
NSRecursiveLock
递归锁 当前线程可以反复上锁不会导致死锁
NSLock * lock = [[NSLock alloc] init];
NSRecursiveLock * relock = [[NSRecursiveLock alloc] init];
dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"线程1 ----------%@",[NSThread currentThread]);
[lock lock];
[lock lock];
NSLog(@"1号犯人越狱成功");
[lock unlock];
[lock unlock];
});
dispatch_async(queue, ^{
NSLog(@"线程2 ----------%@",[NSThread currentThread]);
[relock lock];
[relock lock];
NSLog(@"2号犯人越狱成功");
[relock unlock];
[relock unlock];
});
dispatch_async(queue, ^{
NSLog(@"线程3 ----------%@",[NSThread currentThread]);
sleep(5);
[relock lock];
NSLog(@"3号犯人越狱成功");
[relock unlock];
});
2号犯人越狱成功
3号犯人越狱成功
可以看到 NSLock 因为同一个线程上了2次锁 已经造成死锁 不会有任何输出
NSRecursiveLock 可以同线程多次上锁 如同引用计数 会记录上锁的次数 当解锁次数相同的时候才会释放掉 才不会影响其他线程上锁。
NSCondition
线程检测器 不需要轮询 直接可以让当前线程等待 不影响其他线程上锁
NSCondition * condition = [[NSCondition alloc] init];
dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
__block BOOL finished = NO;
dispatch_async(queue, ^{
[condition lock];
if (!finished) {
[condition wait];
}
NSLog(@"1号任务执行完毕");
[condition unlock];
});
dispatch_async(queue, ^{
sleep(2);
[condition lock];
sleep(10);
NSLog(@"2号任务执行完毕");
finished = YES;;
[condition signal];
[condition unlock];
});
2号任务执行完毕
1号任务执行完毕
OSSpinLock
typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock);
bool OSSpinLockTry( volatile OSSpinLock *__lock );
void OSSpinLockLock( volatile OSSpinLock *__lock );
void OSSpinLockUnlock( volatile OSSpinLock *__lock );
自旋锁 同NSLock
不同的是 NSLock
轮询过后 会进入waiting
等待唤醒 OSSpinLock
会一直轮询 不会进入waiting
比较耗资源 但是速度快
__block OSSpinLock spinLock = OS_SPINLOCK_INIT;
dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"线程1 ----------%@",[NSThread currentThread]);
OSSpinLockLock(&spinLock);
self.price = @"1000";
NSLog(@"你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张");
NSLog(@"你好 现在杭州到北京的票价%@",self.price);
NSLog(@"好的帮我预订一张");
NSLog(@"好的先生我现在帮你处理订单");
sleep(10);
NSLog(@"操作好了请用支付宝或微信支付");
NSLog(@"您已支付完毕 您购买的机票金额为%@元",self.price);
OSSpinLockUnlock(&spinLock);
});
dispatch_async(queue, ^{
NSLog(@"线程2 ----------%@",[NSThread currentThread]);
sleep(2);
OSSpinLockLock(&spinLock);
self.price = @"1200";
NSLog(@"当前杭州到北京的最新价格为%@",self.price);
OSSpinLockUnlock(&spinLock);
});
效果同上
'OSSpinLock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock() from <os/lock.h> instead
你会发现 会有一个警告 因为OSSpinLock
有BUG存在 不再安全 所以推荐使用 os_unfair_lock
__block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"线程1 ----------%@",[NSThread currentThread]);
os_unfair_lock_lock(&unfairLock);
self.price = @"1000";
NSLog(@"你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张");
NSLog(@"你好 现在杭州到北京的票价%@",self.price);
NSLog(@"好的帮我预订一张");
NSLog(@"好的先生我现在帮你处理订单");
sleep(10);
NSLog(@"操作好了请用支付宝或微信支付");
NSLog(@"您已支付完毕 您购买的机票金额为%@元",self.price);
os_unfair_lock_unlock(&unfairLock);
});
dispatch_async(queue, ^{
NSLog(@"线程2 ----------%@",[NSThread currentThread]);
sleep(2);
os_unfair_lock_lock(&unfairLock);
self.price = @"1200";
NSLog(@"当前杭州到北京的最新价格为%@",self.price);
os_unfair_lock_unlock(&unfairLock);
});
pthread_mutex_t
pthread_mutex_t
可以创建互斥锁 检错锁 递归锁
int pthread_mutex_init(pthread_mutex_t * __restrict,
const pthread_mutexattr_t * _Nullable __restrict)
pthread_mutexattr_t
可以指定锁的类型 默认为互斥锁可以传NULL
#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
PTHREAD_MUTEX_NORMAL 、PTHREAD_MUTEX_DEFAULT
默认为互斥锁 保证一次只有一个线程在执行 如果多次锁 会造成死锁
PTHREAD_MUTEX_ERRORCHECK
检错锁 同一个线程 加锁 第一次成功返回0 如果不解锁 后面再加锁则返回非0 不会造成死锁
PTHREAD_MUTEX_RECURSIVE
递归锁 同一个线程可以反复加锁 不会形成死锁 当然要全部解锁掉 才不会影像其他线程
static pthread_mutex_t mutex_lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_ERRORCHECK);
pthread_mutex_init(&mutex_lock, &attr);
pthread_mutexattr_destroy (&attr);
pthread_t thread;
pthread_create(&thread, NULL, methord, NULL);
pthread_t otherThread;
pthread_create(&otherThread, NULL, otherMethord, NULL);
void * methord() {
NSLog(@"线程1 ----------%@",[NSThread currentThread]);
int a = pthread_mutex_lock(&mutex_lock);
int b = pthread_mutex_lock(&mutex_lock);
NSLog(@"b------%d",b);
sleep(6);
NSLog(@"1号开始活动");
pthread_mutex_unlock(&mutex_lock);
pthread_mutex_unlock(&mutex_lock);
return 0;
}
void * otherMethord() {
NSLog(@"线程2 ----------%@",[NSThread currentThread]);
sleep(2);
pthread_mutex_lock(&mutex_lock);
NSLog(@"2号开始活动");
pthread_mutex_unlock(&mutex_lock);
return 0;
}
线程3 ----------<NSThread: 0x6000035bf5c0>{number = 4, name = (null)}
线程2 ----------<NSThread: 0x60000358cb00>{number = 5, name = (null)}
线程1 ----------<NSThread: 0x6000035bf380>{number = 3, name = (null)}
b------11
1号开始活动
2号开始活动