OSSpinLock自旋锁

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012581760/article/details/83919518

OSSpinLock:自旋锁,等待锁的线程会处于忙等(busy-wait)状态,一直占用这CPU资源
OSSpinLock要导入头文件 <libkern/OSAtomic.h>

#import "DetailSaleTicketVC.h"
#import <libkern/OSAtomic.h>
@interface DetailSaleTicketVC ()
@property (assign, nonatomic) int ticketsCount;
@property (assign, nonatomic) OSSpinLock lock;
@end
/*
 OSSpinLock:自旋锁,等待锁的线程会处于忙等(busy-wait)状态,一直占用这CPU资源
 OSSpinLock要导入头文件 <libkern/OSAtomic.h>
 */
@implementation DetailSaleTicketVC

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.ticketsCount = 15;
    OSSpinLock lock = OS_SPINLOCK_INIT;
    self.lock = lock;
    
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self ticketTest];
}

- (void)saleTicket
{
    // 从访问数据之前开始加锁
    
   /* // 初始化锁 (注意加锁,解锁的过程都要为同一把锁才行,不然每次都创建一个新锁,加锁解锁没有意义)
    OSSpinLock lock = OS_SPINLOCK_INIT;如果Lock为局部变量,意味着每次进来这个lock都是新的
    ,重新定义变量,重新初始化一把锁,每个线程进来都是新创建的一把锁,新锁肯定没有被别人加过,又来加锁,,在加锁的同时,另外一条线程又来创建一把锁,又加锁,又来解锁,每次进来都是新的
    达不到加锁的目的,所以的线程都要用同一把锁.
    
    加锁原理:当我们使用多线程的时候,总有一个线程要先执行这段代码,当第一条线程执行这段代码的时候,这段代码先
    加锁,一旦加锁,别的线程就无法进入这段代码,当第一条线程在执行下面的代码中,第二条线程也进来了,由于第二条线程
    也想要加锁,而这次想加的这把锁,就上第一条线程已经加锁的锁,发现这把锁被别的线程加过了,就知道有别人在访问代码
    所以第二条线程就会在这里等待,直到第一条线程把同一把锁进行解锁后第二条线程才能加锁,所以线程都是访问同一
    把锁的时候,才能达到加锁的目的,只有同一把锁,才能办到多线程同步.应该是共用一把锁.
    锁的原来很简单,进来,首先判断这把锁有没有被别人加过,一旦被别人加过就会等待不会往下走,一旦发现这把锁
    没有被别人加过就会加锁往下执行代码
    */
    // 加锁
    NSLog(@"========%p",&_lock);
    OSSpinLockLock(&_lock);
    
    int oldTicketCount = self.ticketsCount;
    sleep(.2);
    oldTicketCount--;
    self.ticketsCount = oldTicketCount;
    NSLog(@"剩余%d票,----%@----",oldTicketCount,[NSThread currentThread]);
    
    // 用完了再解锁
    OSSpinLockUnlock(&_lock);
    
    // 创建了锁,再加锁加锁
}

// 卖票演示

- (void)ticketTest
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
}

总结
在这里有一个问题
如果线程进来发现锁被别人加过了,线程就会在这里等待,相等于线程就会阻塞这个位置,常见的线程阻塞有两种方案
一种是相等于写一个外循环,一直在等,一直执行代码,一直占用CPU资源,自旋锁就是这样的相当等于while(锁还没有被放开);一直执行.一旦锁放开,外循环退出.(自旋锁会出现优先级反转)
还有一种是让线程直接睡觉,直接睡眠,睡觉意味着不占用CPU资源.
自旋锁优先级反转
自旋锁不安全的问题
假设有三条线程thread1,thread2,thread3,三条线程是怎么调度的?,调度就是安排时间给thread1,thread2,thread3,让每一个线程都执行一下任务,比如先分配一点时间给thread1做事情,thread1的时间到了,再分配给thread2一点时间做事情
即轮流给他们提供时间,如果提供的时间足够小,每一个线程给10ms,给大家造成的感觉是thread1,thread2,thread3,3条线程同时执行,这就是实现多线程的原理 时间片轮转调度算法(进程,线程),这里有一个线程优先级的问题,如果thread1的优先级比较高,就会先让thread1执行,或者让thread1多执行一段时间,那么久会出现优先级反转
优先级反转,是假设thread1的优先级比较高,thread2的优先级比较低,假设,thread2先进来了,发现锁还没有被加
意味着先把这把锁进行加锁,紧接着,thread1进来了,发现锁已经被别人加过了,所以thread1只能忙等,即一直执行while(未解锁);由于thread1的优先级比较高,就有可能CPU就一直给thread1的while(未解锁)分配时间,CPU相等于一直在做这个事情,相等于CPU就没有时间分配给thread2,那么thread2的代码就不会继续执行(自己加锁之后执行的任务),thread2的任务完成不了,那么这把锁就无法进行解锁,thread2无法解锁,那么thread1就一直在这里等.导致死锁的感觉.
如果不是以自旋锁的方式,而是以休眠的方式,记优先级比较高的是直接睡觉,不占用CPU资源,优先级比较低的thread2就可以继续往下走,然后解锁,解锁完成,thread1从睡眠中唤响,发现锁解锁了,就执行.

猜你喜欢

转载自blog.csdn.net/u012581760/article/details/83919518