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. LeetCode笔记:39. Combination Sum

    题目描述 给定一个无重复的正整数数组 candidates 和一个正整数 target, 求所有和为 target 的 candidates 中数的组合中.其中相同数的不同顺序组合算做同一种组合,ca ...

  2. VUE 出现Access to XMLHttpRequest at 'http://192.168.88.228/login/Login?phone=19939306484&password=111' from origin 'http://localhost:8080' has been blocked by CORS policy: The value of the 'Access-Contr

    报错如上图!!!!    解决办法首先打开 config -> index.js ,粘贴 如下图代码,'https://www.baidu.com'换成要访问的的api域名,注意只要域名就够了, ...

  3. python Ajax

    Ajax一.准备知识JSON1.什么是json JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation) JSON 是轻量级的文本数据交换格式 JSO ...

  4. [安卓] 20、基于蓝牙BLE的广播包高频快速搜索

    前言: 之前介绍过很多蓝牙beacon.搜索.连接.通讯的文章.不过最近我发现:之前写的蓝牙广播包搜索的工程,搜索频率太慢,而且不能一直保持搜索状态.因此,这里探讨下高频蓝牙广播包扫描 -- 蓝牙BL ...

  5. vue项目实践-前后端分离关于权限的思路

    前后端分离后权限的思路 最近看到许多关于权限的思路,但好像都是使用动态加载路由的方式,现在也分享下我在项目中使用的解决方案. 前后端分离关于权限的处理每个人都不一样,根据项目选择制定合适的方案就好 我 ...

  6. 【DFS】困难的串

    题目: 问题描述:如果一个字符串包含两个相邻的重复子串,则称它为容易的串,其他串称为困难的串.如:BB,ABCDACABCAB,ABCDABCD都是容易的,A,AB,ABA,D,DC,ABDAB,CB ...

  7. git克隆项目出现remote: HTTP Basic: Access denied

    换新电脑,重新装了git,从gitlab上面拉公司项目,出现了remote: HTTP Basic: Access denied错误,说验证失败,百度很多说了很多答案,最后试了这种可以,成功拉下来项目 ...

  8. 机器学习入门17 - 嵌套 (Embedding)

    原文链接:https://developers.google.com/machine-learning/crash-course/embeddings/ 嵌套是一种相对低维的空间,可以将高维矢量映射到 ...

  9. Kubernetes 笔记 06 豌豆荚之旅(一)

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. Hi,大家好, ...

  10. java中过多if-else分支语句的优化方案

    利用Map优化过的的if-else分支 package com.taiping.test; import java.util.HashMap;import java.util.Map; public ...