在 上篇 博客我们讲到 多线程访问数据时会出现数据不正常的问题。现在我们来讲如何安全而且高效的访问数据。
方案一:
我们使用 @synchronized 方式 来解决 假设在 controller 中 有如下的属性
@property (nonatomic,assign)NSInteger synchronizedTickets;
- (void)syncronized
{
self.synchronizedTickets=10;
_queueTickets=10;
count=10;
//开启多个线程,模拟售票员售票
self.thread10=[[NSThread alloc]initWithTarget:self selector:@selector(sycronizedSell) object:nil];
self.thread10.name=@"售票员10";
self.thread11=[[NSThread alloc]initWithTarget:self selector:@selector(sycronizedSell) object:nil];
self.thread11.name=@"售票员11";
self.thread12=[[NSThread alloc]initWithTarget:self selector:@selector(sycronizedSell) object:nil];
self.thread12.name=@"售票员12";
[self.thread10 start];
[self.thread11 start];
[self.thread12 start];
}
在 sycronizedSell 中 我们这样写
while (true) {
// 从代码来看应该 应该不可能出现 负数的 票数
// NSLog(@"start ticket %ld---",(long)self.leftTicketsCount);
@synchronized (self) {
if (self.synchronizedTickets>0) { // 读取
// NSLog(@"valid ticke %ld",self.newLefTicketsCount);
[NSThread sleepForTimeInterval:1]; // 这句话 是关键 睡眠 执行 笔者认为 如果
self.synchronizedTickets--;
NSLog(@"synchronizedTickets thread:%@ ----> %ld",[[NSThread currentThread] name],self.synchronizedTickets);
}else{
break;
}
}
第二中 方案 使用 NSLock
NSLock *_lock; 是一个全局的外部变量
更改 sycronizedSell 方法为
while (true) {
// 从代码来看应该 应该不可能出现 负数的 票数
// NSLog(@"start ticket %ld---",(long)self.leftTicketsCount);
[_lock lock];
if (self.synchronizedTickets>0) { // 读取
// NSLog(@"valid ticke %ld",self.newLefTicketsCount);
[NSThread sleepForTimeInterval:1]; // 这句话 是关键 睡眠 执行 笔者认为 如果
self.synchronizedTickets--;
NSLog(@"synchronizedTickets thread:%@ ----> %ld",[[NSThread currentThread] name],self.synchronizedTickets);
}else{
break;
}
[_lock unlock];
}
运行效果如下:
第三种 使用 GCD
思路是: 使用一个并行队列 多线程设置的时候 其他读取的操作都停止。读取的时候要等待上一个读取完
__block NSInteger count; // 串行队列
dispatch_queue_t _syncQueue;
_asyncQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSInteger)queueTickets
{
__block NSInteger tickets = 0;
dispatch_sync(_asyncQueue, ^{
tickets=_queueTickets;
});
return tickets;
}
- (void)setQueueTickets:(NSInteger)queueTickets
{
//
dispatch_barrier_async(_asyncQueue, ^{
_queueTickets=queueTickets;
});
}
更改 sycronizedSell while 循环里面的 代码为
if ([self queueTickets]>0) { // 读取
// NSLog(@"valid ticke %ld",self.newLefTicketsCount);
[NSThread sleepForTimeInterval:1]; // 这句话 是关键 睡眠 执行 笔者认为 如果
[self setQueueTickets:([self queueTickets]-1)];
NSLog(@"synchronizedTickets thread:%@ ----> %ld",[[NSThread currentThread] name],[self queueTickets]);
}else{
break;
}
这里使用到了GCD 中线程栅栏的功能。
我们发现 时间上面比起 上面的这两种方法都要快 而且没有出现0 的情况推荐使用。
第四种 方案 : 使用 串行队列
__block NSInteger count; // 串行队列 外部的全局变量
dispatch_queue_t _syncQueue;
_syncQueue=dispatch_queue_create("syncQueue", DISPATCH_QUEUE_SERIAL);
- (void)setCount:(NSInteger)newcount{
dispatch_sync(_syncQueue, ^{
count = newcount;
});
}
- (NSInteger)count{
__block NSInteger localCount;
dispatch_sync(_syncQueue, ^{
localCount = count;
});
return localCount;
}
替换 while 循环为
if (count>0) { // 读取
// NSLog(@"valid ticke %ld",self.newLefTicketsCount);
[NSThread sleepForTimeInterval:1]; // 这句话 是关键 睡眠 执行 笔者认为 如果
[self setCount:([self count]-1)];
NSLog(@"synchronizedTickets thread:%@ ----> %ld",[[NSThread currentThread] name],[self count]);
}else{
break;
}
效果如下: