iOS多线程编程中,经常碰到多个线程访问共同的一个资源,在线程相互交互的情况下,需要一些同步措施,来保证线程之间交互的时候是安全的。下面我们一起看一下学一下iOS的几种常用的加锁方式,希望对大家有所帮助!!!

  1. @synchronized
  2. NSLock对象锁
  3. NSRecursiveLock递归锁
  4. NSConditionLock条件锁
  5. dispatch_semaphore 信号量实现加锁(也就是GCD)
  6. OSSpinLock 与 os_unfair_lock
  7. pthread_mutex

介绍与使用

1.@synchronized

@synchronized关键字加锁,互斥锁,性能较差不推荐在项目中使用。

@synchronized(这里添加一个OC对象,一般使用self) {
这里写要加锁的代码
}
注意点
.加锁的代码要尽量少
2.添加的OC对象必须在多个线程中都是同一个对象
3.它的优点是不需要显式的创建锁对象,便可以实现锁的机制。
4. @synchronized块会隐式的添加异常处理例程来保护代码,该处理例程会在异常抛出的时候就会自动  的释放互斥锁。如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。

下面我们以一个最经典的例子:卖票

//设置票的数量为5
_tickets = ; //线程1
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
}); //线程2
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
}); - (void)saleTickets
{
while () {
@synchronized(self) {
[NSThread sleepForTimeInterval:];
if (_tickets > ) {
_tickets--;
NSLog(@"剩余票数= %ld, Thread:%@",_tickets,[NSThread currentThread]);
} else {
NSLog(@"票卖完了 Thread:%@",[NSThread currentThread]);
break;
}
}
}
}

2.NSLock

基本所有锁的接口都是通过NSLocking协议定义的,定义了lock和unlock方法,通过这些方法获取和释放锁。NSLock是对mutex普通锁的封装

下面还是以卖票的例子讲述一下。

//设置票的数量为5
_tickets = ; //创建锁
_mutexLock = [[NSLock alloc] init]; //线程1
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
}); //线程2
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
}); - (void)saleTickets
{ while () {
[NSThread sleepForTimeInterval:];
//加锁
[_mutexLock lock];
if (_tickets > ) {
_tickets--;
NSLog(@"剩余票数= %ld, Thread:%@",_tickets,[NSThread currentThread]);
} else {
NSLog(@"票卖完了 Thread:%@",[NSThread currentThread]);
break;
}
//解锁
[_mutexLock unlock];
}
}

3.NSRecursiveLock递归锁

使用锁比较容易犯的错误是在递归或者循环中造成死锁。

如下代码锁会被多次lock,造成自己被阻塞。

//创建锁
_mutexLock = [[NSLock alloc]init]; //线程1
dispatch_async(self.concurrentQueue, ^{
static void(^TestMethod)(int);
TestMethod = ^(int value)
{
[_mutexLock lock];
if (value > )
{
[NSThread sleepForTimeInterval:];
TestMethod(value--);
}
[_mutexLock unlock];
}; TestMethod();
});

如果把这个NSLock换成NSRecursiveLock,就可以解决问题。

NSRecursiveLock类定义的锁,可以在同一线程多次lock,不会造成死锁。

//创建锁
_rsLock = [[NSRecursiveLock alloc] init]; //线程1
dispatch_async(self.concurrentQueue, ^{
static void(^TestMethod)(int);
TestMethod = ^(int value)
{
[_rsLock lock];
if (value > )
{
[NSThread sleepForTimeInterval:];
TestMethod(value--);
}
[_rsLock unlock];
}; TestMethod();
});

4.NSConditionLock条件锁

NSMutableArray *products = [NSMutableArray array];
NSInteger HAS_DATA = ;
NSInteger NO_DATA = ;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
while () {
[lock lockWhenCondition:NO_DATA];
[products addObject:[[NSObject alloc] init]];
NSLog(@"produce a product,总量:%zi",products.count);
[lock unlockWithCondition:HAS_DATA];
sleep();
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
while () {
NSLog(@"wait for product");
[lock lockWhenCondition:HAS_DATA];
[products removeObjectAtIndex:];
NSLog(@"custome a product");
[lock unlockWithCondition:NO_DATA];
}
});

