深度研究程序员首页投稿

iOS开发中的11种锁整理

2018-08-09  本文已影响15人  百草纪
  • 多线程编程被普遍认为复杂,主要是因为多线程给程序引入了一定的不可预知性,要控制这些不可预知性,就需要使用各种锁各种同步机制,不同的情况就应该使用不同的锁不同的机制。

  • 什么事情一旦放到多线程环境,要考虑的问题立刻就上升了好几个量级。多线程编程带来的好处不可胜数,然而工程师只要一不小心,就很容易让你的程序失去控制,所以你得用各种锁各种机制管住它。

  • 要解决好这些问题,工程师们就要充分了解这些锁机制,分析不同的场景,选择合适的解决方案。

1400498-2bfca138992bb7d8.png
//10000000
OSSpinLock:                 112.38 ms
dispatch_semaphore:         160.37 ms
os_unfair_lock:             208.87 ms
pthread_mutex:              302.07 ms
NSCondition:                320.11 ms
NSLock:                     331.80 ms
pthread_rwlock:             360.81 ms
pthread_mutex(recursive):   512.17 ms
NSRecursiveLock:            667.55 ms
NSConditionLock:            999.91 ms
@synchronized:             1654.92 ms
//1000
OSSpinLock:                   0.02 ms
dispatch_semaphore:           0.03 ms
os_unfair_lock:               0.04 ms
pthread_mutex:                0.06 ms
NSLock:                       0.06 ms
pthread_rwlock:               0.07 ms
NSCondition:                  0.07 ms
pthread_mutex(recursive):     0.09 ms
NSRecursiveLock:              0.12 ms
NSConditionLock:              0.18 ms
@synchronized:                0.33 ms

互斥锁

1、NSLock

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
// 主线程中
NSLock *lock = [[NSLock alloc] init];
    
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock lock];
    NSLog(@"线程1");
    sleep(2);
    [lock unlock];
    sleep(1);
    NSLog(@"线程1解锁成功");
});

//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock lock];
    NSLog(@"线程2");
    [lock unlock];
});

// 打印:
//   线程1
//   线程2
//   线程1解锁成功

2、pthread_mutex

// 用于静态的mutex的初始化,采用默认的attr。比如: static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define PTHREAD_MUTEX_INITIALIZER {_PTHREAD_MUTEX_SIG_init, {0}}

// 动态的初始化一个锁,__restrict 为互斥锁的类型,传 NULL 为默认类型,一共有 4 类型。
int pthread_mutex_init(pthread_mutex_t * __restrict, const pthread_mutexattr_t * __restrict);

 // 请求锁,如果当前mutex已经被锁,那么这个线程就会卡在这儿,直到mutex被释放
int pthread_mutex_lock(pthread_mutex_t *);

// 尝试请求锁,如果当前mutex已经被锁或者不可用,这个函数就直接return了,不会把线程卡住
int pthread_mutex_trylock(pthread_mutex_t *);

// 解锁
int pthread_mutex_unlock(pthread_mutex_t *);

// 把mutex锁干掉,并且释放所有它所占有的资源
int pthread_mutex_destroy(pthread_mutex_t *);

int pthread_mutex_setprioceiling(pthread_mutex_t * __restrict, int,
  int * __restrict);

int pthread_mutex_getprioceiling(const pthread_mutex_t * __restrict,
  int * __restrict);
PTHREAD_MUTEX_NORMAL 缺省类型,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后先进先出原则获得锁。

PTHREAD_MUTEX_ERRORCHECK 检错锁,如果同一个线程请求同一个锁,则返回 EDEADLK,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。

PTHREAD_MUTEX_RECURSIVE 递归锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。

PTHREAD_MUTEX_DEFAULT 适应锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。
 ///////////////////// pthread_src/include/pthread/pthread.h
#define PTHREAD_MUTEX_INITIALIZER __PTHREAD_MUTEX_INITIALIZER

///////////////////// pthread_src/sysdeps/generic/bits/mutex.h
// mutex锁本质上是一个spin lock,空转锁
#  define __PTHREAD_MUTEX_INITIALIZER \
    { __PTHREAD_SPIN_LOCK_INITIALIZER, __PTHREAD_SPIN_LOCK_INITIALIZER, 0, 0, 0, 0, 0, 0 } 

///////////////////// pthread_src/sysdeps/generic/pt-mutex-init.c
// 你看,这里其实用的也是宏。就这一句是初始化,下面都是在设置属性。
int
_pthread_mutex_init (pthread_mutex_t *mutex,
             const pthread_mutexattr_t *attr)
{
  *mutex = (pthread_mutex_t) __PTHREAD_MUTEX_INITIALIZER; 

  if (! attr
      || memcmp (attr, &__pthread_default_mutexattr, sizeof (*attr) == 0))
    /* The default attributes.  */
    return 0;

  if (! mutex->attr
      || mutex->attr == __PTHREAD_ERRORCHECK_MUTEXATTR
      || mutex->attr == __PTHREAD_RECURSIVE_MUTEXATTR)
      //pthread_mutex_destroy释放的就是这里的资源
     mutex->attr = malloc (sizeof *attr);                
  if (! mutex->attr) return ENOMEM;
  *mutex->attr = *attr;
  return 0;
}
#import <pthread.h>

