iOS 锁的简单实现与总结

一、互斥锁

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容