在线程1中的加锁使用了lock,所以是不要条件的,也就锁住了。但在unlock的使用整型条件,它可以开启其他线程中正在等待钥匙的临界池,当线程1循环到一次的时候,打开了线程2的阻塞。

NSCoditionLock中lock,lockWhenCondition:与unlock,unlockWithCondition:是可以随意组合的,具体使用根据需求来区分。

NSCoditionLock 是对NSCodition的进一步封装,可以设置具体的条件值,而NSCodition是对mutex和cond的封装---看本篇博客7.3 条件锁

5.dispatch_semaphore信号量实现加锁

dispatch_semaphore_t signal = dispatch_semaphore_create();
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
dispatch_semaphore_wait(signal, overTime);
NSLog(@"需要线程同步的操作1 开始");
sleep();
NSLog(@"需要线程同步的操作1 结束");
dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
sleep();
dispatch_semaphore_wait(signal, overTime);
NSLog(@"需要线程同步的操作2");
dispatch_semaphore_signal(signal);
});

dispatch_semaphore是GCD用于同步的方式,与之相关的共有三个函数,dispatch_semaphore_wait,dispatch_semaphore_signal,dispatch_semaphore_create。

(1)dispatch_semaphore_create的声明为:

dispatch_semaphore_t dispatch_semaphore_create(long value);

传入的参数是long类型,输出一个dispatch_semaphore_t类型值为Value的信号量(value传入值不能小于0,否则会报错NULL)

(2)dispatch_semaphore_signal声明为下面:

long dispatch_semaphore_signal(dispatch_semaphore_t dsema); 

这个方法会使dsema加1;

(3)dispatch_semaphore_wait的声明为下面:

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

这个方法会使dsema减1。

整个逻辑如下:

如果dsema信号量值为大于0,该函数所在线程就会继续执行下面的语句,并将信号量的减去1;如果dsema为0时,函数就会阻塞当前的线程,如果等待的期间发现dsema的值被dispatch_semaphore_signal加1了,并且该函数得到了信号量,那么继续向下执行,并将信号量减1,如果等待期间没有获得信号量或者值一直为0,那么等到timeout,所处的线程也会自动执行下面的代码。

dispatch_semaphore,当信号量为1时,可以作为锁使用。如果没有出现等待的情况,它的性能比pthread_mutex还要高,当如果有等待情况的时候,性能就会下降很多,相比OSSpinLock(暂不讲解),它的优势在于等待的时侯不会消耗CPU资源。

针对上面代码,发现如果超时时间overTime>2,可完成同步操作,反之,在线程1还没有执行完的情况下,此时超时了,将自动执行下面的代码。

上面代码执行结果:

-- ::52.324 SafeMultiThread[:] 需要线程同步的操作1 开始
-- ::52.325 SafeMultiThread[:] 需要线程同步的操作1 结束
-- ::52.326 SafeMultiThread[:] 需要线程同步的操作2

如果将overTime<2s的时候,执行为

-- ::52.049 SafeMultiThread[:] 需要线程同步的操作1 开始
-- ::52.554 SafeMultiThread[:] 需要线程同步的操作2
-- ::52.054 SafeMultiThread[:] 需要线程同步的操作1 结束

6.OSSpinLock自旋锁与os_unfair_lock

6.1   OSSpinLock

OSSpinLock叫做“自旋锁”,自旋锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源

但是目前是不再安全了,在iOS 10之后弃用啦,如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。

在使用的过程中需要导入头文件#import <libkern/OSAtomic.h>

//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回True)
bool result = OSSpinLockTry(&lock);
//加锁
OSSpinLockLock(&lock);
//解锁
OSSpinLockUnlock(&lock)

