iOS 锁的简单实现与总结

作者: MangK | 来源:发表于2016-08-19 11:23 被阅读2429次

一、互斥锁

百度百科:在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象

1.@synchronized
  • @synchronized要一个参数,这个参数相当于信号量
//用在防止多线程访问属性上比较多
- (void)setTestInt:(NSInteger *)testInt{
    @synchronized (self) {
        _testInt=testInt;
    }
}

2.NSLock

block及宏定义,

//定义block类型
typedef void(^KYSBlock)();

//定义获取全局队列方法
#define KYS_GLOBAL_QUEUE(block) \
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ \
    while (1) { \
        block();\
    }\
})

测试代码

NSLock *lock=[[NSLock alloc] init];
    KYSBlock block=^{
        [lock lock];
        NSLog(@"执行操作");
        sleep(1);
        [lock unlock];
    };
    KYS_GLOBAL_QUEUE(block);

3.pthread

pthread 除了创建互斥锁,还可以创建递归锁、读写锁、once等锁。稍后会介绍一下如何使用(九牛一毛而已)。如果想深入学习pthread请查阅相关文档、资料单独学习。

  • 静态初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
  • 动态初始化 pthread_mutex_init()函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为NULL,使用默认的属性,返回0代表初始化成功。这种方式可以初始化普通锁、递归锁(同NSRecursiveLock),初始化方式有些复杂
  • 此类初始化方法可设置锁的类型,PTHREAD_MUTEX_ERRORCHECK 互斥锁不会检测死锁,PTHREAD_MUTEX_ERRORCHECK 互斥锁可提供错误检查,PTHREAD_MUTEX_RECURSIVE 递归锁,PTHREAD_PROCESS_DEFAULT 映射到 PTHREAD_PROCESS_NORMAL

下面是我从 YYKit copy 下来的:

#import <pthread.h>

//YYKit
static inline void pthread_mutex_init_recursive(pthread_mutex_t *mutex, bool recursive) {
#define YYMUTEX_ASSERT_ON_ERROR(x_) do { \
__unused volatile int res = (x_); \
assert(res == 0); \
} while (0)
    assert(mutex != NULL);
    if (!recursive) {
        //普通锁
        YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init(mutex, NULL));
    } else {
        //递归锁
        pthread_mutexattr_t attr;
        YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_init (&attr));
        YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE));
        YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init (mutex, &attr));
        YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_destroy (&attr));
    }
#undef YYMUTEX_ASSERT_ON_ERROR
}

测试代码

    __block pthread_mutex_t lock;
    pthread_mutex_init_recursive(&lock,false);
    
    KYSBlock block0=^{
        NSLog(@"线程 0:加锁");
        pthread_mutex_lock(&lock);
        NSLog(@"线程 0:睡眠 1 秒");
        sleep(1);
        pthread_mutex_unlock(&lock);
        NSLog(@"线程 0:解锁");
    };
    KYS_GLOBAL_QUEUE(block0);
    
    KYSBlock block1=^(){
        NSLog(@"线程 1:加锁");
        pthread_mutex_lock(&lock);
        NSLog(@"线程 1:睡眠 2 秒");
        sleep(2);
        pthread_mutex_unlock(&lock);
        NSLog(@"线程 1:解锁");
    };
    KYS_GLOBAL_QUEUE(block1);

    KYSBlock block2=^{
        NSLog(@"线程 2:加锁");
        pthread_mutex_lock(&lock);
        NSLog(@"线程 2:睡眠 3 秒");
        sleep(3);
        pthread_mutex_unlock(&lock);
        NSLog(@"线程 2:解锁");
    };
    KYS_GLOBAL_QUEUE(block2);

输出结果

1 2 3

二、递归锁

同一线程可多次加锁,不会造成死锁

1.NSRecursiveLock

实现代码

        NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
    KYS_GLOBAL_QUEUE(^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            [lock lock];
            if (value > 0) {
                NSLog(@"加锁层数 %d", value);
                sleep(1);
                RecursiveBlock(--value);
            }
            [lock unlock];
        };
        RecursiveBlock(3);
    });

输出结果

从输入结果可以看出并未发生死锁
2.pthread

代码实现

    __block pthread_mutex_t lock;
    //第二个参数为true生成递归锁
    pthread_mutex_init_recursive(&lock,true);
    
    KYS_GLOBAL_QUEUE(^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            pthread_mutex_lock(&lock);
            if (value > 0) {
                NSLog(@"加锁层数 %d", value);
                sleep(1);
                RecursiveBlock(--value);
            }
            pthread_mutex_unlock(&lock);
        };
        RecursiveBlock(3);
    });

输出结果


从输入结果可以看出并未发生死锁

三、信号量

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量

1.NSCondition

NSCondition 的对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞

  • 常用来解决生产者消费者问题
  • 注意:wait并不完全可信,通常我们会添加变量标识来规避不正常的返回

