前言
锁是线程编程的基本同步工具。锁使您能够轻松地保护大部分代码,从而确保该代码的正确性。OS X 和 iOS 为所有应用程序类型提供了基本的互斥锁,Foundation 框架为特殊情况定义了一些额外的互斥锁变体。以下部分将展示如何使用其中几种锁。
NSLock (互斥锁)
NSLock 的使用
NSLock
为 App 实现了一个基本的互斥锁。NSLock
的接口实际上是由 NSLocking
协议定义的,协议定义了 lock
和 unlock
方法,可以像使用任何互斥锁一样使用这些方法来获取和释放锁。
除了标准的锁定行为之外,NSLock
类还添加了 tryLock
和 lockBeforeDate:
方法。该 tryLock
方法尝试获取锁,但如果锁不可用则不阻塞;相反,该方法只返回 NO
. 该 lockBeforeDate:
方法尝试获取锁,但如果在指定的时间限制内未获取锁,则解除线程阻塞(并返回)。
NSLock *theLock = [[NSLock alloc] init];
if ([theLock tryLock]) {
//do work
[theLock unlock];
}
NSLock 的实现
在 GNUStep 源码中查看 NSLock.m
文件,可以看到 NSLock 其实内部使用 pthread
中的 pthread_mutex_t
进行加解锁,pthread_mutex_t
类型为 PTHREAD_MUTEX_ERRORCHECK
。源码如下:
pthread_mutexattr_init(&attr_reporting);
pthread_mutexattr_settype(&attr_reporting, PTHREAD_MUTEX_ERRORCHECK);
- (id) init
{
if (nil != (self = [super init]))
{
if (0 != pthread_mutex_init(&_mutex, &attr_reporting))
{
DESTROY(self);
}
}
return self;
}
- (BOOL) tryLock
{
int err = pthread_mutex_trylock(&_mutex);
if (0 == err)
{
CHK(Hold)
return YES;
}
else
{
return NO;
}
}
- (BOOL) lockBeforeDate: (NSDate*)limit
{
do
{
int err = pthread_mutex_trylock(&_mutex);
if (0 == err)
{
CHK(Hold)
return YES;
}
if (EDEADLK == err)
{
(*_NSLock_error_handler)(self, _cmd, NO, @"deadlock");
}
sched_yield();
} while ([limit timeIntervalSinceNow] > 0);
return NO;
}
PTHREAD_MUTEX_ERRORCHECK 是检错锁,如果同一个线程请求同一个锁,则返回 `EDEADLK,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。
NSCondition (条件变量)
NSCondition 的使用
NSCondition
的对象实际上作为⼀个锁和⼀个线程检查器,可以像互斥锁一样锁定,然后像条件一样等待。锁主要为了当检测条件时保护数据源,执⾏条件引发的任务;线程检查器主要是根据条件决定是否继续运⾏线程,即线程是否被阻塞。
以下示例显示了如何使用条件锁来处理生产者-消费者问题。想象一个应用程序包含一个数据队列。生产者线程将数据添加到队列中,消费者线程从队列中提取数据。生产者不需要待特定的等条件,但它必须等待锁可用,这样它才能安全地将数据添加到队列中。
NSCondition *condition = [[ NSCondition alloc] init];
- (void)testConditon{
//创建生产-消费者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer];
});
}
}
- (void)producter{
[condition lock];
[products addObject:[[ NSObject alloc] init]];
NSLog (@ "produce a product" );
[condition signal];
[condition unlock];
}
- (void)consumenr{
[condition lock];
while ([products count] == 0) {
NSLog (@ "wait for products" );
[condition wait];
}
[products removeObjectAtIndex:0];
NSLog (@ "comsume a product" );
[condition unlock];
}
NSCondition 的实现
在 GNUStep 源码中可以看到 NSCondition
其内部同 NSLock
一样使用了检错锁类型的 pthread_mutex_t
,除此之外,其内部还有一个条件变量 pthread_cond_t
。源码如下:
- (id) init
{
if (nil != (self = [super init]))
{
if (0 != pthread_cond_init(&_condition, NULL))
{
DESTROY(self);
}
else if (0 != pthread_mutex_init(&_mutex, &attr_reporting))
{
pthread_cond_destroy(&_condition);
DESTROY(self);
}
}
return self;
}
- (void) signal
{
pthread_cond_signal(&_condition);
}
- (void) wait
{
pthread_cond_wait(&_condition, &_mutex);
}
NSRecursiveLock (递归锁)
使用 NSRecursiveLock
NSRecursiveLock
定义了一个锁,该锁可以被同一个线程多次获取而不会导致线程死锁。递归锁跟踪它被成功获取的次数,每次成功获取锁都必须通过相应的调用来解锁,只有当所有的锁和解锁调用都平衡时,锁才会真正释放,以便其他线程可以获取它。
顾名思义,这种类型的锁通常用于递归函数内部,以防止递归阻塞线程。可以类似地在非递归情况下使用它来调用语义要求它们也获取锁的函数。下面是一个简单的递归函数示例,它通过递归获取锁。如果没有 NSRecursiveLock
为此代码使用对象,则在再次调用该函数时线程将死锁。
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
void MyRecursiveFunction(int value)
{
[theLock lock];
if (value != 0)
{
--value;
MyRecursiveFunction(value);
}
[theLock unlock];
}
MyRecursiveFunction(5);
注意: 由于在所有锁调用与解锁调用平衡之前不会释放递归锁,因此应该仔细权衡使用性能锁的决定与潜在的性能影响。长时间持有任何锁可能会导致其他线程阻塞,直到递归完成。如果可以重写代码以消除递归或消除使用递归锁的需要,可能会获得更好的性能。
NSRecursiveLock 的实现
在 GNUStep 源码中可以看到 NSRecursiveLock
其内部使用的是递归锁类型的 pthread_mutex_t
,源码如下:
pthread_mutexattr_init(&attr_recursive);
pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE);
- (id) init
{
if (nil != (self = [super init]))
{
if (0 != pthread_mutex_init(&_mutex, &attr_recursive))
{
DESTROY(self);
}
}
return self;
}
NSConditionLock (条件变量)
使用 NSConditionLock
NSConditionLock
定义了可以用特定的值锁定和解锁的互斥锁。不应将这种类型的锁与 NSCondition
混淆。该行为与条件有些相似,但不同的是 NSConditionLock
内置了变量 condition 用于判断,而 NSCondition 需要外界判断发送信号。
NSConditionLock
对象响应的锁定和解锁方法可以任意组合使用。例如,可以将 lock
消息与 unlockWithCondition:
配合使用 或 lockWhenCondition:
与 unlock
配合使用。当然,后一种组合会解锁,但可能不会释放任何等待特定条件值的线程。
NSConditionLock condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];
while(true)
{
[condLock lock];
/* Add data to the queue. */
[condLock unlockWithCondition:HAS_DATA];
}
while (true)
{
[condLock lockWhenCondition:HAS_DATA];
/* Remove data from the queue. */
[condLock unlockWithCondition:(isEmpty ? NO_DATA : HAS_DATA)];
// Process the data locally.
}
NSConditionLock 的实现
在 GNUStep 源码中可以看到 NSConditionLock
其内部内置了一个 NSCondition 和 _condition_value,根据_condition_value 判断是否需要等待,源码如下:
- (id) initWithCondition: (NSInteger)value
{
if (nil != (self = [super init]))
{
if (nil == (_condition = [NSCondition new]))
{
DESTROY(self);
}
else
{
_condition_value = value;
[_condition setName:
[NSString stringWithFormat: @"condition-for-lock-%p", self]];
}
}
return self;
}
- (void) lockWhenCondition: (NSInteger)value
{
[_condition lock];
while (value != _condition_value)
{
[_condition wait];
}
}
- (void) unlockWithCondition: (NSInteger)value
{
_condition_value = value;
[_condition broadcast];
[_condition unlock];
}
网友评论