iOS: 线程中那些常见的锁
一、介绍
在多线程开发中,锁的使用基本必不可少,主要是为了解决资源共享时出现争夺而导致数据不一致的问题,也就是线程安全问题。锁的种类很多,在实际开发中,需要根据情况选择性的选取使用,毕竟使用锁也是消耗CPU的。 本人虽然一直有使用多线程进行开发,但是对于锁的使用和理解并不是特别的深入,这不看到一篇挺mark的博客:https://www.jianshu.com/p/a236130bf7a2,在此基础上稍添加点东西转载过来(尊重原创),一是为了记录便于随时翻阅,而是为了写一遍加深印象,知识都是一个copy和attract的过程。
二、种类
1、互斥锁
概念:对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
- 抢到锁的线程先执行,没有抢到锁的线程就会被挂起等待。
- 等锁用完后需要释放,然后其它等待的线程再去抢这个锁,那个线程抢到就让那个线程再执行。
- 具体哪个线程抢到这个锁是由cpu调度决定的。
常用:
@synchronized:同步代码块
example:执行操作
/**
*设置属性值
*/
-(void)setMyTestString:(NSString *)myTestString{
@synchronized(self) {
// todo something
_myTestString = myTestString;
}
}
example:创建单例
//注意:此时为了保证单例模式的更加严谨,需要重写`allocWithZone`方法,保证其他开发者使用`alloc`和`init`方法时,不再创建新的对象。必要的时候还需要重写`copyWithZone`方法防止`copy`属性对单例模式的影响。 iOS中还有一种更加轻便的方法实现单例模式,即使用GCD中的dispatch_once函数实现。
+(instancetype)shareInstance{
// 1.定义一个静态实例,初值nil
static TestSynchronized *myClass = nil;
// 2.添加同步锁,创建实例
@synchronized(self) {
// 3.判断实例是否创建过,创建过则退出同步锁,直接返回该实例
if (!myClass) {
// 4.未创建过,则新建一个实例并返回
myClass = [[self alloc] init];
}
}
return myClass;
}
NSLock:不能迭代加锁,如果发生两次lock,而未unlock过,则会产生死锁问题。
example:执行操作
///定义一个静态锁变量, lock--unlock 、tryLuck---unLock 必须成对存在
static NSLock *mylock;
-(void)viewDidLoad {
[super viewDidLoad];
mylock = [[NSLock alloc] init];
} //当前线程锁失败,也可以继续其它任务,用 trylock 合适
-(void)myLockTest1{
if ([mylock tryLock]) {
// to do something
[mylock unlock];
}
} //当前线程只有锁成功后,才会做一些有意义的工作,那就lock,没必要轮询trylock
-(void)myLockTest2{
[mylock lock];
// to do something
[mylock unlock];
}
2、递归锁
概念:递归锁可以被同一线程多次请求,而不会引起死锁,即在多次被同一个线程进行加锁时,不会造成死锁,这主要是用在循环或递归操作中。
- 递归锁会跟踪它被lock的次数,每次成功的lock都必须平衡调用unlock操作。
- 只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。
常用:
NSRecursiveLock: 递归锁
example: 异步执行block
//创建递归锁
NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
static void (^MyRecursiveLockBlock)(int value);
MyRecursiveLockBlk = ^(int value){
[myRecursiveLock lock];
if (value > ) {
// to do something
NSLog(@"MyRecursiveLockBlk value = %d", value);
MyRecursiveLockBlock(value - );
}
[myRecursiveLock unlock];
};
MyRecursiveLockBlock();
}); //注意:此时如果将例程中的递归锁换成互斥锁:
//NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];换成
//NSLock *myLock = [[NSLock alloc] init];,则会发生死锁问题。
3、读写锁
概念:读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。
读写锁将访问者分为读出和写入两种,当读写锁在读加锁模式下,所有以读加锁方式访问该资源时,都会获得访问权限,而所有试图以写加锁方式对其加锁的线程都将阻塞,直到所有的读锁释放。
当在写加锁模式下,所有试图对其加锁的线程都将阻塞。
常用:
pthread_rwlock_t(读写锁)、 pthread_rwlock_wrlock(写锁)、 pthread_rwlock_rdlock(读锁)
example: 异步读写数据
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property(nonatomic, copy) NSString *rwStr;
@end @implementation ViewController ///全局的读写锁
pthread_rwlock_t rwlock; -(void)viewDidLoad {
[super viewDidLoad];
// 初始化读写锁
pthread_rwlock_init(&rwlock,NULL);
__block int i;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
i = ;
while (i>=) {
NSString *temp = [NSString stringWithFormat:@"writing == %d", i];
[self writingLock:temp];
i--;
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
i = ;
while (i>=) {
[self readingLock];
i--;
}
});
}
// 写加锁
-(void)writingLock:(NSString *)temp{
pthread_rwlock_wrlock(&rwlock);
// writing
self.rwStr = temp;
NSLog(@"%@", temp);
pthread_rwlock_unlock(&rwlock);
}
// 读加锁
-(NSString *)readingLock{
pthread_rwlock_rdlock(&rwlock);
// reading
NSString *str = self.rwStr;
NSLog(@"reading == %@",self.rwStr);
pthread_rwlock_unlock(&rwlock);
return str;
}
@end
4、自旋锁
概念:它是一种忙等的锁,适用于轻量访问。自旋锁是非阻塞的,当一个线程无法获取自旋锁时,会自旋,直到该锁被释放,等待的过程中线程并不会挂起。(实质上就是,如果自旋锁已经被别的执行单元保持,调用者就一直循环在等待该自旋锁的保持着已经释放了锁)。
自旋锁的使用者一般保持锁的时间很短,此时其效率远高于互斥锁
自旋锁保持期间是抢占失效的
- 优点:不用进行线程的切换
- 缺点:如果一个线程霸占锁的时间过长,自旋会消耗CPU资源
常用:
OSSpinLock:自旋锁
example: 执行操作
// 头文件
#import <libkern/OSAtomic.h>
// 初始化自旋锁
static OSSpinLock myLock = OS_SPINLOCK_INIT;
// 自旋锁的使用
-(void)SpinLockTest{
OSSpinLockLock(&myLock);
// to do something
OSSpinLockUnlock(&myLock);
}
5、分布锁
概念:跨进程的分布式锁,是进程间同步的工具,底层是用文件系统实现的互斥锁,并不强制进程休眠,而是起到告知的作用。
- 它没有实现NSLocking协议,所以没有会阻塞线程的lock方法,取而代之的是非阻塞的tryLock方法来获取锁,用unLock方法释放锁。
- 如果一个获取锁的进程在释放锁之前就退出了,那么锁就一直不能释放,此时可以通过breakLock强行获取锁。
常用:
NSDistributedLock:自旋锁
example: 执行操作
//给文件创建分布锁
NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/lock.lock"];
while (![lock tryLock])
{
sleep();
} //do something [lock unlock]; //但在实际使用过程中,当执行到do something时程序退出,程序再次启动之后tryLock就再也不能成功了,陷入死锁状态.这是使用NSDistributedLock时非常隐蔽的风险.其//实要解决的问题就是如何在进程退出时会自动释放锁.
6、条件变量
概念:与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
一个线程需要等待某一条件出现才能继续执行,而这个条件是由别的线程产生的,这个时候就用到条件变量。常见的情况是:生产者-消费者问题。
条件变量可以让一个线程等待某一条件,当条件满足时,会收到通知。在获取条件变量并等待条件发生的过程中,也会产生多线程的竞争,所以条件变量通常和互斥锁一起工作。
常用:
NSCondition:是互斥锁和条件锁的结合,即一个线程在等待signal而阻塞时,可以被另一个线程唤醒,由于操作系统实现的差异,即使没有发送signal消息,线程也有可能被唤醒,所以需要增加谓词变量来保证程序的正确性。
example: 执行操作
// 创建锁
NSCondition *condition = [[NSCondition alloc] init];
static int count = ;
// 生产者
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
while(count<)
{
[condition lock];
// 生产
count ++;
NSLog(@"生产 = %d",count);
[condition signal];
[condition unlock];
}
});
// 消费者
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
while (count>)
{
[condition lock];
// 消耗
count --;
NSLog(@"消耗剩余 = %d",count);
[condition unlock];
}
});
NSConditionLock:与NSCondition的实现机制不一样,当定义的条件成立的时候会获取锁,反之,释放锁。
example: 执行操作
// 创建锁
NSConditionLock *condLock = [[NSConditionLock alloc] initWithCondition:ConditionHASNOT];
static int count = ;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
// 生产者
while(true)
{
[condLock lock];
// 生产
count ++;
[condLock unlockWithCondition:ConditionHAS];
}
} dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
// 消费者
while (true)
{
[condLock lockWhenCondition:ConditionHAS];
// 消耗
count --;
[condLock unlockWithCondition:(count<= ? ConditionHASNOT : ConditionHAS)];
}
}
7、信号量
概念:信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源的同步访问。临界资源可以简单的理解为在某一时刻只能由一个进程或线程进行操作的资源,这里的资源可以是一段代码、一个变量或某种硬件资源。
- 信号量:可以是一种特殊的互斥锁,可以是资源的计数器
- 可以使用GCD中的Dispatch Semaphore实现,Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。计数为0时等待,计数大于等于1时,减1为不等待。
常用:
dispatch_semaphore_t(信号)、dispatch_semaphore_signal(持有信号)、diapatch_semaphore_wait(释放信号)
example: 执行操作
//创建信号
dispatch_semaphore_t semaphore = dispatch_semaphore_create(); [self getTasksWithCompletionHandler:^ { //doing something //持有信号
dispatch_semaphore_signal(semaphore);
}]; //释放信号
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
8、栅栏/屏障(barrier)
概念:dispatch_barrier_async函数的作用与barrier的意思相同,在进程管理中起到一个栅栏的作用,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行,该函数需要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用。
- 栅栏必须单独执行,不能与其他任务并发执行,栅栏只对并发队列有意义。
- 栅栏只有等待当前队列所有并发任务都执行完毕后,才会单独执行,带起执行完毕,再按照正常的方式继续向下执行。
常用:
dispatch_barrier_async:异步栅栏函数
example: 多读单写(读读并发、读写互斥、写写互斥)
- (id)objectForKey:(NSString *)key { __block id obj;
//同步读取指定数据
dispatch_sync(concurrent_queue, ^{
obj = [userCenterDic objectForKey:key];
}); return obj;
} -(void)setObject:(id )obj foeKey:(NSString *)key{ //异步栅栏调用设置数据
dispatch_async(concurrent_queue, ^{
[userCenterDic setObject:obj forKey:key];
});
}
9、pthread_mutex
概念:C语言定义下的多线程加锁方式,在很多OC对象的底层结构中,可以看到pthread_mutex使用的还是很受苹果官方推荐的。
用法:
- pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t attr); 初始化锁变量mutex,attr为锁属性,NULL值为默认属性。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 宏初始化锁变量mutex
- pthread_mutexattr_settype(pthread_mutexattr_t attr, int type); 设置锁类型
- pthread_mutex_lock(pthread_mutex_t* mutex);加锁
- pthread_mutex_tylock(pthread_mutex_t* mutex);加锁,但是与2不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待。
- pthread_mutex_unlock(pthread_mutex_t* mutex);释放锁
- pthread_mutex_destroy(pthread_mutex_t* *mutex);使用完后释放
常用:
pthread_mutex:互斥锁
example:
//创建锁
__block pthread_mutex_t theLock;
pthread_mutex_init(&theLock, NULL); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
pthread_mutex_lock(&theLock);
NSLog(@"需要线程同步的操作1 开始");
sleep();
NSLog(@"需要线程同步的操作1 结束");
pthread_mutex_unlock(&theLock);
}); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
sleep();
pthread_mutex_lock(&theLock);
NSLog(@"需要线程同步的操作2");
pthread_mutex_unlock(&theLock);
});
pthread_mutex(recursive):递归锁
example:
//注意:这是pthread_mutex为了防止在递归的情况下出现死锁而出现的递归锁。作用和NSRecursiveLock递归锁类似。
//如果使用pthread_mutex_init(&theLock, NULL)初始化锁的话,下面的代码会出现死锁现象,但是改成使用递归锁的形式,则没有问题。 //创建锁
__block pthread_mutex_t theLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置成递归类型
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value) {
pthread_mutex_lock(&theLock);
if (value > ) {
NSLog(@"value = %d", value);
sleep();
RecursiveMethod(value - );
}
pthread_mutex_unlock(&theLock);
};
RecursiveMethod();
});
三、性能
- No1.自旋锁OSSpinLock耗时最少
- No2.pthread_mutex
- No3.NSLock、NSCondition、NSRecursiveLock耗时接近
- No4. @synchronized
- No5. NSConditionLock
- 栅栏的性能并没有很好,在实际开发中也很少用到。
自旋锁是线程不安全的在 ibireme 的 不再安全的 OSSpinLock有解释,进一步的ibireme在文中也有提到苹果在新系统中已经优化了 pthread_mutex 的性能,所以它看上去和 OSSpinLock 差距并没有那么大。
iOS: 线程中那些常见的锁的更多相关文章
- iOS开发中多线程间关于锁的使用
为什么需要使用锁,当然熟悉多线程的你,自然不会感到陌生. 那你在代码中是否很好的使用了锁的机制呢?你又知道几种实现锁的方法呢? main.m 1 int main(int argc, const ch ...
- iOS开发中一些常见的并行处理
本文主要探讨一些常用多任务的最佳实践.包括Core Data的多线程访问,UI的并行绘制,异步网络请求以及一些在运行态内存吃紧的情况下处理大文件的方案等.??其实编写异步处理的程序有很多坑!所以,本文 ...
- iOS开发中一些常见的并行处理(转)
本文主要探讨一些常用多任务的最佳实践.包括Core Data的多线程访问,UI的并行绘制,异步网络请求以及一些在运行态内存吃紧的情况下处理大文件的方案等. 其实编写异步处理的程序有很多坑!所以,本文 ...
- C++线程中的几种锁
线程之间的锁有:互斥锁.条件锁.自旋锁.读写锁.递归锁.一般而言,锁的功能越强大,性能就会越低. 1.互斥锁 互斥锁用于控制多个线程对他们之间共享资源互斥访问的一个信号量.也就是说是为了避免多个线程在 ...
- 线程中的wait() 与 锁的关系
我们先看一段代码: /** * 计算输出其他线程锁计算的数据 * */ public class ThreadA { public static void main(String[] args) th ...
- [多线程] 线程中的synchronized关键字锁
为什么要用锁? 在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实 ...
- iOS 项目中的常见文件
iOS的笔记-项目中的常见文件 新建一个项目之后,有那么多的文件,下面介绍一下主要的几个. 1.文件名 (1)AppDelegate UIApplication的代理,app收到干扰的时候,进行处 ...
- Java线程并发中常见的锁
随着互联网的蓬勃发展,越来越多的互联网企业面临着用户量膨胀而带来的并发安全问题.本文着重介绍了在java并发中常见的几种锁机制. 1.偏向锁 偏向锁是JDK1.6提出来的一种锁优化的机制.其核心的思想 ...
- Java线程并发中常见的锁--自旋锁 偏向锁
随着互联网的蓬勃发展,越来越多的互联网企业面临着用户量膨胀而带来的并发安全问题.本文着重介绍了在java并发中常见的几种锁机制. 1.偏向锁 偏向锁是JDK1.6提出来的一种锁优化的机制.其核心的思想 ...
随机推荐
- MySQL数据库~~~~ 完整性约束
1. not null 与 default not null : 不可空 default : 默认值 例: create table t1(id int not null default 2); 2. ...
- 10. Vue - axios
一.预备知识 1. JS面向对象 特点:ES5之前用构造函数方式,构造函数就是一个普通函数,它的函数名大写. 构造函数的问题:方法不会提升至构造函数内,而是每创建一个对象,就要把那个方法保存在每个对象 ...
- String对象及正则表达式
1,String和string还是有区别的,一个就是用双引号或单引号包括起来的数据就是字符串,另一个本质是数组多个字符串组成的只读字符数组: 2,你说string他是数组吧,他和数组还是有点区别的,他 ...
- 使用matplotlib.pyplot中scatter()绘制散点图
1.二维散点图 二维散点图的函数原型: matplotlib.pyplot.scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=Non ...
- go语言之指针
package main import "fmt" //指针 //go语言的指针是非常容易学习的,比c中容易很多,他可以更简单的执行一些任务 //与变量类型,使用前需要定义 fun ...
- 深入理解 Java 数组
- IMP-00009: abnormal end of export file解决方案
一.概述 最近在测试环境的一个oracle数据库上面,使用exp将表导出没有问题,而将导出的文件使用imp导入时却出现了如下错误. IMP-00009: abnormal end of export ...
- 执行超大的.sql文件处理,100M++
sqlcmd的命令参数如下: 1 [-U 登录 ID] [-P 密码] 2 [-S 服务器] [-H 主机名] [-E ...
- 设计院老师良心汇总:值得牢记的15个CAD基础技巧,能帮大忙
哈喽!你们的CAD魔鬼(老师)来喽! 良心CAD技巧汇总,设计院师傅精心汇总,值得你牢记的15个CAD基础技巧,满满的都是干货,日常最常见的问题以及解决方法这里都汇总给你,给你高效的绘图体验,关键时刻 ...
- vscode自动修复eslint规范的插件及配置
在开发大型项目中,经常都是需要多人合作的.相信大家一定都非常头疼于修改别人的代码的吧,而合理的使用eslint规范可以让我们在代码review时变得轻松,也可以让我们在修改小伙伴们的代码的时候会更加清 ...