应用程序里面多个线程的存在引发了多个执行线程安全访问资源的潜在问题。两个线程同时修改同一资源有可能以意想不到的方式互相干扰。
iOS 提供了你可以使用的多个同步工具,从提供互斥访问你程序的有序的事件的工具等。以下个部分介绍了这些工具和如何在代码中使用他们来影响安全的访问程序的资源。
我们通过同一个例子来说明这些锁,当两个线程同时操作一个可变数组时,一个线程添加数据,一个线程删除数据,类似一个生产消费者模式,就会存在线程安全问题;
使用POSIX互斥锁
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL);
__block NSMutableArray* products = [[NSMutableArray alloc]init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (YES) {
pthread_mutex_lock(&mutex);
[products addObject:@"product"];
pthread_mutex_unlock(&mutex);
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (YES) {
pthread_mutex_lock(&mutex);
if (products.count != 0) {
[products removeObjectAtIndex:0];
}
pthread_mutex_unlock(&mutex);
}
});
使用NSLock类
在 Cocoa 程序中 NSLock 中实现了一个简单的互斥锁。所有锁(包括NSLock)的接口实际上都是通过NSLocking协议定义的,它定义了lock和unlock方法。你使用这些方法来获取和释放该锁。
除了标准的锁行为,NSLock类还增加了tryLock和lockBeforeDate:方法。方法tryLock试图获取一个锁,但是如果锁不可用的时候,它不会阻塞线程。相反,它只是返回NO。而lockBeforeDate:方法试图获取一个锁,但是如果锁没有在规定的时间内被获得,它会让线程从阻塞状态变为非阻塞状态(或者返回NO)。
NSLock* lock = [[NSLock alloc]init];
__block NSMutableArray* products = [[NSMutableArray alloc]init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (YES) {
[lock lock];
[products addObject:@"product"];
[lock unlock];
/*避免阻塞*/
/*
if([lock tryLock])
{
[products addObject:@"product"];
[lock unlock];
}
*/
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (YES) {
[lock lock];
if (products.count != 0) {
[products removeObjectAtIndex:0];
}
[lock unlock];
}
});
使用@synchronized指令
@synchronized指令是在Objective-C代码中创建一个互斥锁非常方便的方法。@synchronized指令做和其他互斥锁一样的工作(它防止不同的线程在同一时间获取同一个锁)。然而在这种情况下,你不需要直接创建一个互斥锁或锁对象。相反,你只需要简单的使用Objective-C对象作为锁的令牌
__block NSMutableArray* products = [[NSMutableArray alloc]init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(products)
{
while (YES) {
[products addObject:@"product"];
}
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(products)
{
while (YES) {
if (products.count != 0) {
[products removeLastObject];
}
}
}
});
使用单例的时候
@interface SingleObject : NSObject
@property (strong, nonatomic) NSMutableArray *products;
+ (instancetype)sharedInstance;
- (void)addmethod;
- (void)removemethod;
@end
@implementation SingleObject
+ (instancetype)sharedInstance
{
static SingleObject *sharedInstance_ = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance_ = [[[self class] alloc] init];
});
return sharedInstance_;
}
- (id)init
{
self = [super init];
if (self)
{
self.products = [[NSMutableArray alloc]init];
}
return self;
}
- (void)addmethod
{
[self.products addObject:@"product"];
}
- (void)removemethod
{
if (self.products.count != 0) {
[self.products removeObjectAtIndex:0];
}
}
@end
SingleObject *object = [SingleObject sharedInstance];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(object)
{
while (YES) {
[object addmethod];
}
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(object)
{
while (YES) {
[object removemethod];
}
}
});
使用NSConditionLock对象
NSConditionLock对象定义了一个互斥锁,可以使用特定值来锁住和解锁。不要把该类型的锁和条件(NSCondition)混淆了。它的行为和条件有点类似,但是它们的实现非常不同。
#define HAS_DATA 1
#define NO_DATA 0
NSConditionLock* condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];
NSMutableArray* products = [[NSMutableArray alloc]init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while(1)
{
[condLock lock];
[products addObject:@"product"];
[condLock unlockWithCondition:HAS_DATA];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while(1)
{
[condLock lockWhenCondition:HAS_DATA];
[products removeLastObject];
[condLock unlockWithCondition:(products.count == 0 ? NO_DATA : HAS_DATA)];
}
});
问题:以上的消费者都是在线程中轮询来取数据,这样会消耗大量cpu资源,如果可以在有数据的时候自己被告知,没有数据的时候空闲等待就好了,这时我们可以使用条件来完成,有以下两种方式(使用POSIX条件或者使用NSCondition类)。
使用NSCondition类
NSCondition类提供了和POSIX条件相同的语义,但是它把锁和条件数据结构封装在一个单一对象里面。结果是一个你可以像互斥锁那样使用的对象,然后等待特定条件。
消费者取得锁,取产品,如果没有,则wait,这时会释放锁,直到有线程唤醒它去消费产品
生产者制造产品,首先也是要取得锁,然后生产,再发signal,这样可唤醒wait的消费者
NSMutableArray* products = [[NSMutableArray alloc]init];
NSCondition* condition = [[NSCondition alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 消费者
while(1)
{
[condition lock];
while (products.count < 10) {
[condition wait];
}
[products removeObjectAtIndex:0];
[condition unlock];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 生产者
while(1)
{
[condition lock];
[products addObject:@"product"];
[condition signal];
[condition unlock];
}
});
使用POSIX条件
pthread_mutex_t mutex;
pthread_cond_t condition;
Boolean ready_to_go = true;
void MyCondInitFunction()
{
pthread_mutex_init(&mutex);
pthread_cond_init(&condition, NULL);
}
void MyWaitOnConditionFunction()
{
// Lock the mutex.
pthread_mutex_lock(&mutex);
// If the predicate is already set, then the while loop is bypassed;
// otherwise, the thread sleeps until the predicate is set.
while(ready_to_go == false)
{
pthread_cond_wait(&condition, &mutex);
}
// Do work. (The mutex should stay locked.)
// Reset the predicate and release the mutex.
ready_to_go = false;
pthread_mutex_unlock(&mutex);
}
void SignalThreadUsingCondition()
{
// At this point, there should be work for the other thread to do.
pthread_mutex_lock(&mutex);
ready_to_go = true;
// Signal the other thread to begin work.
pthread_cond_signal(&condition);
pthread_mutex_unlock(&mutex);
}
-参考《多线程编程指南》