static pthread_mutex_t theLock;
- (void)example5 {
    pthread_mutex_init(&theLock, NULL);
    
    pthread_t thread;
    pthread_create(&thread, NULL, threadMethord1, NULL);
    
    pthread_t thread2;
    pthread_create(&thread2, NULL, threadMethord2, NULL);
}
void *threadMethord1() {
    pthread_mutex_lock(&theLock);
    printf("线程1\n");
    sleep(2);
    pthread_mutex_unlock(&theLock);
    printf("线程1解锁成功\n");
    return 0;
}
void *threadMethord2() {
    sleep(1);
    pthread_mutex_lock(&theLock);
    printf("线程2\n");
    pthread_mutex_unlock(&theLock);
    return 0;
}

// 打印:
//   线程1
//   线程1解锁成功
//   线程2

pthread_mutex(recursive)递归锁

- (void)example5 {
    // 定义mutex
    pthread_mutex_init(&theLock, NULL);
    
    // 定义mutexattr
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    
    pthread_mutex_init(&theLock, &attr);
    pthread_mutexattr_destroy(&attr);
    
    pthread_t thread;
    pthread_create(&thread, NULL, threadMethord, 5);
}

void *threadMethord(int value) {
    pthread_mutex_lock(&theLock);
    
    if (value > 0) {
        printf("Value:%i\n", value);
        sleep(1);
        threadMethord(value - 1);
    }
    pthread_mutex_unlock(&theLock);
    return 0;
}

// Value:5
// Value:4
// Value:3
// Value:2
// Value:1

3、@synchronized

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @synchronized(self) {
        sleep(2);
        NSLog(@"线程1");
    }
    sleep(1);
    NSLog(@"线程1解锁成功");
});
    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @synchronized(self) {
        NSLog(@"线程2");
    }
});

// 线程1
// 线程2
// 线程1解锁成功

自旋锁

1、OSSpinLock

typedef int32_t OSSpinLock;
//尝试加锁
bool    OSSpinLockTry( volatile OSSpinLock *__lock );
//加锁
void    OSSpinLockLock( volatile OSSpinLock *__lock );
//解锁
void    OSSpinLockUnlock( volatile OSSpinLock *__lock );
#import <libkern/OSSpinLockDeprecated.h>

__block OSSpinLock theLock = OS_SPINLOCK_INIT;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    OSSpinLockLock(&theLock);
    NSLog(@"需要线程同步的操作1 开始");
    sleep(3);
    NSLog(@"需要线程同步的操作1 结束");
    OSSpinLockUnlock(&theLock);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    OSSpinLockLock(&theLock);
    sleep(1);
    NSLog(@"需要线程同步的操作2");
    OSSpinLockUnlock(&theLock);
});
// 需要线程同步的操作1 开始
// 需要线程同步的操作1 结束
// 需要线程同步的操作2

os_unfair_lock

// 简单使用
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);

读写锁

1、pthread_rwlock

  • 读写锁的特性是这样的
    • 当一个线程加了读锁访问临界区,另外一个线程也想访问临界区读取数据的时候,也可以加一个读锁,这样另外一个线程就能够成功进入临界区进行读操作了。此时读锁线程有两个。
    • 当第三个线程需要进行写操作时,它需要加一个写锁这个写锁只有在读锁的拥有者为0时才有效。也就是等前两个读线程都释放读锁之后,第三个线程就能进去写了。
  • 总结一下就是,读写锁里,读锁能允许多个线程同时去读,但是写锁在同一时刻只允许一个线程去写。
PTHREAD_RWLOCK_INITIALIZER

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

//加读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

//加写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

//解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

// 这个函数在Linux和Mac的man文档里都没有,新版的pthread.h里面也没有,旧版的能找到
int pthread_rwlock_timedrdlock_np(pthread_rwlock_t *rwlock, const struct timespec *deltatime); 
// 同上
int pthread_rwlock_timedwrlock_np(pthread_rwlock_t *rwlock, const struct timespec *deltatime); 

注意的地方

////////////////////////////// /usr/include/pthread.h

/* Read-write lock types.  */
#if defined __USE_UNIX98 || defined __USE_XOPEN2K
enum
{
  PTHREAD_RWLOCK_PREFER_READER_NP,
  PTHREAD_RWLOCK_PREFER_WRITER_NP, // 妈蛋,没用,一样reader优先
  PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,
  PTHREAD_RWLOCK_DEFAULT_NP = PTHREAD_RWLOCK_PREFER_READER_NP
};

递归锁

1、NSRecursiveLock

@interface NSRecursiveLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    static void (^RecursiveBlock)(int);
    RecursiveBlock = ^(int value) {
        [lock lock];
        if (value > 0) {
            NSLog(@"value:%d", value);
            RecursiveBlock(value - 1);
        }
        [lock unlock];
    };
    RecursiveBlock(2);
});

// value:2
// value:1

2、pthread_mutex(recursive)

见上文

条件锁

1、NSCondition

