iOS:多线程与锁

按照功能来区分锁:

互斥锁(mutexlock)sleep-waiting:

保证共享数据操作的完整性, 锁被占用的时候会休眠, 等待锁释放的时候会唤醒。
在访问共享资源之前进行加锁,访问完成后解锁。
线程A获取到锁,在释放锁之前,其他线程都获取不到锁。
解锁时,如果有1个以上的线程阻塞,那么所有该锁上的线程变为就绪状态,第一个就绪的加锁,其他的又进入休眠。
从而实现在任意时刻,最多只有1个线程能够访问被互斥锁保护的资源。

自旋锁(spinlock)busy-waiting:

跟互斥锁类似, 只是资源被占用的时候, 会一直循环检测锁是否被释放(CPU不能做其他的事情)
线程A获取到锁,在释放锁之前,线程B 来获取锁,此时获取不到,线程B就会不断的进入循环,一直检查锁是否已被释放,如果释放,则能获取到锁。
由于调用方会一直循环看该自旋锁的的保持者是否已经释放了资源,节省了唤醒睡眠线程的内核消耗。
但是如果不能短时间获得锁,就会一直占用着CPU,造成效率低下。
(在加锁时间短暂的情况下会大大提高效率)

其他锁都是这两种锁的延伸和扩展。

常见的锁

1、OSSpinLock

自旋锁,会造成优先级反转的问题。当一个低优先级线程获得锁的时候,如果此时一个高优先级的系统到来,那么会进入忙等状态,不会进入睡眠,此时会一直占用着系统CPU时间,导致低优先级的无法拿到CPU时间片,从而无法完成任务也无法释放锁。除非能保证访问锁的线程全部处于同一优先级,否则系统所有的自旋锁都会出现优先级反转的问题。

现在苹果的OSSpinLockiOS10+已废弃,已经被替换成os_unfair_lock。,有兴趣可以看看:不再安全的OSSpinLock

2、os_unfair_lock

互斥锁,iOS10+才支持,为了替代OSSpinLock。

os_unfair_lock 是一种适用于需要简单和轻量级互斥的情况下的锁。

此锁必须从与锁定它的相同线程中解锁,尝试从不同线程解锁将导致断言中止进程。
必须使用 OS_UNFAIR_LOCK_INIT 进行初始化。

使用时需要引入#import <os/lock.h>

// 必须在线程里初始化 且必须使用OS_UNFAIR_LOCK_INIT初始化
os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT); 
os_unfair_lock_lock(unfairLock); // 加锁
......其他操作......
os_unfair_lock_unlock(unfairLock); // 解锁

3、pthread_mutex

互斥锁,等待锁的线程会处于休眠状态。

使用时需要引入头文件#import <pthread.h>

  // 定义互斥锁和互斥锁属性对象
  // 若不特殊设置互斥锁属性对象可以不设置
  // 初始化互斥锁时的第二个参数可以传NULL
    pthread_mutex_t mutex;
    pthread_mutexattr_t mutex_attr;

    // 初始化互斥锁属性对象
    pthread_mutexattr_init(&mutex_attr);
    
    // 设置互斥锁属性为默认类型
    pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_DEFAULT);
    
    // 初始化互斥锁
    pthread_mutex_init(&mutex, &mutex_attr);

    // 加锁
    pthread_mutex_lock(&mutex);
    
    // 访问共享资源
    // 这里省略了共享资源的操作
    
    // 解锁
    pthread_mutex_unlock(&mutex);
    
    // 销毁互斥锁
    pthread_mutex_destroy(&mutex);
    
    // 销毁互斥锁属性对象
    pthread_mutexattr_destroy(&mutex_attr);
#define PTHREAD_MUTEX_NORMAL        0
#define PTHREAD_MUTEX_ERRORCHECK    1
#define PTHREAD_MUTEX_RECURSIVE        2
#define PTHREAD_MUTEX_DEFAULT        PTHREAD_MUTEX_NORMAL

互斥锁类型具体为:
PTHREAD_MUTEX_NORMAL:普通锁。默认类型。不提供死锁检测或递归保护。如果同一线程尝试再次获取锁,可能会导致死锁。
PTHREAD_MUTEX_ERRORCHECK:错误检查锁。提供死锁检测。如果同一线程尝试再次获取锁,函数将返回错误 EBUSY。
PTHREAD_MUTEX_RECURSIVE:递归锁。允许同一线程多次获取锁而不会死锁。必须在解锁时与加锁次数匹配。
PTHREAD_MUTEX_DEFAULT:等同于 PTHREAD_MUTEX_NORMAL。

4、NSLock

互斥锁,遵循NSLocking协议。内部是封装了pthread_mutext,类型是PTHREAD_MUTEXT_ERRORCHECK。

NSLock、NSCondition、NSConditionLock、NSRecursiveLock都实现了NSLocking协议。了解更多查看官方文档

@protocol NSLocking
- (void)lock;
- (void)unlock ;
@end

-lock和-unlock必须在相同的线程调用。

5、NSCondition

互斥锁,也遵循NSLocking协议。内部是封装了pthread_mutext,和它和NSLock的区别是:NSLock在获取不到锁的时候自动使线程进入休眠,锁被释放后线程又自动被唤醒,NSCondition可以使我们更加灵活的控制线程状态,在任何需要的时候使线程进入休眠或唤醒它。