6.2 os_unfair_lock互斥锁

os_unfair_lock用于取代不安全的OSSpinLock,从iOS10开始才支持。从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等

os_unfair_lock需要导入头文件#import<os/lock.h>

    //初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//尝试加锁
os_unfair_lock_trylock(&lock);
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);

7.pthread_mutex

mutex叫做“互斥锁”,等待锁的线程处于休眠状态

需要导入头文件#import <pthread.h>

7.1

    //初始化锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); //初始化锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr); //尝试加锁
pthread_mutex_trylock(&mutex); //加锁
pthread_mutex_lock(&mutex); //解锁
pthread_mutex_unlock(&mutex); //销毁相关资源 --pthread_mutex在对象类释放的时候要销毁,其他锁无此情况
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);

7.2 pthread_mutex-递归锁

     //初始化锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_t_int(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//递归锁 //初始化锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);

7.3 pthread_mutex-条件

#import "MutexDemo3.h"

#import <pthread.h>

@interface MutexDemo3()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end @implementation MutexDemo3 - (instancetype)init
{
if (self = [super init]) {
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(&_mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr); // 初始化条件
pthread_cond_init(&_cond, NULL); self.data = [NSMutableArray array];
}
return self;
} - (void)otherTest
{
[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
} // 生产者-消费者模式 // 线程1
// 删除数组中的元素
- (void)__remove
{
pthread_mutex_lock(&_mutex);
NSLog(@"__remove - begin"); if (self.data.count == ) {
// 等待
pthread_cond_wait(&_cond, &_mutex);
} [self.data removeLastObject];
NSLog(@"删除了元素"); pthread_mutex_unlock(&_mutex);
} // 线程2
// 往数组中添加元素
- (void)__add
{
pthread_mutex_lock(&_mutex); sleep(); [self.data addObject:@"Test"];
NSLog(@"添加了元素"); // 信号
pthread_cond_signal(&_cond);
// 广播
// pthread_cond_broadcast(&_cond); pthread_mutex_unlock(&_mutex);
} - (void)dealloc
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
} @end

补充:自旋锁、互斥锁比较

1. 什么情况下使用自旋锁比较划算?(OSSpinLock,但被os_unfair_lock取代,但是os_unfair_lock不是自旋锁)

  • 预计线程等待锁的时间很短
  • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
  • CPU资源不紧张
  • 多核处理器

2. 什么情况下使用互斥锁比较划算?(pthread_mutex, NSLock等)

  • 预计线程等待锁的时间较长
  • 单核处理器
  • 临界区有IO操作
  • 临界区代码复杂或者循环量大
  • 临界区竞争非常激烈

以上就是自己在开发中所经常使用到的加锁方式,希望对大家有所帮助!!!

