iOS中的几种锁的总结,三种开启多线程的方式(GCD、NSOperation、NSThread)
学习内容
欢迎关注我的iOS学习总结——每天学一点iOS:https://github.com/practiceqian/one-day-one-iOS-summary
OC中的几种锁
为什么要引入锁:在多线程编程中,并发会使多个线程在同一时间内争抢同一资源,因此可能造成资源的数据不一致性,为了解决这个问题就引入了锁
自旋锁:自旋锁在访问被加锁资源时,调用者线程不会进行休眠,而是不停循环在那里,等待被锁资源被释放(不断循环的状态也叫做忙等状态)
互斥锁:互斥锁在访问被加锁资源时,调用者线程会进如休眠状态,此时cpu会调用其他的线程工作,直到被加锁资源释放,唤醒休眠线程。
优缺点:自旋锁由于不会引起调用者线程休眠,所以不会进行cpu调度以及时间片轮转等耗时操作,所以如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁,缺点是自旋锁会一直占用cpu,在未获得锁的情况下一直运行,如果不能短时间内获得锁,会使cpu的运行效率降低,同时自旋锁不能实现递归调用
OSSpinLock进行加锁
//OSSpinLock自旋锁的初始化
OSSpinLock _lock = OS_SPINLOCK_INIT;
//锁定
OSSpinLockLock(&_lock);
//解锁
OSSpinLockUnlock(&_lock);
OSSpinLock现在不能继续保证线程安全,因此不建议使用
@synchronized实现加锁
//假设共有十张票,有多个线程同时进行买票操作,使用synchronized控制每次只能允许一个线程进行访问
-(void)saleTickets{
@synchronized (self) {
if (self.tickets>0) {
self.tickets -= 1;
NSLog(@"tickets:%ld---%@",(long)self.tickets,[NSThread currentThread]);
}else
return;
}
}
- (IBAction)clickToPushB:(UIButton *)sender {
while (self.tickets>0) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self saleTickets];
});
}
}
dispatch_semaphore信号量实现加锁
每当发送一个信号时,信号量加一
每当发送一个等待信号时,信号量减一
如果信号量为0,则信号处于等待状态,直到信号量大于0才开始执行
while (self.tickets>0) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[self saleTickets];
dispatch_semaphore_signal(sema);
});
}
信号量也能用来控制线程的最大并发数
使用pthread来进行加锁
static pthread_mutex_t pLock;
pthread_mutex_init(&pLock, NULL);
while (self.tickets>0) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&pLock);
[self saleTickets];
pthread_mutex_unlock(&pLock);
});
}
加锁后只能有一个线程可以进行某个操作,后面的线程再进行时需要排队,并且lock和unlock是对应出现的,同一个线程不能进行多次lock(递归锁允许其在未释放自己拥有的锁时,反复对该资源进行加锁)
使用NSLock来进行加锁
self.lock = [NSLock new];
while (self.tickets>0) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.lock lock];
[self saleTickets];
[self.lock unlock];
});
}
使用NSConditionLock条件锁进行加锁
self.conditionLock = [NSConditionLock new];
while (self.tickets>0) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.conditionLock lock];
[self saleTickets];
[self.conditionLock unlock];
});
}
//NSConditonLock条件锁同时也能用来控制线程同步
self.conditionLock = [NSConditionLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.conditionLock lock];
NSLog(@"1");
[self.conditionLock unlockWithCondition:2]; });
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.conditionLock lockWhenCondition:2];
NSLog(@"2");
[self.conditionLock unlock];
});
使用NSRecursiveLock(递归锁)进行加锁
递归锁主要用在循环或者递归调用中
//下面的代码中RecrsiveBlock是递归调用的,如果使用NSLock的话,每次进入RecursiveLock的代码中都会进行一次加锁,但是因为没有解锁,所以需要等待解锁,这样线程就会进入阻塞状态,但是这里使用了NSRecursiveLock就不会进入阻塞状态
NSRecursiveLock *rLock = [NSRecursiveLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[rLock lock];
if (value > 0) {
NSLog(@"线程%d", value);
RecursiveBlock(value - 1);
}
[rLock unlock];
};
RecursiveBlock(4);
});
几种锁的性能对比
- 锁的性能从高到低依次如下
- OSSpinLock
- Dispatch_semaphore
- Pthread_mutex
- NSLock
- NSCondition
- Pthread_mutex(recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized
iOS中多线程总结
GCD(Grand Central Dispatch)
GCD的两个核心概念
队列:这里的队列指的是执行任务的等待队列,即用来存放任务的队列,队列是一种特殊的线性表,采用FIFO(先进先出的)的原则,新任务总是被插入到队列的末尾,而读取任务的时总是从队列的头部开始读取,每读取一个任务,则从队列中释放一个任务
- 串行队列(serial dispatch queue)
- 每次只有一个任务被执行,让任务一个接着一个执行(只开启一个线程,一个线程执行完毕后再执行下一个任务)
- 并发队列
- 可以让多个任务同时(并发)执行(可以开启多个线程,并发执行任务)
- 串行队列(serial dispatch queue)
任务:任务就是我们需要执行的操作,即放入队列中的那段代码,在GCD中任务是放在block中执行的,执行任务有两种方式,同步执行(sync),异步执行(async),两者的主要区别是,是否等待队列的任务执行结束,以及是否具备开启新线程的能力
- 同步任务(sync)
- 同步添加任务到执行的队列中,在添加的任务执行结束之前,会一直等待,知道队列里面的任务完成之后再继续执行
- 只能在当前线程中执行任务,不具备开启新线程的能力
- 异步任务(async)
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
- 可以在新的线程中执行任务,具备开启新线程的能力(但是并不一定会开启新线程,这与指定的队列有关)
- 同步任务(sync)
任务和队列不同组合方式的区别
并发队列 串行队列 主队列 同步任务(sync) 不开启新线程,串行执行 不开启新线程,串行执行 不开启新线程,造成死锁 异步任务(async) 开启新线程,并发执行 开启一条新线程,串行执行 不开启新线程,串行执行任务 队列嵌套的情况下,不同组合方式的区别
在异步串行队列中增加同一个队列的串行同步任务同样也会造成死锁,原理和主队列同步任务是相同的
dispatch_queue_t serial_queue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serial_queue, ^{
NSLog(@"serial_queue:%@",[NSThread currentThread]);
dispatch_sync(serial_queue, ^{
NSLog(@"serial_queue_sync:%@",[NSThread currentThread]);
});
});
主队列同步任务造成死锁的原因:在主队列追加同步任务,当执行到同步任务时,主队列中剩下的任务需要等待追加任务的完成才能继续往下执行,但是追加的任务需要等待主队列中的任务按顺序往下执行完才能继续执行。(主队列中追加的任务和主队列中本身的任务相互等待,最终造成死锁)
在iOS中UI的更新必须在主线程中执行
dispatch_async(dispatch_get_main_queue(), ^{
[self.view addSubview:self.scrollView];
});
使用NSOperation实现多线程
NSOperation是个抽象类,并不具备封装操作的能力,所以我们需要使用它们的子类
NSBlockOperation
NSLog(@"1---%@",[NSThread currentThread]);
NSBlockOperation* opeartion = [NSBlockOperation new];
[opeartion addExecutionBlock:^{
NSLog(@"2---%@",[NSThread currentThread]); }];
[opeartion addExecutionBlock:^{
NSLog(@"4---%@",[NSThread currentThread]);
}];
NSLog(@"3---%@",[NSThread currentThread]);
[opeartion start];
-----------------------------------------------------
1---<NSThread: 0x6000014d0a00>{number = 1, name = main}
3---<NSThread: 0x6000014d0a00>{number = 1, name = main}
2---<NSThread: 0x6000014d0a00>{number = 1, name = main}
4---<NSThread: 0x6000014a1140>{number = 6, name = (null)}
当NSBlockOperation中只有一个任务的话是同步执行,当使用addExecutionBlock:添加了多个任务的话,那么就会默认开启新的线程
NSInvocationBlock
//NSInvocationOperation默认是在当前线程进行同步执行的,除非自定义额外线程执行Operation
NSInvocationOperation* invokeOp = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
[invokeOp start];
-------------------------------------------------------------------
//在异步并发队列中增加NSOperation的操作,此时就是异步执行
NSInvocationOperation* invokeOp = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[invokeOp start];
});
NSOperationQueue
将NSoperation操作(NSBlockOperation和NSInvocationOperation)添加到NSOperationQueue中默认是开启了多线程操作,除了添加这两种封装的操作对象外,NSOperationQueue自己也可以添加操作
//操作队列自身添加操作,同样也是异步执行的
NSOperationQueue* queue = [NSOperationQueue new];
[queue addOperationWithBlock:^{
NSLog(@"operationQueue");
}];
NSOperationQueue中的操作可以添加依赖,指定任务执行的顺序,同时也可以指定开启的最大线程数量
queue.maxConcurrentOperationCount = 1;
[op1 addDependency:op2];
[queue addOperation:op2];
[queue addOperation:op1];
可以自定义子类继承自NSoperation,但是需要实现内部相应的方法
使用NSThread实现多线程
//创建方式一,能手动拿到线程对象,但是需要手动启动线程(start)
NSThread* thread = [[NSThread alloc]initWithBlock:^{
NSLog(@"threadTest---%@",[NSThread currentThread]);
}];
[thread start];
//创建方式二,自启动线程,但是无法拿到线程对象
[NSThread detachNewThreadWithBlock:^{
NSLog(@"threadTest---%@",[NSThread currentThread]);
}];
//创建方式三,自启动线程,不能拿到线程对象
[self performSelectorInBackground:@selector(saleTickets) withObject:nil];
//方式四,自定义线程继承自NSThread,需要重写main方法
.... //指定操作,指定线程,waitUntilDone(是否等指定的操作完成之后再进行后面的任务)
[self performSelector:@selector(saleTickets) onThread:[NSThread mainThread] withObject:nil waitUntilDone:false];
iOS中的几种锁的总结,三种开启多线程的方式(GCD、NSOperation、NSThread)的更多相关文章
- 【MySQL】锁——查看当前数据库锁请求的三种方法 20
MySQL提供了查看当前数据库锁请求的三种方法:1. show full processlist命令 观察state和info列 2. show engine innodb status\G ...
- 对象的notify方法的含义和对象锁释放的三种情况
1,notify的含义 (1)notify一次只随机通知一个线程进行唤醒 (2)在执行了notify方法之后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获得该对象锁, 要等到 ...
- ASP.NET MVC中使用Unity进行依赖注入的三种方式
在ASP.NET MVC中使用Unity进行依赖注入的三种方式 2013-12-15 21:07 by 小白哥哥, 146 阅读, 0 评论, 收藏, 编辑 在ASP.NET MVC4中,为了在解开C ...
- VC中加载LIB库文件的三种方法
VC中加载LIB库文件的三种方法 在VC中加载LIB文件的三种方法如下: 方法1:LIB文件直接加入到工程文件列表中 在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中&quo ...
- Java-五种线程池,四种拒绝策略,三种阻塞队列(转)
Java-五种线程池,四种拒绝策略,三种阻塞队列 三种阻塞队列: BlockingQueue<Runnable> workQueue = null; workQueue = n ...
- iOS --- UIWebView的加载本地数据的三种方式
UIWebView是IOS内置的浏览器,可以浏览网页,打开文档 html/htm pdf docx txt等格式的文件. safari浏览器就是通过UIWebView做的. 服务器将MIM ...
- 【转】iOS学习之容易造成循环引用的三种场景
ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是——循环引用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都同 ...
- 在ASP.NET MVC中使用Unity进行依赖注入的三种方式
在ASP.NET MVC4中,为了在解开Controller和Model的耦合,我们通常需要在Controller激活系统中引入IoC,用于处理用户请求的 Controller,让Controller ...
- ASP.NET MVC2中Controller向View传递数据的三种方式
转自:http://www.cnblogs.com/zhuqil/archive/2010/08/03/Passing-Data-from-Controllers-to-View.html 在Asp. ...
随机推荐
- vue2.x学习笔记(二十三)
接着前面的内容:https://www.cnblogs.com/yanggb/p/12639440.html. 渲染函数&JSX 基础 vue推荐在绝大多数的情况下使用模板来创建html.然而 ...
- webpack之Loader
我们知道webpack的优点之一就是专注于处理模块化的项目,能做到开箱即用,但同时这也是webpack的缺点,只能用于模块化开发的项目,例如:Vue,React,Angular.Webpack在进行打 ...
- python 携程asyncio 实现高并发示例2
https://www.bilibili.com/video/BV1g7411k7MD?from=search&seid=13649975876676293013 import asyncio ...
- Ubuntu 设置 log 级别
Linux环境下使用rsyslog管理日志 rsyslog linux运维 linux 22.7k 次阅读 · 读完需要 22 分钟 在 Linux 系统中,日志文件记录了系统中包括内核. ...
- Scala教程之:Option-Some-None
文章目录 Option和Some Option和None Option和模式匹配 在java 8中,为了避免NullPointerException,引入了Option,在Scala中也有同样的用法. ...
- 联想在S规则债券市场完成了里程碑式的新债券发行
腾讯科技讯,香港,2020 年 4 月 24 日-联想集团(HKSE:992)(ADR:LNVGY)今日宣布,在S规则债券市场上成功发行了里程碑式的 6.5 亿美元债券. 这些债券吸引了全球大量固定收 ...
- SSH公钥登录和RSA非对称加密
SSH登录方式 接触过Linux服务器的同学肯定用过SSH协议登录系统,通常SSH协议都有两种登录方式:密码口令登录和公钥登陆. 一.密码口令(类似于账号密码登录) 1.客户端连接服务器,服务器把公钥 ...
- yum报[Errno 256] No more mirrors to try
解决方法: yum clean all #清除yum缓存yum makecache #将服务器软件包写到本地缓存,提高包的搜索.安装效率
- 怎样借助Python爬虫给宝宝起个好名字
每个人一生中都会遇到一件事情,在事情出现之前不会关心,但是事情一旦来临就发现它极其重要,并且需要在很短的时间内做出重大决定,那就是给自己的新生宝宝起个名字.因为要在孩子出生后两周内起个名字(需要办理出 ...
- matlab混合编程向导(vc,vb,.net...)
一.matlab与vc混编 1.通过mcc将matlab的m文件转化为cpp,c文件或dll供vc调用: 这方面的实现推荐精华区Zosco和ljw总结的方法(x-6-1-4-3-1和2) ...