// 让当前线程处于等待状态
- (void)wait;
// 向等待在条件上的一个线程发出信号,可以继续执行
- (void)signal;
//   向等待在条件上的所有线程发出信号,从而唤醒所有线程
- (void)broadcast;
NSCondition *cLock = [NSCondition new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lock];
    NSLog(@"线程1加锁成功");
    [cLock wait];
    NSLog(@"线程1");
    [cLock unlock];

});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lock];
    NSLog(@"线程2加锁成功");
    [cLock wait];
    sleep(1);
    NSLog(@"线程2");
    [cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(2);
    NSLog(@"唤醒一个等待的线程");
    [cLock signal];
});

输出:
线程1加锁成功
线程2加锁成功
唤醒一个等待的线程
线程1

6、NSConditionLock

互斥锁,有条件的互斥锁。

// 只读属性condition,保存锁当前的条件
@property (readonly) NSInteger condition;

// 解锁并设置新的条件值。

-(void)unlockWithCondition:(NSInteger)condition;

// 条件值满足时加锁, 执行之后代码

-(BOOL)lockWhenCondition:(NSInteger)condition;

// 当条件值等于指定值时尝试加锁。

-(BOOL)tryLockWhenCondition:(NSInteger)condition;

NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0]; // 设置初始标志位
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  if ([cLock tryLockWhenCondition:0]) {
      NSLog(@"Task - 1, cLock.condition = %ld",cLock.condition);
      [cLock unlockWithCondition:1];
      NSLog(@"Task - 1, end - cLock.condition = %ld",cLock.condition);

  } else {
      NSLog(@"Task -  加锁失败");
  }
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lockWhenCondition:3];
    NSLog(@"Task - 2, cLock.condition = %ld",cLock.condition);
    [cLock unlockWithCondition:2];
    NSLog(@"Task - 2, end- cLock.condition = %ld",cLock.condition);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lockWhenCondition:1];
    NSLog(@"Task - 3, cLock.condition = %ld",cLock.condition);
    [cLock unlockWithCondition:3];
    NSLog(@"Task - 3, end- cLock.condition = %ld",cLock.condition);

});

输出:
Task - 1, cLock.condition = 0
Task - 1, end - cLock.condition = 1
Task - 3, cLock.condition = 1
Task - 3, end- cLock.condition = 3
Task - 2, cLock.condition = 3
Task - 2, end- cLock.condition = 2

7、NSRecursiveLock

互斥锁中的递归锁。内部使用的pthread_mutex_t的类型是PTHREAD_MUTEXT_RECURSIVE。可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中。

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
NSLock *lo = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    static void (^RecursiveMethod)(int);
    RecursiveMethod = ^(int value) {
        NSLog(@"加锁: %d", value);
        [lock lock]; // 加锁
        if (value < 3) {
            sleep(2);
            RecursiveMethod(++value); // 解锁之前又锁上
        }
        NSLog(@"解锁: %d", value);
        [lock unlock]; // 解锁
    };
    RecursiveMethod(1);
    NSLog(@"finish");
});

输出:
加锁: 1
加锁: 2
加锁: 3
解锁: 3
解锁: 3
解锁: 2
finish

8、@synchronized

@synchronized(id)的使用应该是较多的,它底层实现是个递归锁,不会产生死锁,且不需要手动去加锁解锁,使用起来比较方便。

@synchronized(self) {  
    //数据操作  
}

9、atomic

修饰属性关键字,对被修饰的进行原子操作(不负责使用,只是setter/getter方法安全,其他不保证)点击属性关键字-atomic了解更多。

10、dispatch_semaphore_t

是GCD用来同步的一种方式。

GCD 信号量dispatch_semaphore可以用来控制最大并发数量,可以用来实现 iOS 的线程同步方案。
信号量的初始值,可以用来控制线程并发访问的最大数量;
信号量的初始值为1,代表同时只允许 1 条线程访问资源,保证线程同步。
若设置信号量初始值为3.则控制最大并发数为3。

// 创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_create

// 发送一个信号,让信号总量加 1
dispatch_semaphore_signal

// 可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
dispatch_semaphore_wait

点击iOS:多线程,了解更多信号量相关信息。

锁的关系及性能

锁之间的关系

性能排序(从高到低):

1、os_unfair_lock

2、OSSpinLock(已经不推荐使用)

3、dispatch_semaphore

4、pthread_mutext

5、NSLock

6、NSCondition

7、NSRecursiveLock

8、NSConditonLock

9、@synchronized

死锁

指多个进程因竞争共享资源而造成的一种僵局,若无外力作用,这些进程都将永远不能再向前推进。

死锁的条件

1、互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。

2、占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。

3、不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来,只能在使用完时由自己释放。

4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

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

推荐阅读更多精彩内容

  • 前言 iOS开发中由于各种第三方库的高度封装,对锁的使用很少,刚好之前面试中被问到的关于并发编程锁的问题,都是一知...
    iOS__开发者皮皮峰阅读 556评论 1 5
  • 前言 iOS开发中由于各种第三方库的高度封装,对锁的使用很少,刚好之前面试中被问到的关于并发编程锁的问题,都是一知...
    KingWorld阅读 1,074评论 0 0
  • 多线程需要一种互斥的机制来访问共享资源。 一、 互斥锁 互斥锁的意思是某一时刻只允许一个线程访问某一资源。为了保证...
    doudo阅读 734评论 0 5
  • 前言 iOS开发中由于各种第三方库的高度封装,对锁的使用很少,刚好之前面试中被问到的关于并发编程锁的问题,都是一知...
    喵渣渣阅读 3,700评论 0 33
  • 1.概述 1.1 类型 自旋锁 OSSpinLock 信号量 dispatch_semaphore 互斥锁 ...
    George_Luofz阅读 362评论 0 0