iOS 加锁的方式的更多相关文章

  1. iOS页面传值方式

    普遍传值方式如下: 1.委托delegate方式: 2.通知notification方式: 3.block方式: 4.UserDefault或者文件方式: 5.单例模式方式: 6.通过设置属性,实现页 ...

  2. iOS数据持久化方式及class_copyIvarList与class_copyPropertyList的区别

    iOS数据持久化方式:plist文件(属性列表)preference(偏好设置)NSKeyedArchiver(归档)SQLite3CoreData沙盒:iOS程序默认情况下只能访问自己的程序目录,这 ...

  3. IOS 网络请求方式

    iOS开发中的网络请求   今天来说说关于iOS开发过程中的网络请求. 关于网络请求的重要性我想不用多说了吧.对于移动客户端来说,网络的重要性不言而喻.常见的网络请求有同步GET, 同步POST, 异 ...

  4. IOS以无线方式安装企业内部应用(开发者)

    请先阅读:http://help.apple.com/deployment/ios/#/apda0e3426d7 操作系统:osx yosemite 10.10.5 (14F1509) xcode:V ...

  5. Java 多线程加锁的方式总结及对比(转载)

    转自https://blog.csdn.net/u010842515/article/details/67634813 参考博文:http://www.cnblogs.com/handsomeye/p ...

  6. iOS -数据持久化方式-以真实项目讲解

    前面已经讲解了SQLite,FMDB以及CoreData的基本操作和代码讲解(CoreData也在不断学习中,上篇博客也会不断更新中).本篇我们将讲述在实际开发中,所使用的iOS数据持久化的方式以及怎 ...

  7. IOS渠道追踪方式

    本文来自网易云社区 作者:马军 IOS,安卓渠道追踪的差异 Google Play国内不可用,国内的安卓 App 分发,都是依托几十个不同的应用市场或发行渠道,如百度.360.腾讯等互联网企业以及小米 ...

  8. 最新iOS砸壳方式Frida (Mac OSX)

    1. 安装Frida 首先需要安装Python3,我下载的是 macOS 64-bit installer 安装,因Macbook本机自带python为2.7.x,故需要配置~/.bash_profi ...

  9. IOS自动化定位方式

    原文地址http://blog.csdn.net/wuyepiaoxue789/article/details/77885136 元素属性的介绍 type:元素类型,与className作用一致,如: ...

随机推荐

  1. thinkphp5使用空模块

    今天想做一个功能,可以后台设置url是二级域名(也是指向同一个服务器)还是一级域名(域名/模块),网上找了找,TP3.2开始取消了空模块.所以只能自己修改框架源码了. ----------有点晚,明天 ...

  2. CVE-2018-20129:DedeCMS V5.7 SP2前台文件上传漏洞

    一.漏洞摘要 漏洞名称: DedeCMS V5.7 SP2前台文件上传漏洞上报日期: 2018-12-11漏洞发现者: 陈灿华产品首页: http://www.dedecms.com/软件链接: ht ...

  3. 过滤html标签

    public static String delHTMLTag(String htmlStr){ String regEx_script="<script[^>]*?>[\ ...

  4. MVVM简介与运用

    在介绍MVVM框架之前,先给大家简单介绍一下MVC.MVP框架(由于本博文主要讲解MVVM,所以MVC和MVP将简化介绍,如果需要我将在以后的博文中补充进来). MVC框架: M-Model : 业务 ...

  5. APP研发录笔记

    一.消灭全局变量 在内存不足时,系统会回收一部分闲置的资源,由于App被切换到后台,所以之前存放的全局变量很容易被回收,这时再切换到前台继续使用,会报空指针崩溃.想彻底解决这个问题,就要使用序列化. ...

  6. Java中的队列同步器AQS

    一.AQS概念 1.队列同步器是用来构建锁或者其他同步组件的基础框架,使用一个int型变量代表同步状态,通过内置的队列来完成线程的排队工作. 2.下面是JDK8文档中对于AQS的部分介绍 public ...

  7. FFmpeg开发实战(四):FFmpeg 抽取音视频的音频数据

    如何使用FFmpeg抽取音视频的音频数据,代码如下: void adts_header(char *szAdtsHeader, int dataLen); // 使用FFmpeg从视频中抽取音频 vo ...

  8. Build Assimp library for Android

    Build Assimp library for Android 首先各路教程中有推荐使用 NDK 或者 STANDALONE TOOLCHAIN 编译的,根据我的理解,这两种方式都是可以的,如果能直 ...

  9. 如何解决http请求返回结果中文乱码

    如何解决http请求返回结果中文乱码 1.问题描述 http请求中,请求的结果集中包含中文,最终以乱码展示. 2.问题的本质 乱码的本质是服务端返回的字符集编码与客户端的编码方式不一致. 场景的如服务 ...

  10. [SDOI2018] 旧试题

    推狮子的部分 \[ \sum_{i=1}^A\sum_{j=1}^B\sum_{k=1}^C\sigma(ijk) =\sum_{i=1}^A\sum_{j=1}^B\sum_{k=1}^C\sum_ ...