工作文档iOS开发技术iOS技术点

iOS 锁的简单实现与总结

2016-08-19  本文已影响2429人  MangK

一、互斥锁

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

1.@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请查阅相关文档、资料单独学习。

下面是我从 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 的对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞

测试代码

    __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

测试代码

//执行一次,之后用到,不在定义
#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

六、读写锁

百度百科:读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑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
#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

同步实现

    #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

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

上一篇下一篇

猜你喜欢

热点阅读