iOS源码解析:多线程 线程同步
起始票数是1000,第一个卖票的站点先读取的票的余额,过了一会第二个卖票的站点也读取了票的余额,然后第一个站点卖出了一张票,因此把票数余额修改为了999,过了一会第二个站点也卖了一张票,把票数余额修改为了999,这样一来,票就永远卖不完了。
我们用代码实现一下卖票的过程:
@property (nonatomic, assign)int ticketsCount; - (void)saleTicket{ //这里使用oldTicketsCount主要是模拟整个读取票数然后卖票的过程,睡眠0.2使效果更明显 int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"最后还剩的票数%d 线程%@", oldTicketsCount, [NSThread currentThread]); } - (void)saleTickets{ self.ticketsCount = 15; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; } }); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; } }); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; } }); }
打印结果:
2018-09-26 15:12:52.746209+0800 TEST[10226:312194] 最后还剩的票数13 线程 {number = 5, name = (null)}2018-09-26 15:12:52.746209+0800 TEST[10226:312193] 最后还剩的票数14 线程 {number = 4, name = (null)}2018-09-26 15:12:52.746245+0800 TEST[10226:312195] 最后还剩的票数14 线程 {number = 3, name = (null)}2018-09-26 15:12:52.746414+0800 TEST[10226:312194] 最后还剩的票数12 线程 {number = 5, name = (null)}2018-09-26 15:12:52.746552+0800 TEST[10226:312193] 最后还剩的票数11 线程 {number = 4, name = (null)}2018-09-26 15:12:52.746650+0800 TEST[10226:312195] 最后还剩的票数10 线程 {number = 3, name = (null)}2018-09-26 15:12:52.746707+0800 TEST[10226:312194] 最后还剩的票数9 线程 {number = 5, name = (null)}2018-09-26 15:12:52.746730+0800 TEST[10226:312193] 最后还剩的票数8 线程 {number = 4, name = (null)}2018-09-26 15:12:52.746913+0800 TEST[10226:312195] 最后还剩的票数7 线程 {number = 3, name = (null)}2018-09-26 15:12:52.747049+0800 TEST[10226:312194] 最后还剩的票数6 线程 {number = 5, name = (null)}2018-09-26 15:12:52.747301+0800 TEST[10226:312193] 最后还剩的票数5 线程 {number = 4, name = (null)}2018-09-26 15:12:52.747861+0800 TEST[10226:312194] 最后还剩的票数4 线程 {number = 5, name = (null)}2018-09-26 15:12:52.747861+0800 TEST[10226:312195] 最后还剩的票数4 线程 {number = 3, name = (null)}2018-09-26 15:12:52.748157+0800 TEST[10226:312193] 最后还剩的票数3 线程 {number = 4, name = (null)}2018-09-26 15:12:52.749157+0800 TEST[10226:312195] 最后还剩的票数2 线程 {number = 3, name = (null)}
可以看到产生了混乱,最后剩余的票数并不为0。
然后继续用代码实现取钱存钱的过程
@property (nonatomic, assign)int money; - (void)moneyTest{ self.money = 100; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); //存钱的线程 dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { [self saveMoney]; } }); //取钱的线程 dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { [self drawmoney]; } }); }//存钱- (void)saveMoney{ int oldMoney = self.money; sleep(.2); oldMoney += 50; self.money = oldMoney; NSLog(@"存50 还剩%d元 - %@", oldMoney, [NSThread currentThread]); }//取钱- (void)drawmoney{ int oldMoney = self.money; sleep(.2); oldMoney -= 20; self.money = oldMoney; NSLog(@"取20 还剩%d元 - %@", oldMoney, [NSThread currentThread]); }
打印结果:
2018-09-26 15:27:13.265434+0800 TEST[10568:324343] 取20 还剩80元 - {number = 4, name = (null)}2018-09-26 15:27:13.265459+0800 TEST[10568:324337] 存50 还剩150元 - {number = 3, name = (null)}2018-09-26 15:27:13.265587+0800 TEST[10568:324337] 存50 还剩180元 - {number = 3, name = (null)}2018-09-26 15:27:13.265589+0800 TEST[10568:324343] 取20 还剩130元 - {number = 4, name = (null)}2018-09-26 15:27:13.265685+0800 TEST[10568:324337] 存50 还剩230元 - {number = 3, name = (null)}2018-09-26 15:27:13.265693+0800 TEST[10568:324343] 取20 还剩210元 - {number = 4, name = (null)}2018-09-26 15:27:13.265771+0800 TEST[10568:324337] 存50 还剩260元 - {number = 3, name = (null)}2018-09-26 15:27:13.265853+0800 TEST[10568:324343] 取20 还剩240元 - {number = 4, name = (null)}2018-09-26 15:27:13.266059+0800 TEST[10568:324337] 存50 还剩290元 - {number = 3, name = (null)}2018-09-26 15:27:13.266210+0800 TEST[10568:324343] 取20 还剩270元 - {number = 4, name = (null)}2018-09-26 15:27:13.266343+0800 TEST[10568:324337] 存50 还剩320元 - {number = 3, name = (null)}2018-09-26 15:27:13.266485+0800 TEST[10568:324343] 取20 还剩300元 - {number = 4, name = (null)}2018-09-26 15:27:13.266667+0800 TEST[10568:324337] 存50 还剩350元 - {number = 3, name = (null)}2018-09-26 15:27:13.266844+0800 TEST[10568:324343] 取20 还剩330元 - {number = 4, name = (null)}2018-09-26 15:27:13.267284+0800 TEST[10568:324337] 存50 还剩380元 - {number = 3, name = (null)}2018-09-26 15:27:13.267373+0800 TEST[10568:324343] 取20 还剩360元 - {number = 4, name = (null)}2018-09-26 15:27:13.267496+0800 TEST[10568:324337] 存50 还剩410元 - {number = 3, name = (null)}2018-09-26 15:27:13.267866+0800 TEST[10568:324343] 取20 还剩390元 - {number = 4, name = (null)}2018-09-26 15:27:13.268062+0800 TEST[10568:324337] 存50 还剩440元 - {number = 3, name = (null)}2018-09-26 15:27:13.268578+0800 TEST[10568:324343] 取20 还剩420元 - {number = 4, name = (null)}
从最后剩余的钱数来看就完全不对,数据发生了明显的混乱。
那么多线程的安全隐患怎么解决呢? 解决方案就是使用线程同步技术,常见的线程同步技术是加锁。
iOS中的线程同步方案有下面这些:
OSSpinLock
-
OSSpinlock叫做”自旋锁”,等待锁的线程会处于忙等状态,一直占用CPU资源
-
目前已经不再安全,可能会出现优先级反转的问题,即如果等待锁的线程优先级较高,它会一直占用着CPU的资源,优先级低的线程就无法释放锁。
关于OSSpinLock的API:
//初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁看,如果需要等待就不加锁,直接返回false,如果不需要等待就加锁,返回true。 bool result = OSSpinLockTry(&lock); //加锁 OSSpinLockLock(&lock); //解锁 OSSpinLockUnlock(&lock);
下面我们使用OSSpinLock来解决卖票的资源争夺的问题:
- (void)saleTicket{ //加锁 OSSpinLockLock(&_lock); //这里使用oldTicketsCount主要是模拟整个读取票数然后卖票的过程,睡眠0.2使效果更明显 int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"最后还剩的票数%d 线程%@", oldTicketsCount, [NSThread currentThread]); //解锁 OSSpinLockUnlock(&_lock); }
我们看一下打印结果:
2018-09-26 15:59:05.225340+0800 TEST[11218:345833] 最后还剩的票数14 线程 {number = 3, name = (null)}2018-09-26 15:59:05.225623+0800 TEST[11218:345833] 最后还剩的票数13 线程 {number = 3, name = (null)}2018-09-26 15:59:05.225799+0800 TEST[11218:345833] 最后还剩的票数12 线程 {number = 3, name = (null)}2018-09-26 15:59:05.225946+0800 TEST[11218:345833] 最后还剩的票数11 线程 {number = 3, name = (null)}2018-09-26 15:59:05.226248+0800 TEST[11218:345833] 最后还剩的票数10 线程 {number = 3, name = (null)}2018-09-26 15:59:05.227334+0800 TEST[11218:345826] 最后还剩的票数9 线程 {number = 4, name = (null)}2018-09-26 15:59:05.227480+0800 TEST[11218:345826] 最后还剩的票数8 线程 {number = 4, name = (null)}2018-09-26 15:59:05.227709+0800 TEST[11218:345826] 最后还剩的票数7 线程 {number = 4, name = (null)}2018-09-26 15:59:05.228151+0800 TEST[11218:345826] 最后还剩的票数6 线程 {number = 4, name = (null)}2018-09-26 15:59:05.233128+0800 TEST[11218:345826] 最后还剩的票数5 线程 {number = 4, name = (null)}2018-09-26 15:59:05.237517+0800 TEST[11218:345827] 最后还剩的票数4 线程 {number = 5, name = (null)}2018-09-26 15:59:05.238065+0800 TEST[11218:345827] 最后还剩的票数3 线程 {number = 5, name = (null)}2018-09-26 15:59:05.238499+0800 TEST[11218:345827] 最后还剩的票数2 线程 {number = 5, name = (null)}2018-09-26 15:59:05.239221+0800 TEST[11218:345827] 最后还剩的票数1 线程 {number = 5, name = (null)}2018-09-26 15:59:05.239897+0800 TEST[11218:345827] 最后还剩的票数0 线程 {number = 5, name = (null)}
可以看到现在的输出没有任何问题了。
线程加锁的原理就是,当某一个线程首次访问资源时,对该资源加锁,当另外一个线程要访问该资源时首先判断锁有没有加上,没有的话就加锁然后访问资源,如果锁已经加上了,那么就会等待,等待锁打开。
下面再用OSSpinLock来完成存钱取钱的加锁:
//存钱- (void)saveMoney{ OSSpinLockLock(&_lock); int oldMoney = self.money; sleep(.2); oldMoney += 50; self.money = oldMoney; NSLog(@"存50 还剩%d元 - %@", oldMoney, [NSThread currentThread]); OSSpinLockUnlock(&_lock); }//取钱- (void)drawmoney{ OSSpinLockLock(&_lock); int oldMoney = self.money; sleep(.2); oldMoney -= 20; self.money = oldMoney; NSLog(@"取20 还剩%d元 - %@", oldMoney, [NSThread currentThread]); OSSpinLockUnlock(&_lock); }
看一下打印结果:
2018-09-26 16:45:14.317794+0800 TEST[12223:379269] 存50 还剩150元 - {number = 3, name = (null)}2018-09-26 16:45:14.317953+0800 TEST[12223:379269] 存50 还剩200元 - {number = 3, name = (null)}2018-09-26 16:45:14.318071+0800 TEST[12223:379269] 存50 还剩250元 - {number = 3, name = (null)}2018-09-26 16:45:14.318182+0800 TEST[12223:379269] 存50 还剩300元 - {number = 3, name = (null)}2018-09-26 16:45:14.318374+0800 TEST[12223:379269] 存50 还剩350元 - {number = 3, name = (null)}2018-09-26 16:45:14.318500+0800 TEST[12223:379269] 存50 还剩400元 - {number = 3, name = (null)}2018-09-26 16:45:14.318587+0800 TEST[12223:379269] 存50 还剩450元 - {number = 3, name = (null)}2018-09-26 16:45:14.318689+0800 TEST[12223:379269] 存50 还剩500元 - {number = 3, name = (null)}2018-09-26 16:45:14.318823+0800 TEST[12223:379269] 存50 还剩550元 - {number = 3, name = (null)}2018-09-26 16:45:14.319047+0800 TEST[12223:379269] 存50 还剩600元 - {number = 3, name = (null)}2018-09-26 16:45:14.320129+0800 TEST[12223:379270] 取20 还剩580元 - {number = 4, name = (null)}2018-09-26 16:45:14.320242+0800 TEST[12223:379270] 取20 还剩560元 - {number = 4, name = (null)}2018-09-26 16:45:14.320347+0800 TEST[12223:379270] 取20 还剩540元 - {number = 4, name = (null)}2018-09-26 16:45:14.320459+0800 TEST[12223:379270] 取20 还剩520元 - {number = 4, name = (null)}2018-09-26 16:45:14.320588+0800 TEST[12223:379270] 取20 还剩500元 - {number = 4, name = (null)}2018-09-26 16:45:14.320693+0800 TEST[12223:379270] 取20 还剩480元 - {number = 4, name = (null)}2018-09-26 16:45:14.320900+0800 TEST[12223:379270] 取20 还剩460元 - {number = 4, name = (null)}2018-09-26 16:45:14.321222+0800 TEST[12223:379270] 取20 还剩440元 - {number = 4, name = (null)}2018-09-26 16:45:14.321331+0800 TEST[12223:379270] 取20 还剩420元 - {number = 4, name = (null)}2018-09-26 16:45:14.321548+0800 TEST[12223:379270] 取20 还剩400元 - {number = 4, name = (null)}
OSSpinLock目前已经不能使用的原因
OSSpinLock目前不建议使用的原因主要是会出现优先级反转。假设有3个线程线程1,线程2,线程3,那么如果这三个线程的优先级是一样的,那么CPU会平均的分配时间给这3个线程,比如首先给线程1 10ms去处理事件,然后给线程2 10ms去处理事件,再给线程3 10ms去处理事件,这样把时间切成碎片去处理,给人的感觉就像是三个线程一起在处理事件。 但是当三个线程的优先级不一样的时候就会出现一些问题了,加入线程1的优先级较高,线程2的优先级较低,线程2首先访问资源,首先给资源加锁,这个时候线程1再去访问资源的时候,检查到锁已经加上了,所以就会在外面忙等,由于优先级很高,所以CPU分配给线程1的时间很多,分配给线程2的时间很少,这样会导致线程2没有时间来处理事件,锁很久不能打开,线程1长时间在外面等着,有点类似于死锁。
为了更加直管的观察各种锁,现在把存钱取钱卖票的业务逻辑抽到一个基类中,名为BaseDemo,主要代码如下:
@interface BaseDemo() @property (nonatomic, assign)int money;@property (nonatomic, assign)int ticketsCount;@end@implementation BaseDemo- (void)moneyTest{ self.money = 100; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); //存钱的线程 dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { [self saveMoney]; } }); //取钱的线程 dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { [self drawMoney]; } }); }//存钱- (void)saveMoney{ int oldMoney = self.money; sleep(.2); oldMoney += 50; self.money = oldMoney; NSLog(@"存50 还剩%d元 - %@", oldMoney, [NSThread currentThread]); }//取钱- (void)drawMoney{ int oldMoney = self.money; sleep(.2); oldMoney -= 20; self.money = oldMoney; NSLog(@"取20 还剩%d元 - %@", oldMoney, [NSThread currentThread]); } - (void)saleTicket{ //这里使用oldTicketsCount主要是模拟整个读取票数然后卖票的过程,睡眠0.2使效果更明显 int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"最后还剩的票数%d 线程%@", oldTicketsCount, [NSThread currentThread]); } - (void)ticketTest{ self.ticketsCount = 15; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; } }); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; } }); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; } }); }@end
然后例如要演示OSSpinLock锁,我们可以创建一个类名为OSSPinLockDemo继承自BaseDemo,然后在其中实现存钱取钱卖票:
//OSSpinLockDemo.m- (instancetype)init{ if (self = [super init]) { self.moneyLock = OS_SPINLOCK_INIT; self.ticketlock = OS_SPINLOCK_INIT; } return self; } - (void)saveMoney{ OSSpinLockLock(&_moneyLock); [super saveMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)drawMoney{ OSSpinLockLock(&_moneyLock); [super drawMoney]; OSSpinLockUnlock(&_moneyLock); } - (void)saleTicket{ OSSpinLockLock(&_ticketlock); [super saleTicket]; OSSpinLockUnlock(&_ticketlock); }
在主函数中这样调用:
OSSpimLinkDemo *demo = [[OSSpimLinkDemo alloc] init]; [demo ticketTest];
这样做的好处是,我们可以更加专注于加锁的过程,而不用去管业务逻辑,每学习一个锁,就写一个子类。
os_unfair_lock
下面学习os_unfair_lock这种锁。
os_unfair_lock用于取代不安全的OSSpinLock,从iOS10开始才支持。
从底层调用看,等待os_unfair_lock锁的线程处于休眠状态,并非忙等。
需要导入头文件
os_unfair_lock的基本API如下:
//初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁 os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);
接下来我们可以写一个子类OSUnFairLockDemo类,然后在这个类中重写卖票方法如下:
//OSUnFairLockDemo.m- (instancetype)init{ if (self = [super init]) { self.ticketlock = OS_UNFAIR_LOCK_INIT; } return self; } - (void)saleTicket{ os_unfair_lock_lock(&_ticketlock); [super saleTicket]; os_unfair_lock_unlock(&_ticketlock); }
然后看一下输出结果:
2018-09-27 16:06:24.453628+0800 TEST[26669:857080] 最后还剩的票数14 线程 {number = 3, name = (null)}2018-09-27 16:06:24.453777+0800 TEST[26669:857080] 最后还剩的票数13 线程 {number = 3, name = (null)}2018-09-27 16:06:24.453893+0800 TEST[26669:857080] 最后还剩的票数12 线程 {number = 3, name = (null)}2018-09-27 16:06:24.453988+0800 TEST[26669:857080] 最后还剩的票数11 线程 {number = 3, name = (null)}2018-09-27 16:06:24.454108+0800 TEST[26669:857080] 最后还剩的票数10 线程 {number = 3, name = (null)}2018-09-27 16:06:24.454235+0800 TEST[26669:857082] 最后还剩的票数9 线程 {number = 4, name = (null)}2018-09-27 16:06:24.454323+0800 TEST[26669:857082] 最后还剩的票数8 线程 {number = 4, name = (null)}2018-09-27 16:06:24.454421+0800 TEST[26669:857082] 最后还剩的票数7 线程 {number = 4, name = (null)}2018-09-27 16:06:24.454513+0800 TEST[26669:857082] 最后还剩的票数6 线程 {number = 4, name = (null)}2018-09-27 16:06:24.454600+0800 TEST[26669:857082] 最后还剩的票数5 线程 {number = 4, name = (null)}2018-09-27 16:06:24.454712+0800 TEST[26669:857083] 最后还剩的票数4 线程 {number = 5, name = (null)}2018-09-27 16:06:24.454840+0800 TEST[26669:857083] 最后还剩的票数3 线程 {number = 5, name = (null)}2018-09-27 16:06:24.458107+0800 TEST[26669:857083] 最后还剩的票数2 线程 {number = 5, name = (null)}2018-09-27 16:06:24.458217+0800 TEST[26669:857083] 最后还剩的票数1 线程 {number = 5, name = (null)}2018-09-27 16:06:24.458307+0800 TEST[26669:857083] 最后还剩的票数0 线程 {number = 5, name = (null)}
可以看到,数据没有发生混乱。
pthread_mutex
mutex叫做”互斥锁”,等待锁的线程会处于休眠状态。
需要导入头文件
与之相关的API有:
//初始化锁的属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); //初始化锁 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); /* *Mutex type attributes */ #define PTHREAD_MUTEX_NORMAL 0 #define PTHREAD_MUTEX_ERRORCHECK 1 #define PTHREAD_MUTEX_RECURSIVE 2 #define PTHREAD_MUTEX_DEFAULT
我们可以创建一个子类MutexDemo,然后重写卖票方法:
//MutexDemo.m- (instancetype)init{ if (self = [super init]) { //初始化锁的属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL ); //初始化锁 pthread_mutex_t mutex; pthread_mutex_init(&_ticketLock, &attr); pthread_mutexattr_destroy(&attr); } return self; } - (void)saleTicket{ pthread_mutex_lock(&_ticketLock); [super saleTicket]; pthread_mutex_unlock(&_ticketLock); }
打印出来数据没有发生混乱。
由一个问题引出递归锁
创建一个子类MutexDemo2,在这个类中像MutexDemo一样,创建pthread_Mutex类型的互斥锁:
- (instancetype)init{ if (self = [super init]) { //初始化锁的属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); //通过属性确定创建的是互斥锁 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); //初始化锁 pthread_mutex_init(&_ticketLock, &attr); pthread_mutexattr_destroy(&attr); } return self; } - (void)otherTest{ pthread_mutex_lock(&_ticketLock); NSLog(@"%s", __func__); [self otherTest2]; pthread_mutex_unlock(&_ticketLock); } - (void)otherTest2{ pthread_mutex_lock(&_ticketLock); NSLog(@"%s", __func__); pthread_mutex_unlock(&_ticketLock); }
然后创建实例对象去调用otherTest这个方法:
MutexDemo2 *demo = [[MutexDemo2 alloc] init]; [demo otherTest];
我们看一下运行效果:
2018-09-27 18:44:56.627062+0800 TEST[30733:965088] -[MutexDemo2 otherTest]
只打印了otherTest方法中的输出,而没有打印otherTest2方法中的输出,这是什么原因呢?
原因在于,执行otherTest时,将ticketLock这个锁锁上了,锁上后去调用otherTest2方法,在otherTest2方法中,检查到锁锁上了,所以就会一直在碗面等,等这个锁打开,而锁打开又依赖于otherTest2方法执行完成,这样代码就没法执行下去了。
这个方法其实很好解决,由于是两个不同的方法,所以这两个方法使用不同的锁就行了,那么如果是递归呢?也就是otherTest里面调用otherTest呢?这样就不可能使用两把锁了,那这个问题又该怎么解决呢?
这个时候递归锁就派上用场了
递归锁:允许同一个线程对一把锁进行重复加锁
我们可以把pthread_Mutex锁的属性改为递归锁:
//改变锁的属性为递归锁 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
- (void)otherTest{ //第二次调用到这个地方的时候,可以再给ticketLock这个锁加一次锁 pthread_mutex_lock(&_ticketLock); NSLog(@"%s", __func__); [self otherTest]; //在解锁的时候相对应也会解两次锁 pthread_mutex_unlock(&_ticketLock); }
这样就能解决这个递归死锁的问题。
从汇编实现来看自旋锁是忙等,互斥锁是休眠
我们在BaseDemo这个基类中修改ticketTest这个方法的实现,创建十条线程来调用saleTicket方法:
- (void)ticketTest{ self.ticketsCount = 15; for (int i = 0; i < 15; i++) { [[[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil] start]; }
然后在saleTicket这个方法里面设置睡眠时间为600s,这样一来,当第一条线程进入saleTicket方法后,由于休眠600s,所以锁在600s内会被锁着,当第二条线程调用saleTicket方法时,就会在外面等待:
- (void)saleTicket{ //睡眠600s是保证第二条线程进来时锁是被锁着,于是w要在外面等待 int oldTicketsCount = self.ticketsCount; sleep(600); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"最后还剩的票数%d 线程%@", oldTicketsCount, [NSThread currentThread]); }