- 多线程为我们带来了很大便利,也提高了程序的执行效率,但同时也带来了
Data race
(当至少有两个线程同时访问同一个变量,而且至少其中有一个是写操作时,就发生了Data race
)。这时就要利用一些同步机制来确保数据的准确性,锁
就是同步机制中的一种。
一、各种锁
-
@synchronized
关键字加锁 -
NSLock
对象锁 -
NSCondition
条件锁1 -
NSConditionLock
条件锁2 -
NSRecursiveLock
递归锁 -
pthread_mutex
互斥锁(C语言) -
pthread_mutex(recursive)
互斥锁2(递归锁一种类似NSConditionLock) -
dispath_semaphore
信号量实现加锁(GCD) -
OSSpinlock
自旋锁(iOS10以后被废弃,因其不安全,有可能造成死锁) -
os_unfair_lock
自旋锁(iOS之后才可以使用,代替OSSpinlock
的方案)
二、性能比对图
性能测试Demo的GitHub地址:https://github.com/Yjunjie/MultithreadingAndLock/tree/master
参考链接:https://www.jianshu.com/p/c9c5bc68449d
总体来说:
OSSpinLock
和dispatch_semaphore
的效率远远高于其他。
@synchronized
和NSConditionLock
效率较差。临界区
:指的是一块对公共资源进行访问的代码,并非一种机制或是算法。
三、详细介绍
- 自旋锁:
如果共享数据被其他线程加锁,那么当前线程会以死循环的方式等待解锁,一旦访问的资源被解锁,则等待线程就会立即执行。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
1、OSSpinLock(不安全,已废弃)
可能造成死锁的原因:
有可能在优先级比较低的线程里对共享资源进行加锁了,然后高优先级的线程抢占了低优先级的调用CPU时间,导致高优先级的线程一直在等待低优先级的线程释放锁,然而低优先级根本没法抢占高优先级的CPU时间。
这种情况我们称作 优先级倒转。
OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
...
OSSpinLockUnlock(&lock);
2、os_unfair_lock
os_unfair_lock 是苹果官方推荐的替换OSSpinLock的方案,但是它在iOS10.0以上的系统才可以调用。
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);
- 互斥锁(Mutex):
如果共享资源已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。
互斥锁不会同时被两个不同的线程同时得到。也就是如果是当前线程加的锁,别的线程是没有办法获取这个锁,也就没有办法对他进行解锁。
1、NSLock
Foundation框架中以对象形式暴露给开发者的一种锁,在AFNetworking的AFURLSessionManager.m中应用如下:
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
...
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
...
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
...
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
2、pthread_mutex
实际项目中: 在YYKit的YYMemoryCach中可以看到
- (instancetype)init {
...
pthread_mutex_init(&_lock, NULL);
...
}
- (void)_trimToCost:(NSUInteger)costLimit {
BOOL finish = NO;
pthread_mutex_lock(&_lock);
if (costLimit == 0) {
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCost <= costLimit) {
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru->_totalCost > costLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
usleep(10 * 1000); //10 ms
}
}
...
}
3、@synchronized:
实际项目中:AFNetworking中 isNetworkActivityOccurring属性的getter方法
- (BOOL)isNetworkActivityOccurring {
@synchronized(self) {
return self.activityCount > 0;
}
}
- 条件锁:
当进程的某些资源要求不满足时就进入休眠等待,也就是锁住了。直到满足条件后,条件锁打开,进程继续运行。
1、NSCondition
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
遵循NSLocking
协议,使用的时候同样是lock
,unlock
加解锁,wait
是傻等,waitUntilDate:
方法是等一会,都会阻塞线程,signal
是唤起一个在等待的线程,broadcast
是广播全部唤起。
NSCondition *lock = [[NSCondition alloc] init];
//Son 线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
while (No Money) {
[lock wait];
}
NSLog(@"The money has been used up.");
[lock unlock];
});
//Father线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"Work hard to make money.");
[lock signal];
[lock unlock];
});
2、NSConditionLock
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
- 递归锁
特点:同一个线程可以加锁N次而不会死锁。
1、NSRecursiveLock:
NSRecursiveLock在YYKit中YYWebImageOperation.m中有用到:
_lock = [NSRecursiveLock new];
- (void)dealloc {
[_lock lock];
...
...
[_lock unlock];
}
2、pthread_mutex(recursive)
pthread_mutex
锁也支持递归,只需要设置PTHREAD_MUTEX_RECURSIVE
即可
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
- 信号量加锁
多元信号量允许多个线程访问同一个资源,多元信号量简称信号量(Semaphore),对于允许多个线程并发访问的资源,这是一个很好的选择。一个初始值为N的信号量允许N个线程并发访问。其实严格的来说信号量不能算锁。而且如果信号量设置为1,我们可以把它当作互斥锁来用
1、dispatch_semaphore
dispatch_semaphore在YYKit中的YYThreadSafeArray.m有所应用,YY大神有这样一句注释:
@discussion Generally, access performance is lower than NSMutableArray,
but higher than using @synchronized, NSLock, or pthread_mutex_t.
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);
补充:读写锁(又称共享-互斥锁)
允许多个线程同时对同一个数据进行读操作,而只允许一个线程进行写操作。这是因为读操作不会改变数据的内容,是安全的;而写操作会改变数据的内容,是不安全的。
1、pthread_rwlock_t
//加读锁
pthread_rwlock_rdlock(&rwlock);
//解锁
pthread_rwlock_unlock(&rwlock);
//加写锁
pthread_rwlock_wrlock(&rwlock);
//解锁
pthread_rwlock_unlock(&rwlock);