测试代码

    __block NSMutableArray *products=[[NSMutableArray alloc] init];
    NSCondition *condition = [[NSCondition alloc] init];
    KYS_GLOBAL_QUEUE(^{
        NSLog(@"product+1.:加锁");
        [condition lock];
        NSLog(@"product+2.:随眠5秒");
        sleep(5);
        NSLog(@"product+3.:生产产品");
        [products addObject:@"Prodeuc"];
        NSLog(@"product+4.:发送生产信号");
        [condition signal];
        NSLog(@"product+5.:发送生产信号完毕");
        [condition unlock];
        NSLog(@"product+6.:解锁");
    });
    
    KYS_GLOBAL_QUEUE(^{
        NSLog(@"consume-1.:加锁");
        [condition lock];
        NSLog(@"consume-2.:准备消费产品");
        if (!products.count) {
            NSLog(@"consume-3.:无产品,休眠等待");
            [condition wait];
        }
        NSLog(@"consume-4.:消费产品");
        [products removeObjectAtIndex:0];
        [condition unlock];
        NSLog(@"consume-5.:解锁");
    });

输出感受一下

测试输出
2.GCD dispatch_semaphore_t

同步实现

    //参数可以理解为信号的总量,传入的值必须大于或等于0,否则,返回NULL
    //dispatch_semaphore_signal +1
    //dispatch_semaphore_wait等待信号,当<=0时会进入等待状态,否则,-1
    __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    KYS_GLOBAL_QUEUE(^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"这里简单写一下用法,可自行实现生产者、消费者");
        sleep(1);
        dispatch_semaphore_signal(semaphore);
    });
pthread

简单实现

    __block pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
    __block pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
    
    KYS_GLOBAL_QUEUE(^{
        //NSLog(@"线程 0:加锁");
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);
        NSLog(@"线程 0:wait");
        pthread_mutex_unlock(&mutex);
        //NSLog(@"线程 0:解锁");
    });
    
    KYS_GLOBAL_QUEUE(^{
        //NSLog(@"线程 1:加锁");
        sleep(3);//3秒发一次信号
        pthread_mutex_lock(&mutex);
        NSLog(@"线程 1:signal");
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        //NSLog(@"线程 1:加锁");
    });

四、条件锁

1.NSConditionLock
  • lock 不分条件,如果锁没被申请,直接执行代码
  • unlock 不会清空条件,之后满足条件的锁还会执行
  • unlockWithCondition:我的理解就是设置解锁条件(同一时刻只有一个条件,如果已设置条件,相当于修改条件)
  • lockWhenCondition:满足特定条件,执行相应代码

测试代码

//执行一次,之后用到,不在定义
#define KYS_GLOBAL_QUEUE_ONCE(block) \
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ \
    block();\
})
    NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:0];
    
    KYS_GLOBAL_QUEUE_ONCE(^{
        for (int i=0;i<3;i++){
            [conditionLock lock];
            NSLog(@"线程 0:%d",i);
            sleep(1);
            [conditionLock unlockWithCondition:i];
        }
    });
    
    sleep(1);
    
    KYS_GLOBAL_QUEUE_ONCE(^{
        [conditionLock lock];
        NSLog(@"线程 1");
        [conditionLock unlock];
    });
    
    KYS_GLOBAL_QUEUE_ONCE(^{
        [conditionLock lockWhenCondition:2];
        NSLog(@"线程 2");
        [conditionLock unlockWithCondition:0];
    });
    
    KYS_GLOBAL_QUEUE_ONCE(^{
        [conditionLock lockWhenCondition:1];
        NSLog(@"线程 3");
        [conditionLock unlockWithCondition:2];
    });
    
    KYS_GLOBAL_QUEUE_ONCE(^{
        [conditionLock lockWhenCondition:0];
        NSLog(@"线程 4");
        [conditionLock unlockWithCondition:1];
    });

输出(3次)

1
2
3

注释线程2

//    KYS_GLOBAL_QUEUE_ONCE(^{
//        [conditionLock lockWhenCondition:2];
//        NSLog(@"线程 2");
//        [conditionLock unlockWithCondition:0];
//    });

输出

注释线程2输出

五、分布式锁

维基百科: 分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁

1.NSDistributedLock
  • 处理多个进程或多个程序之间互斥问题
  • 一个获取锁的进程或程序在释放锁之前挂掉,锁不会被释放,可以通过breakLock方法解锁
  • iOS很少用到,暂不详细研究

六、读写锁

百度百科:读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者

1.GCD

样例

#import "MyObject.h"

@interface MyObject()

@property(nonatomic,copy)NSString *testString;
@property(nonatomic,strong)dispatch_queue_t syncQueue;

@end

@implementation MyObject{
    NSString *_testString;
}

@synthesize testString=_testString;

