GCD
- 同步/异步 和 串行/ 并发
- dispatch_barrier_async
- dispatch_group
同步/异步 和 串行/ 并发
- dispatch_sync(serial_queue, ^{//任务});
- dispatch_async(serial_queue, ^{//任务});
- dispatch_sync(concurrent_queue, ^{//任务});
- dispatch_async(concurrent_queue, ^{//任务});
同步串行
//头条面试题
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
[self doSomething];
});
}
上面代码的问题:
这段代码的逻辑会产生死锁,死锁的原因队列引起的循环等待.
viewDidLoad
的执行过程中需要依赖于调用Block任务,而Block在队列中的排列(即栈的先进先出,因为Block任务在栈顶)导致他需要依赖viewDidLoad
执行完毕,这种彼此依赖对方先完成,就导致了死锁发生。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(serialQueue, ^{
[self doSomething];
});
}
代码执行正常,没有问题:
//美团
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(global_queue, ^{
NSLog(@"2");
dispatch_sync(global_queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}//12345
只要同步去提交任务,无论队列是串行还是并发,最终都会在当前线程去执行
异步并发
//腾讯视频
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(global_queue, ^{
NSLog(@"1");
[self performSelector:@selector(printLog) withObject:nil afterDelay:0];
NSLog(@"3");
});
//输出结果13
}
- (void)printLog {
NSLog(@"2");
}
performSelector:withObject:afterDelay:
因为需要当前线程有runloop去执行timer事件,但GCD底层是不创建runloop的没有runloop,即使时间是0,方法也会失效。所以结果是13
dispatch_barrier_async()
怎样利用GCD实现多读单写?(滴滴,美团面试题)
#import "UserCenter.h"
@interface UserCenter()
{
// 定义一个并发队列
dispatch_queue_t concurrent_queue;
// 用户数据中心, 可能多个线程需要数据访问
NSMutableDictionary *userCenterDic;
}
@end
// 多读单写模型
@implementation UserCenter
- (id)init
{
self = [super init];
if (self) {
// 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 创建数据容器
userCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKey:(NSString *)key
{
__block id obj;
// 同步读取指定数据
dispatch_sync(concurrent_queue, ^{
obj = [userCenterDic objectForKey:key];
});
return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key
{
// 异步栅栏调用设置数据
dispatch_barrier_async(concurrent_queue, ^{
[userCenterDic setObject:obj forKey:key];
});
}
@end
dispatch_group_async()
使用GCD实现三个需求:A、B、C三个任务并发,完成后执行任务D?(爱奇艺面试题)
#import "GroupObject.h"
@interface GroupObject()
{
dispatch_queue_t concurrent_queue;
NSMutableArray <NSURL *> *arrayURLs;
}
@end
@implementation GroupObject
- (id)init
{
self = [super init];
if (self) {
// 创建并发队列
concurrent_queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
arrayURLs = [NSMutableArray array];
}
return self;
}
- (void)handle
{
// 创建一个group
dispatch_group_t group = dispatch_group_create();
// for循环遍历各个元素执行操作
for (NSURL *url in arrayURLs) {
// 异步组分派到并发队列当中
dispatch_group_async(group, concurrent_queue, ^{
//根据url去下载图片
NSLog(@"url is %@", url);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 当添加到组中的所有任务执行完成之后会调用该Block
NSLog(@"所有图片已全部下载完成");
});
}
@end
NSOperation
需要和NSOperationQueue配合使用来实现多线程方案
优势和特点:
- 添加任务依赖
- 任务执行状态控制
- 最大并发量
任务执行状态控制
- isReady - 当前任务是否就绪
- isExecuting - 当前任务是否处于正在执行
- isFinished - 当前任务是否完成
- isCancelled - 当前任务是否取消
状态控制
- 如果只重写
main
方法,底层控制变更任务执行完成状态,以及任务退出。 - 如果只重写
start
方法,自行控制任务状态
系统是怎样移除一个isFinished=YES
的NSOperation的?
系统是通过KVO监听实现的。
NSThread
启动流程
源码来自gnustep-base
- (void) start
{
pthread_attr_t attr;
pthread_t thr;
if (_active == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-$@] called on active thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
if (_cancelled == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-$@] called on cancelled thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
if (_finished == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-$@] called on finished thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
/* Make sure the notification is posted BEFORE the new thread starts.
*/
gnustep_base_thread_callback();
/* The thread must persist until it finishes executing.
*/
[self retain];
/* Mark the thread as active whiul it's running.
*/
_active = YES;
errno = 0;
pthread_attr_init(&attr);
/* Create this thread detached, because we never use the return state from
* threads.
*/
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
/* Set the stack size when the thread is created. Unlike the old setrlimit
* code, this actually works.
*/
if (_stackSize > 0)
{
pthread_attr_setstacksize(&attr, _stackSize);
}
//指定线程的气筒函数为nsthreadLauncher
if (pthread_create(&thr, &attr, nsthreadLauncher, self))
{
DESTROY(self);
[NSException raise: NSInternalInconsistencyException
format: @"Unable to detach thread (last error %@)",
[NSError _last]];
}
}
static void *nsthreadLauncher(void* thread)
{
NSThread *t = (NSThread*)thread;//获取启动线程t
setThreadForCurrentThread(t);
#if __OBJC_GC__
objc_registerThreadWithCollector();
#endif
#if GS_WITH_GC && defined(HAVE_GC_REGISTER_MY_THREAD)
{
struct GC_stack_base base;
if (GC_get_stack_base(&base) == GC_SUCCESS)
{
int result;
result = GC_register_my_thread(&base);
if (result != GC_SUCCESS && result != GC_DUPLICATE)
{
fprintf(stderr, "Argh ... no thread support in garbage collection library\n");
}
}
else
{
fprintf(stderr, "Unable to determine stack base to register new thread for garbage collection\n");
}
}
#endif
/*
* Let observers know a new thread is starting.
*/
if (nc == nil)
{
nc = RETAIN([NSNotificationCenter defaultCenter]);
}
//发送通知
[nc postNotificationName: NSThreadDidStartNotification
object: t
userInfo: nil];
[t main];
[NSThread exit];
// Not reached
return NULL;
}
- (void) main
{
if (_active == NO)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-$@] called on inactive thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
[_target performSelector: _selector withObject: _arg];
}
多线程和锁
锁
iOS当中都有哪些锁?
@synchronized
- atomic
- OSSpinLock
- NSRecursiveLock
- NSLock
- dispatch_semaphore_t
@synchronized
- 一般在创建单例对象的时候使用,保证多线程环境下创建对象是唯一的
@synchronized
的作用是创建一个互斥锁,保证此时没有其它线程对self对象进行修改。这个是objective-c的一个锁定令牌,防止self
对象在同一时间内被其它线程访问,起到线程的保护作用。 一般在公用变量的时候使用,如单例模式或者操作类的static变量中使用。
指令@synchronized()
需要一个参数。该参数可以使任何的Objective-C对象,包括self
。这个对象就是互斥信号量。他能够让一个线程对一段代码进行保护,避免别的线程执行该段代码。
互斥锁使用格式
@synchronized(锁对象){ //需要锁定的代码 }
锁定一份代码只用1把锁,用多把锁是无效的。
互斥锁的优缺点
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
线程同步
多条线程在同一条线上执行(按顺序地执行任务)
互斥锁,就是使用了线程同步技术。
例如:一个电影院,有3个售票员。一场电影的总数量固定。3个售票员售票时,要判断是非还有余票。
#import "ViewController.h"
@interface ViewController ()
/** 售票员01 */
@property (nonatomic, strong) NSThread *thread01;
/** 售票员02 */
@property (nonatomic, strong) NSThread *thread02;
/** 售票员03 */
@property (nonatomic, strong) NSThread *thread03;
/** 票的总数 */
@property (nonatomic, assign) NSInteger ticketCount;
/** 锁对象 */
//@property (nonatomic, strong) NSObject *locker;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// self.locker = [[NSObject alloc] init];
self.ticketCount = 100;
self.thread01 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread01.name = @"售票员01";
self.thread02 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread02.name = @"售票员02";
self.thread03 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread03.name = @"售票员03";
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.thread01 start];
[self.thread02 start];
[self.thread03 start];
}
- (void)saleTicket
{
while (1) {
@synchronized(self) {
// 先取出总数
NSInteger count = self.ticketCount;
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@卖了一张票,还剩下%zd张", [NSThread currentThread].name, self.ticketCount);
} else {
NSLog(@"票已经卖完了");
break;
}
}
}
}
@end
atomic
- 修饰属性的关键字
- 对被修饰对象进行原子操作(不负责使用)
@property(atomic) NSMutableArray * array;
self.array = [NSMutableArray array];//✅赋值是线程安全的
[self.array addObject:obj];//❎操作数组是不能保证线程安全的
atomic和nonatomic的对比
1、atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。
2、atomic:系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。getter 还是能得到一个完好无损的对象(可以保证数据的完整性),但这个对象在多线程的情况下是不能确定的,比如上面的例子。
也就是说:如果有多个线程同时调用setter的话,不会出现某一个线程执行完setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样,每次只能有一个线程调用对象的setter方法,所以可以保证数据的完整性。
atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。
3、nonatomic:就没有这个保证了,nonatomic返回你的对象可能就不是完整的value。因此,在多线程的环境下原子操作是非常必要的,否则有可能会引起错误的结果。但仅仅使用atomic并不会使得对象线程安全,我们还要为对象线程添加lock来确保线程的安全。
4、nonatomic的速度要比atomic的快。
5、atomic与nonatomic的本质区别其实也就是在setter方法上的操作不同
atomic对象setter和getter方法的实现:
- (void)setCurrentImage:(UIImage *)currentImage
{
@synchronized(self) {
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
}
- (UIImage *)currentImage
{
@synchronized(self) {
return _currentImage;
}
}
OSSpinLock - 自旋锁
- 循环等待询问,不释放当前资源
- 用于轻量级数据访问。比如简单的
int
值+1/-1操作
runtime中有使用到,进行引用计数的+1/-1操作
NSLock
一般用于解决一些细粒度的线程同步问题,用来保证各个线程互斥来进入自己的临界区。
//蚂蚁金服
- (void)methodA {
[nslock lock];
[self methodB];
[nslock unlock];
}
- (void)methodB {
[nslock lock];
//操作逻辑
[nslock unlock];
}
以上代码的问题:死锁
methodA
中在某一线程的调用下,已经对线程加了lock
,如果在methodB
再次调用lock
,由于锁已经被使用了且没有解锁,所以它需要等待锁被解除,这样就导致了死锁,线程被阻塞住了。
NSRecursiveLock - 递归锁
NSRecursiveLock实际上定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中。
递归锁会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。
上面的问题可以通过使用递归锁进行解决:
- (void)methodA {
[recursiveLock lock];
[self methodB];
[recursiveLock unlock];
}
- (void)methodB {
[recursiveLock lock];
//操作逻辑
[recursiveLock unlock];
}
dispatch_semaphore_t
dispatch_semaphore_create(long value); // 创建信号量
dispatch_semaphore_signal(dispatch_semaphore_t semaphore); // 发送信号量
dispatch_semaphore_wait(dispatch_semaphore_t semaphore, dispatch_time_t timeout); // 等待信号量
-
dispatch_semaphore_create(long value);
和GCD的group等用法一致,这个函数是创建一个dispatch_semaphore_类型的信号量,并且创建的时候需要指定信号量的大小。 -
dispatch_semaphore_signal(dispatch_semaphore_t semaphore);
发送信号量。该函数会对信号量的值进行加1操作。 -
dispatch_semaphore_wait(dispatch_semaphore_t semaphore, dispatch_time_t timeout);
等待信号量。如果信号量值为0,那么该函数就会一直等待,也就是不返回(相当于阻塞当前线程),直到该函数等待的信号量的值大于等于1,该函数会对信号量的值进行减1操作,然后返回。
dispatch_semaphore_create(long value);
在底层会生成:
struct semaphore {
int value; //信号量的值
List <thread>; //一个线程列表
}
dispatch_semaphore_wait(dispatch_semaphore_t semaphore, dispatch_time_t timeout)
在底层的实现逻辑大致如下:
dispatch_semaphore_wait()
{
S.value -= 1;
if S.value < 0 then Block(S.list);//阻塞-主动行为
}
dispatch_semaphore_signal()
底层实现逻辑大致如下:
dispatch_semaphore_signal()
{
S.value += 1;
if S.value <= 0 then wakeup(S.list);//唤醒-被动行为
}
通常等待信号量和发送信号量的函数是成对出现的。并发执行任务时候,在当前任务执行之前,用dispatch_semaphore_wait
函数进行等待(阻塞),直到上一个任务执行完毕后且通过dispatch_semaphore_signal
函数发送信号量(使信号量的值加1),dispatch_semaphore_wait
函数收到信号量之后判断信号量的值大于等于1,会再对信号量的值减1,然后当前任务可以执行,执行完毕当前任务后,再通过dispatch_semaphore_signal
函数发送信号量(使信号量的值加1),通知执行下一个任务......如此一来,通过信号量,就达到了并发队列中的任务同步执行的要求。
总结
怎样用GCD事项多读单写?
iOS系统为我们提供的几种多线程技术各自的特点是怎样的?
在iOS系统当中主要提供了三种多线程技术,分别为GCD, NSOperation, NSThread,一般使用GCD来解决一些简单的线程同步,包括一些子线程的分派,包括实现一些例如多读单写这种场景的问题的解决。对于NSOperation,比如AFNetworking,SDWebimageView,他们内部都会涉及到
NSOperation
,由于他的特点是可以方便我们对任务的状态进行控制,包括可以控制依赖的添加和移除依赖。对于NSThread往往我们用他来实现同一个常驻线程。
-
NSOperation
对象在Finished
之后是怎样从队列中移除掉的?
NSOperation
对象在Finished
之后,会在内部用KVO的形式通知NSOperationQueue
达到对NSOperation
的移除
你都用过哪些锁?结合实际谈谈你是怎样使用的?