@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
NSCondition *lock = [[NSCondition alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock lock];
    while (!array.count) {
        [lock wait];
    }
    [array removeAllObjects];
    NSLog(@"array removeAllObjects");
    [lock unlock];
});
    
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);//以保证让线程2的代码后执行
    [lock lock];
    [array addObject:@1];
    NSLog(@"array addObject:@1");
    [lock signal];
    [lock unlock];
});

// array addObject:@1
// array removeAllObjects

2、NSConditionLock

@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;

- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
//主线程中
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
    
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock lockWhenCondition:1];
    NSLog(@"线程1");
    sleep(2);
    [lock unlock];
});
    
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);//以保证让线程2的代码后执行
    if ([lock tryLockWhenCondition:0]) {
        NSLog(@"线程2");
        [lock unlockWithCondition:2];
        NSLog(@"线程2解锁成功");
    } else {
        NSLog(@"线程2尝试加锁失败");
    }
});
    
//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(2);//以保证让线程2的代码后执行
    if ([lock tryLockWhenCondition:2]) {
        NSLog(@"线程3");
        [lock unlock];
        NSLog(@"线程3解锁成功");
    } else {
        NSLog(@"线程3尝试加锁失败");
    }
});
    
//线程4
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(3);//以保证让线程2的代码后执行
    if ([lock tryLockWhenCondition:2]) {
        NSLog(@"线程4");
        [lock unlockWithCondition:1];    
        NSLog(@"线程4解锁成功");
    } else {
        NSLog(@"线程4尝试加锁失败");
    }
});
    
// 线程2
// 线程2解锁成功
// 线程3
// 线程3解锁成功
// 线程4
// 线程4解锁成功
// 线程1

信号量

dispatch_semaphore

//传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。
//值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。
dispatch_semaphore_create(long value);

//这个函数会使传入的信号量dsema的值减1;
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

//这个函数会使传入的信号量dsema的值加1;
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
// 超时时间overTime设置成>2
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(signal, overTime);
        NSLog(@"需要线程同步的操作1 开始");
        sleep(2);
        NSLog(@"需要线程同步的操作1 结束");
    dispatch_semaphore_signal(signal);
});
    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);
    dispatch_semaphore_wait(signal, overTime);
        NSLog(@"需要线程同步的操作2");
    dispatch_semaphore_signal(signal);
});
// 需要线程同步的操作1 开始
// 需要线程同步的操作1 结束
// 需要线程同步的操作2

// 超时时间设置为<2s的时候
//...
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
//...
// 需要线程同步的操作1 开始
// 需要线程同步的操作2
// 需要线程同步的操作1 结束
  • dispatch_semaphoreNSCondition 类似,都是一种基于信号的同步方式,但 NSCondition 信号只能发送,不能保存(如果没有线程在等待,则发送的信号会失效)。而 dispatch_semaphore 能保存发送的信号。dispatch_semaphore 的核心是 dispatch_semaphore_t 类型的信号量。

补充

pthread_cleanup_push() & pthread_cleanup_pop()

    void pthread_cleanup_push(void (*callback)(void *), void *arg);
    void pthread_cleanup_pop(int execute);
static void * thread_function(void *args)
{
    ...
    ...
    ...
    ...
    return 0; // 线程退出时没有调用pthread_exit()退出,而是直接return,此时是不会调用栈内callback的
}
注意事项:callback函数是可以传参数的
void callback(void *callback_arg)
{
    printf("arg is : %s\n", (char *)callback_arg);
}

static void * thread_function(void *thread_arg)
{
    ...
    pthread_cleanup_push(callback, "this is a queue thread, and was terminated.");
    ...
    pthread_exit((void *) 0); // 这句不调用,线程结束就不会调用你塞进去的callback函数。
    return ((void *) 0);
}

int main ()
{
    ...
    ...
    error = pthread_create(&tid, NULL, thread_function, (void *)thread_arg)
    ...
    ...
    return 0;
}
要保持callback栈平衡
pthread_cleanup_pop(0); // 传递参数0,在pop的时候就不会调用对应的callback,如果传递非0值,pop的时候就会调用对应callback了。
pthread_cleanup_pop(0); // push了两次就pop两次,你要是只pop一次就不能编译通过
/* ./include/pthread/pthread.h */
#define pthread_cleanup_push(rt, rtarg) __pthread_cleanup_push(rt, rtarg)
#define pthread_cleanup_pop(execute) __pthread_cleanup_pop(execute)

/* ./sysdeps/generic/bits/cancelation.h */
#define __pthread_cleanup_push(rt, rtarg) \
    { \
      struct __pthread_cancelation_handler **__handlers \
        = __pthread_get_cleanup_stack (); \
      struct __pthread_cancelation_handler __handler = \
        { \
          (rt), \
          (rtarg), \
          *__handlers \
        }; \
      *__handlers = &__handler;

#define __pthread_cleanup_pop(execute) \
      if (execute) \
        __handler.handler (__handler.arg); \
       *__handlers = __handler.next; \
    } \

具体细节请参考推荐博文

上一篇下一篇

猜你喜欢

热点阅读