美文网首页
各种锁介绍以及性能对比

各种锁介绍以及性能对比

作者: 哈豊玛奥 | 来源:发表于2019-02-22 16:55 被阅读0次
    • 多线程为我们带来了很大便利,也提高了程序的执行效率,但同时也带来了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的方案)

    二、性能比对图

    lock_benchmark.png

    性能测试Demo的GitHub地址:https://github.com/Yjunjie/MultithreadingAndLock/tree/master
    参考链接:https://www.jianshu.com/p/c9c5bc68449d

    • 总体来说:
      OSSpinLockdispatch_semaphore的效率远远高于其他。
      @synchronizedNSConditionLock效率较差。

    • 临界区指的是一块对公共资源进行访问的代码,并非一种机制或是算法。

    三、详细介绍

    • 自旋锁:
      如果共享数据被其他线程加锁,那么当前线程会以死循环的方式等待解锁,一旦访问的资源被解锁,则等待线程就会立即执行。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。

    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);
    

    相关文章

      网友评论

          本文标题:各种锁介绍以及性能对比

          本文链接:https://www.haomeiwen.com/subject/wecdyqtx.html