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. ...
随机推荐
- niuke --abc
链接:https://ac.nowcoder.com/acm/contest/1083/A来源:牛客网 给出一个字符串s,你需要做的是统计s中子串”abc”的个数.子串的定义就是存在任意下标a< ...
- 浏览器中 JS 的事件循环机制
目录 事件循环机制 宏任务与微任务 实例分析 参考 1.事件循环机制 浏览器执行JS代码大致可以分为三个步骤,而这三个步骤的往复构成了JS的事件循环机制(如图). 第一步:主线程(JS引擎线程)中执行 ...
- 深入浅出node.js游戏服务器开发1——基础架构与框架介绍
2013年04月19日 14:09:37 MJiao 阅读数:4614 深入浅出node.js游戏服务器开发1——基础架构与框架介绍 游戏服务器概述 没开发过游戏的人会觉得游戏服务器是很神秘的 ...
- 批量重命名脚本(Python)
便携的批处理脚本,代码如下: import os import sys def rename(): path=input("请输入路径(例如D:/picture):") name= ...
- CSS属性中的display属性浅谈;
首先我们要知道什么是块级元素和行内元素有什么区别: 承接上篇文章:(浅谈HTML和body标签) 块级元素:浏览器解析为独占一行的元素(例如:div.table.ul等.),浏览器会在该元素的前后显示 ...
- bluecms v1.6 sp1 代码审计学习
前言 正式开始代码审计的学习,拓宽自己的知识面.代码审计学习的动力也是来自团队里的王叹之师傅,向王叹之师傅学习. 这里参考了一些前辈,师傅的复现经验和bluecms审计的心得 安装 install.p ...
- 关于对vue-router的优化(详尽版)
这两天总结了关于vue-router优化的几点技法,做个笔记 在基于vue的移动端app中,通过vue-router可以便捷的进入某一路由或回退到上一路由,但是若不对vue-router做相关优化处理 ...
- 大数据Hbase相关运维题
1.启动先电大数据平台的 Hbase 数据库,其中要求使用 master 节点的RegionServer.在 Linux Shell 中启动 Hbase shell,查看 HBase 的版本信息.(相 ...
- 理解java容器底层原理--手动实现ArrayList
为了照顾初学者,我分几分版本发出来 版本一:基础版本 实现对象创建.元素添加.重新toString() 方法 package com.xzlf.collection; /** * 自定义一个Array ...
- Task Scheduler API Error 80041318
https://stackoverflow.com/questions/42307917/task-scheduler-api-error-80041318/42462235#42462235 Hi ...