//在并发队列里同步读取属性值
- (NSString *)testString{
    __block NSString *str;
    dispatch_sync(self.syncQueue, ^{
        str=_testString;
    });
    return str;
}

//异步设置属性值
- (void)setTestString:(NSString *)testString{
    //执行此操作时队列其他操作等待
    //这样可同时有多个线程读取该属性,同一时刻只能有一个线程写值且读线程等到
    dispatch_barrier_async(self.syncQueue, ^{
        _testString=testString;
    });
}

- (dispatch_queue_t)syncQueue{
    if(!_syncQueue){
        //这里使用全局并发队列
        _syncQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    }
    return _syncQueue;
}
2.pthread
  • 与上述初始化方式类似,静态THREAD_RWLOCK_INITIALIZER、动态pthread_rwlock_init() ,pthread_rwlock_destroy用来销毁该锁
#import <pthread.h>

    __block pthread_rwlock_t rwlock;
    pthread_rwlock_init(&rwlock,NULL);
    
    //读
    KYS_GLOBAL_QUEUE(^{
        //NSLog(@"线程0:随眠 1 秒");//还是不打印能直观些
        sleep(1);
        NSLog(@"线程0:加锁");
        pthread_rwlock_rdlock(&rwlock);
        NSLog(@"线程0:读");
        pthread_rwlock_unlock(&rwlock);
        NSLog(@"线程0:解锁");
    });
    //写
    KYS_GLOBAL_QUEUE(^{
        //NSLog(@"线程1:随眠 3 秒");
        sleep(3);
        NSLog(@"线程1:加锁");
        pthread_rwlock_wrlock(&rwlock);
        NSLog(@"线程1:写");
        pthread_rwlock_unlock(&rwlock);
        NSLog(@"线程1:解锁");
    });

输出结果

读写顺序

七、自旋锁

百度百科:何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

1.OSSpinLock
  • OSSpinLock已不再安全,详情请参考 这里

同步实现

    #import <libkern/OSAtomic.h>

    __block OSSpinLock lock = OS_SPINLOCK_INIT;
    
    KYS_GLOBAL_QUEUE(^{
        OSSpinLockLock(&lock);
        NSLog(@"已不安全,就了解这些吧......,也许将来会安全!?");
        sleep(1);
        OSSpinLockUnlock(&lock);
    });

八、ONCE(只执行一次)

  • 多用来创建单例
GCD

实现

    KYS_GLOBAL_QUEUE(^{
        NSLog(@"once");
        sleep(1);
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
    });

输出

GCD
2.pthread
//定义方法
    void fun(void){
        NSLog(@"%@",[NSThread currentThread]);
    }    

    __block pthread_once_t once=PTHREAD_ONCE_INIT;
    KYS_GLOBAL_QUEUE(^{
        NSLog(@"fun");
        sleep(1);
        pthread_once(&once, fun);
    });

输出结果


pthread

九、关于运行效率请参考下列文章

相关文章

  • iOS 多线程

    参考链接 iOS多线程iOS 多线程:『GCD』详尽总结iOS简单优雅的实现复杂情况下的串行需求(各种锁、GCD ...

  • Java中的锁

    对Java中的锁做了简单的罗列总结,以便于后期回顾。 本文对如下概念进行了总结:锁的意义;锁的定义与实现;Java...

  • iOS 锁的简单实现与总结

    一、互斥锁 百度百科:在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" ...

  • iOS开发中的锁

    iOS开发中的锁 本人对锁没有深入理解,只是看了几篇文章,在这里做一下简单的总结。 iOS开发中,锁是用来解决线程...

  • iOS分类的实现原理简记

    该文为分类原理的简单记录,总结自如下文章,感谢作者分享: iOS底层原理总结 iOS分类底层实现原理小记 1、分类...

  • iOS中的锁简单总结

    由于之前开发中用到了锁这个东西,加上这个知识在之前的项目中用之甚少,所以对于此想做一下在百度网上文章过程中的再一次...

  • 乐观锁和悲观锁

    参考来源 深入理解乐观锁与悲观锁 乐观锁的一种实现方式——CAS mysql乐观锁总结和实践 乐观锁和悲观锁 悲观...

  • iOS底层原理总结 - 关联对象实现原理

    iOS底层原理总结 - 关联对象实现原理 iOS底层原理总结 - 关联对象实现原理

  • iOS简单优雅的实现复杂情况下的串行需求(各种锁、GCD 、NS

    iOS简单优雅的实现复杂情况下的串行需求(各种锁、GCD 、NSOperationQueue...) 昨天一个同事...

  • iOS 锁屏问题

    iOS实现关闭/开启自动锁屏1 不自动锁屏[UIApplication sharedApplication]....

网友评论

  • SuperDawn_0828:读写锁中的GCD例子,假设写操作是一个耗时操作,可能会卡主线程
  • DoubleShawn:文章写得很棒!支持!另外,YY真是屌。

本文标题:iOS 锁的简单实现与总结

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