iOS开发技巧

OC底层原理21-锁的原理

2020-11-23  本文已影响0人  夏天的枫_

iOS--OC底层原理文章汇总

本文探索常用锁以及@synchronized底层的原理。

锁的分类

在开发中,使用最常见的恐怕就是@synchronized(互斥锁)、NSLock(互斥锁)、以及dispatch_semaphore(信号量)。其实还有许多种,总分类有:互斥锁、自旋锁,细分之下多出了: 读写锁、递归锁、条件锁、信号量,后三者是对基本锁的上层封装。先介绍几个概念。

自旋锁】是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。

【互斥锁】是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成。

【读写锁】是计算机程序的并发控制的一种同步机制(也称“共享-互斥锁”、多读-单写锁) 用于解决多线程对公共资源读写问题。读的操作可并发重入,写操作是互斥的。 读写锁通常用互斥锁、条件变量、信号量实现。

【信号量】是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。

条件锁】:条件锁就是条件变量,当进程的某些资源要求不满足时就进入休眠,即锁住了,当资源被分配到了,条件锁打开了,进程继续运行。

对应有以下锁:
  1. OSSpinLock(自旋锁)
  2. dispatch_semaphone(信号量)
  3. pthread_mutex(互斥锁)
  4. NSLock(互斥锁)
  5. NSCondition(条件锁)
  6. os_unfair_lock (互斥锁)
  7. pthread_mutex(recursive 互斥递归锁)
  8. NSRecursiveLock(递归锁)
  9. NSConditionLock(条件锁)
  10. synchronized(互斥锁)

OSSpinLock(自旋锁)

OSSpinLock效率很高,但是已不再安全。如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。

ibireme 大神--<不再安全的 OSSpinLock>

各类型锁的性能

所以苹果已经推荐使用os_unfair_lock

#import <os/lock.h>
// 创建一个 os_unfair_lock_t 锁
os_unfair_lock_t unfairLock;
// 先分配此类型的变量并将其初始化为OS_UNFAIR_LOCK_INIT
unfairLock = &(OS_UNFAIR_LOCK_INIT);
// 尝试加锁,返回YES or NO
os_unfair_lock_trylock(unfairLock)
// 加锁
os_unfair_lock_lock(unfairLock);
// 解锁
os_unfair_lock_unlock(unfairLock);

dispatch_semaphore(信号量)

信号量适用于异步线程同步操作的场景。

    // 创建使用
    dispatch_semaphore_create(long value); // 创建信号量
    dispatch_semaphore_signal(dispatch_semaphore_t deem); // 发送信号量
    dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); // 等待信号量
    //  👉注意: 发送信号量和信号等待是成对出现

    // 常见使用场景
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{ // ①
        
        NSLog(@"任务1:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sem); // ③
    });
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // ②
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务2:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sem); // ⑤
    });
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // ④
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务3:%@",[NSThread currentThread]); // ⑥
    });
    
    // 执行顺序:① - ② - ③ - ④ - ⑤ - ⑥

}

通过控制信号量通过数,就可实现锁的功能。

pthread_mutex(互斥锁)

加解锁流程:sleep(加锁) -> 出让时间片 -> 线程休眠 -> 等待唤醒 -> running(解锁)

时间⽚(quantum):系统给每个正在运行的进程或线程微观上的一段CPU时间。

//  导入互斥锁头文件--C语言
#import <pthread.h>
// 可添加成员变量
 pthread_mutex_t mutex;

- (void)myfun
{
    pthread_mutex_init(&mutex, NULL);
    
}
- (void)MyLockingFunction
{
    pthread_mutex_lock(&mutex);
    // Do something.
    pthread_mutex_unlock(&mutex);
}
- (void)dealloc
{  
     // 不用要释放掉
    pthread_mutex_destroy(&mutex);
}
// 这只是简单使用,具体还需针对进行错误代码处理
互斥锁 vs 自旋锁

相同:都能保证同一时间只有一个线程访问共享资源。都能保证线程安全。
不同:

NSLock(互斥锁)

NSLock是对底层pthread_mutex的封装。一般使用有:

self.lock = [[NSLock alloc] init];
[self.lock tryLock]; // 尝试加锁;返回YES or NO
[self.lock lock]; // 加锁
[self.lock unlock]; // 解锁

在Apple官方文档中指出

Warning
The NSLock class uses POSIX threads to implement its locking behavior. When sending an
unlock message to an NSLock object, you must be sure that message is sent from the
same thread that sent the initial lock message. Unlocking a lock from a different thread can result in undefined behavior.
Tra:本NSLock类使用POSIX线程执行其锁定行为。向NSLock对象发送解锁消息时,必须确保该消
息是从发送初始锁定消息的同一线程发送的。从其他线程解锁锁可能导致未定义的行为。

所有它仅限用于同一线程中,且也不应使用此类来实现递归锁。lock在同一线程上两次调用该方法将永久锁定您的线程。原因是加锁还未解锁又再一次加锁,一直在加锁就会陷入死锁状态。如下:

NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<50; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void (^testMethod)(int);
        testMethod = ^(int value){
            [lock lock];
            if (value > 0) {
              NSLog(@"current value = %d",value);
              testMethod(value - 1);
            }
        };
        testMethod(10);
        [lock unlock];
    });
}  

可以使用NSRecursiveLock来实现递归锁。

atomic & nonatomic

atomic

nonatomic

属性应都声明为 nonatomic
尽量避免多线程抢夺同⼀块资源;
尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减⼩移动客户端的压⼒。

@synchronized

上一篇 下一篇

猜你喜欢

热点阅读