iOS开发中因为各类第三方库的高度封装,对锁的使用不多,恰好以前面试中被问到的关于并发编程锁的问题,都是只知其一;不知其二,因而决定整理一下关于iOS中锁的知识,为你们查缺补漏。html
在过去几十年并发研究领域的出版物中,锁老是扮演着坏人的角色,锁背负的指控包括引发死锁、锁封护(luyang注:lock convoying,多个同优先级的线程重复竞争同一把锁,此时大量虽然被唤醒而得不到锁的线程被迫进行调度切换,这种频繁的调度切换至关影响系统性能)、饥饿、不公平、data races以及其余许多并发带来的罪孽。有趣的是,在共享内存并行软件中真正承担重担的是——你猜对了——锁。前端
在计算机科学中,锁是一种同步机制,用于多线程环境中对资源访问的限制。你能够理解成它用于排除并发的一种策略。ios
if (lock == 0) {
lock = myPID;
}复制代码
上面这段代码并不能保证这个任务有锁,所以它能够在同一时间被多个任务执行。这个时候就有可能多个任务都检测到lock是空闲的,所以两个或者多个任务都将尝试设置lock,而不知道其余的任务也在尝试设置lock。这个时候就会出问题了。再看看下面这段代码(Swift):git
class Account {
private(set) var val: Int = 0 //这里不可在其余方法修改,只能经过add/minus修改
public func add(x: Int) {
objc_sync_enter(self)
defer {
objc_sync_exit(self)
}
val += x
}
public func minus(x: Int) {
objc_sync_enter(self)
defer {
objc_sync_exit(self)
}
val -= x;
}
}复制代码
这样就能防止多个任务去修改val了。github
锁根据不一样的性质能够分红不一样的类。面试
在WiKiPedia介绍中,通常的锁都是建议锁,也就四每一个任务去访问公共资源的时候,都须要取得锁的资讯,再根据锁资讯来肯定是否能够存取。若存取对应资讯,锁的状态会改变为锁定,所以其余线程不会访问该资源,当结束访问时,锁会释放,容许其余任务访问。有些系统有强制锁,若未经受权的锁访问锁定的资料,在访问时就会产生异常。数据库
在iOS中,锁分为互斥锁、递归锁、信号量、条件锁、自旋锁、读写锁(一种特所的自旋锁)、分布式锁。编程
对于数据库的锁分类:数组
分类方式 | 分类 |
---|---|
按锁的粒度划分 | 表级锁、行级锁、页级锁 |
按锁的级别划分 | 共享锁、排他锁 |
按加锁的方式划分 | 自动锁、显示锁 |
按锁的使用方式划分 | 乐观锁、悲观锁 |
按操做划分 | DML锁、DDL锁 |
这里就再也不详细的介绍了,感兴趣的你们能够带Wiki
查阅相关资料。xcode
在编程中,引入对象互斥锁的概念,来保证共享数据操做的完整性。每一个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问对象。
1.1 @synchronized
// 用在防止多线程访问属性上比较多
- (void)setTestInt:(NSInteger)testInt {
@synchronized (self) {
_testInt = testInt;
}
}复制代码
1.2 NSLock
// 定义block类型
typedef void(^MMBlock)(void);
// 定义获取全局队列方法
#define MM_GLOBAL_QUEUE(block) \
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ \
while (1) { \
block();\
}\
})复制代码
NSLock *lock = [[NSLock alloc] init];
MMBlock block = ^{
[lock lock];
NSLog(@"执行操做");
sleep(1);
[lock unlock];
};
MM_GLOBAL_QUEUE(block);复制代码
1.3 pthread
pthread除了建立互斥锁,还能够建立递归锁、读写锁、once等锁。稍后会介绍一下如何使用。若是想要深刻学习pthread请查阅相关文档、资料单独学习。
静态初始化: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
动态初始化: pthread_mutex_init()
函数是以动态方式建立互斥锁的,参数 attr 指定了新建互斥锁的属性。若是参数 attr 为 NULL ,使用默认的属性,返回0表明初始化成功。这种方式能够初始化普通锁、递归锁(同 NSRecursiveLock ), 初始化方式有些复杂。
此类初始化方法可设置锁的类型,PTHREAD_MUTEX_ERRORCHECK
互斥锁不会检测死锁, PTHREAD_MUTEX_ERRORCHECK
互斥锁可提供错误检查, PTHREAD_MUTEX_RECURSIVE
递归锁, PTHREAD_PROCESS_DEFAULT
映射到 PTHREAD_PROCESS_NORMAL
.
下面是我从YYKitcopy下来的:
#import <pthread.h>
//YYKit
static inline void pthread_mutex_init_recursive(pthread_mutex_t *mutex, bool recursive) {
#define YYMUTEX_ASSERT_ON_ERROR(x_) do { \
__unused volatile int res = (x_); \
assert(res == 0); \
} while (0)
assert(mutex != NULL);
if (!recursive) {
//普通锁
YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init(mutex, NULL));
} else {
//递归锁
pthread_mutexattr_t attr;
YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_init (&attr));
YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE));
YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init (mutex, &attr));
YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_destroy (&attr));
}
#undef YYMUTEX_ASSERT_ON_ERROR
}复制代码
__block pthread_mutex_t lock;
pthread_mutex_init_recursive(&lock,false);
MMBlock block0=^{
NSLog(@"线程 0:加锁");
pthread_mutex_lock(&lock);
NSLog(@"线程 0:睡眠 1 秒");
sleep(1);
pthread_mutex_unlock(&lock);
NSLog(@"线程 0:解锁");
};
MM_GLOBAL_QUEUE(block0);
MMBlock block1=^(){
NSLog(@"线程 1:加锁");
pthread_mutex_lock(&lock);
NSLog(@"线程 1:睡眠 2 秒");
sleep(2);
pthread_mutex_unlock(&lock);
NSLog(@"线程 1:解锁");
};
MM_GLOBAL_QUEUE(block1);
MMBlock block2=^{
NSLog(@"线程 2:加锁");
pthread_mutex_lock(&lock);
NSLog(@"线程 2:睡眠 3 秒");
sleep(3);
pthread_mutex_unlock(&lock);
NSLog(@"线程 2:解锁");
};
MM_GLOBAL_QUEUE(block2);复制代码
线程 2:加锁
线程 0:加锁
线程 1:加锁
线程 2:睡眠 3 秒复制代码
线程 2:加锁
线程 0:加锁
线程 1:加锁
线程 2:睡眠 3 秒
线程 2:解锁
线程 0:睡眠 1 秒
线程 2:加锁复制代码
线程 2:加锁
线程 0:加锁
线程 1:加锁
线程 2:睡眠 3 秒
线程 2:解锁
线程 0:睡眠 1 秒
线程 2:加锁
线程 0:解锁
线程 1:睡眠 2 秒
线程 0:加锁复制代码
同一个线程能够屡次加锁,不会形成死锁
举个🌰:
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value = %d", value);
sleep(2);
RecursiveMethod(value - 1);
}
[lock unlock];
};
RecursiveMethod(5);
});复制代码
这段代码是一个典型的死锁状况。在咱们的线程中,RecursiveMethod是递归调用的。全部每次进入这个block时,都会去加一次锁,而从第二次开始,因为锁已经被使用了且没有解锁,全部它须要等待锁被解除,这样就致使了死锁,线程被阻塞住了。控制台会输出以下信息:
value = 5
*** -[NSLock lock]: deadlock ( '(null)') *** Break on _NSLockError() to debug.复制代码
2.1 NSRecursiveLock
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
MM_GLOBAL_QUEUE(^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"加锁层数 %d", value);
sleep(1);
RecursiveBlock(--value);
}
[lock unlock];
};
RecursiveBlock(3);
});复制代码
加锁层数 3
加锁层数 2
加锁层数 1
加锁层数 3
加锁层数 2
加锁层数 1
加锁层数 3
加锁层数 2复制代码
2.2 pthread
__block pthread_mutex_t lock;
//第二个参数为true生成递归锁
pthread_mutex_init_recursive(&lock,true);
MM_GLOBAL_QUEUE(^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
pthread_mutex_lock(&lock);
if (value > 0) {
NSLog(@"加锁层数 %d", value);
sleep(1);
RecursiveBlock(--value);
}
pthread_mutex_unlock(&lock);
};
RecursiveBlock(3);
});复制代码
加锁层数 3
加锁层数 2
加锁层数 1
加锁层数 3
加锁层数 2
加锁层数 1
加锁层数 3
加锁层数 2复制代码
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是能够用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段以前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量
3.1 dispatch_semaphore_t
// 参数能够理解为信号的总量,传入的值必须大于或等于0,不然,返回NULL
// dispatch_semaphore_signal + 1
// dispatch_semaphore_wait等待信号,当 <= 0会进入等待状态
__block dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
MM_GLOBAL_QUEUE(^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"这里简单写一下用法,可自行实现生产者、消费者");
sleep(1);
dispatch_semaphore_signal(semaphore);
});复制代码
3.2 pthread
__block pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
__block pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
MM_GLOBAL_QUEUE(^{
//NSLog(@"线程 0:加锁");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
NSLog(@"线程 0:wait");
pthread_mutex_unlock(&mutex);
//NSLog(@"线程 0:解锁");
});
MM_GLOBAL_QUEUE(^{
//NSLog(@"线程 1:加锁");
sleep(3);//3秒发一次信号
pthread_mutex_lock(&mutex);
NSLog(@"线程 1:signal");
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
//NSLog(@"线程 1:加锁");
});复制代码
3.1 NSCodition
NSCondition 的对象实际上做为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引起的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。
- (void)getIamgeName:(NSMutableArray *)imageNames{
NSCondition *lock = [[NSCondition alloc] init];
NSString *imageName;
[lock lock];
if (imageNames.count>0) {
imageName = [imageNames lastObject];
[imageNames removeObject:imageName];
}
[lock unlock];
}复制代码
NSCondition和NSLock、@synchronized等是不一样的是,NSCondition能够给每一个线程分别加锁,加锁后不影响其余线程进入临界区。这是很是强大。
可是正是由于这种分别加锁的方式,NSCondition使用wait并使用加锁后并不能真正的解决资源的竞争。好比咱们有个需求:不能让m<0。假设当前m=0,线程A要判断到m>0为假,执行等待;线程B执行了m=1操做,并唤醒线程A执行m-1操做的同时线程C判断到m>0,由于他们在不一样的线程锁里面,一样判断为真也执行了m-1,这个时候线程A和线程C都会执行m-1,可是m=1,结果就会形成m=-1.
当我用数组作删除试验时,作增删操做并非每次都会出现,大概3-4次后会出现。单纯的使用lock、unlock是没有问题的。
- (void)executeNSCondition {
NSCondition* lock = [[NSCondition alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSUInteger i=0; i<3; i++) {
sleep(2);
if (i == 2) {
[lock lock];
[lock broadcast];
[lock unlock];
}
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[self threadMethodOfNSCodition:lock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[self threadMethodOfNSCodition:lock];
});
}
-(void)threadMethodOfNSCodition:(NSCondition*)lock{
[lock lock];
[lock wait];
[lock unlock];
}复制代码
3.2 NSCoditionLock
lock
不分条件,若是锁没被申请,直接执行代码
unlock
不会清空条件,以后知足条件的锁还会执行
unlockWithCondition:
个人理解就是设置解锁条件(同一时刻只有一个条件,若是已经设置条件,至关于修改条件)
lockWhenCondition:
知足特定条件,执行相应代码
NSConditionLock一样实现了NSLocking协议,试验过程当中发现性能很低。
- (void)executeNSConditionLock {
NSConditionLock* lock = [[NSConditionLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSUInteger i=0; i<3; i++) {
sleep(2);
if (i == 2) {
[lock lock];
[lock unlockWithCondition:i];
}
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[self threadMethodOfNSCoditionLock:lock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[self threadMethodOfNSCoditionLock:lock];
});
}
-(void)threadMethodOfNSCoditionLock:(NSConditionLock*)lock{
[lock lockWhenCondition:2];
[lock unlock];
}复制代码
3.3 POSIX Conditions
POSIX条件锁须要互斥锁和条件两项来实现,虽然看起来没有什么关系,但在运行时中,互斥锁将会与条件结合起来。线程将被一个互斥和条件结合的信号来唤醒。
首先初始化条件和互斥锁,当ready_to_go
为false的时候,进入循环,而后线程将会被挂起,直到另外一个线程将ready_to_go
设置为true的时候,而且发送信号的时候,该线程才会被唤醒。
测试代码
pthread_mutex_t mutex;
pthread_cond_t condition;
Boolean ready_to_go = true;
void MyCondInitFunction()
{
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&condition, NULL);
}
void MyWaitOnConditionFunction()
{
// Lock the mutex.
pthread_mutex_lock(&mutex);
// If the predicate is already set, then the while loop is bypassed;
// otherwise, the thread sleeps until the predicate is set.
while(ready_to_go == false)
{
pthread_cond_wait(&condition, &mutex);
}
// Do work. (The mutex should stay locked.)
// Reset the predicate and release the mutex.
ready_to_go = false;
pthread_mutex_unlock(&mutex);
}
void SignalThreadUsingCondition()
{
// At this point, there should be work for the other thread to do.
pthread_mutex_lock(&mutex);
ready_to_go = true;
// Signal the other thread to begin work.
pthread_cond_signal(&condition);
pthread_mutex_unlock(&mutex);
}复制代码
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,经常须要协调他们的动做。若是不一样的系统或是同一个系统的不一样主机之间共享了一个或一组资源,那么访问这些资源的时候,每每须要互斥来防止彼此干扰来保证一致性,在这种状况下,便须要使用到分布式锁。
5.1 NSDistributedLock
处理多个进程或多个程序之间互斥问题。
一个获取锁的进程或程序在是否锁以前挂掉,锁不会被释放,能够经过breakLock方式解锁。
iOS不多用到,暂不详细研究。
读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分红读者和写者,读者只对共享资源进行读访问,写者则须要对共享资源进行写操做。这种锁相对于自旋锁而言,能提升并发性,由于在多处理器系统中,它容许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
6.1 dispatch_barrier_async / dispatch_barrier_sync
先来一个需求:假设咱们原先有6个任务要执行,咱们如今要插入一个任务0,这个任务0要在一、二、4都并发执行完以后才能执行,而四、五、6号任务要在这几个任务0结束后才容许并发。大体的意思以下图
直接上代码:
- (void)rwLockOfBarrier {
dispatch_queue_t queue = dispatch_queue_create("thread", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"test1");
});
dispatch_async(queue, ^{
NSLog(@"test2");
});
dispatch_async(queue, ^{
NSLog(@"test3");
});
dispatch_barrier_sync(queue, ^{
for (int i = 0; i <= 500000000; i++) {
if (5000 == i) {
NSLog(@"point1");
}else if (6000 == i) {
NSLog(@"point2");
}else if (7000 == i) {
NSLog(@"point3");
}
}
NSLog(@"barrier");
});
NSLog(@"aaa");
dispatch_async(queue, ^{
NSLog(@"test4");
});
dispatch_async(queue, ^{
NSLog(@"test5");
});
dispatch_async(queue, ^{
NSLog(@"test6");
});
}复制代码
共同点:一、等待在它前面插入队列的任务先执行完;二、等待他们本身的任务执行完再执行后面的任务。
不一样点:一、dispatch_barrier_sync将本身的任务插入到队列的时候,须要等待本身的任务结束以后才会继续插入被写在它后面的任务,而后执行它们;二、dispatch_barrier_async将本身的任务插入到队列以后,不会等待本身的任务结束,它会继续把后面的任务插入队列,而后等待本身的任务结束后才执行后面的任务。
6.2 pthread
THREAD_RWLOCK_INITIALIZER
、动态pthread_rwlock_init()
、pthread_rwlock_destroy
用来销毁该锁#import <pthread.h>
__block pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock,NULL);
//读
MM_GLOBAL_QUEUE(^{
//NSLog(@"线程0:随眠 1 秒");//仍是不打印能直观些
sleep(1);
NSLog(@"线程0:加锁");
pthread_rwlock_rdlock(&rwlock);
NSLog(@"线程0:读");
pthread_rwlock_unlock(&rwlock);
NSLog(@"线程0:解锁");
});
//写
MM_GLOBAL_QUEUE(^{
//NSLog(@"线程1:随眠 3 秒");
sleep(3);
NSLog(@"线程1:加锁");
pthread_rwlock_wrlock(&rwlock);
NSLog(@"线程1:写");
pthread_rwlock_unlock(&rwlock);
NSLog(@"线程1:解锁");
});复制代码
何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较相似,它们都是为了解决对某项资源的互斥使用。不管是互斥锁,仍是自旋锁,在任什么时候刻,最多只能有一个保持者,也就说,在任什么时候刻最多只能有一个执行单元得到锁。可是二者在调度机制上略有不一样。对于互斥锁,若是资源已经被占用,资源申请者只能进入睡眠状态。可是自旋锁不会引发调用者睡眠,若是自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是所以而得名。
7.1 OSSpinLock
// 初始化
spinLock = OS_SPINKLOCK_INIT;
// 加锁
OSSpinLockLock(&spinLock);
// 解锁
OSSpinLockUnlock(&spinLock);复制代码
然而,YYKit做者的文章再也不安全的 OSSpinLock有说到这个自旋锁存在优先级反转的问题。
7.2 os_unfair_lock
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);复制代码
利用
set
/get
接口的属性实现原子操做,进而确保“被共享”的变量在多线程中读写安全,这已是不能知足部分多线程同步要求。
在定义 property
的时候, 有atomic
和 nonatomic
的属性修饰关键字。
对于atomic
的属性,系统生成的 getter/setter
会保证 get、set 操做的完整性,不受其余线程影响。好比,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 仍是能获得一个无缺无损的对象。
而nonatomic
就没有这个保证了。因此,nonatomic
的速度要比atomic
快。
Atomic
Non-Atomic
-
[self setName:@"A"]`,线程 B 调
[self setName:@"B"],线程 C 调
[self name]``,那么全部这些不一样线程上的操做都将依次顺序执行——也就是说,若是一个线程正在执行 getter/setter,其余线程就得等待。所以,属性 name 是读/写安全的。可是,若是有另外一个线程 D 同时在调[name release]
,那可能就会crash,由于 release 不受 getter/setter 操做的限制。也就是说,这个属性只能说是读/写安全的,但并非线程安全的,由于别的线程还能进行读写以外的其余操做。线程安全须要开发者本身来保证。
若是 name 属性是 nonatomic 的,那么上面例子里的全部线程 A、B、C、D 均可以同时执行,可能致使没法预料的结果。若是是 atomic 的,那么 A、B、C 会串行,而 D 仍是并行的。
-
9.1 GCD
+ (instancetype) sharedInstance {
static id __instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__instance = [[self alloc] init];
});
return __instance;
}复制代码
9.2 pthread
// 定义方法
void fun() {
NSLog(@"%@", [NSThread currentThread]);
}
- (void)onceOfPthread {
__block pthread_once_t once = PTHREAD_ONCE_INIT;
int i= 0;
while (i > 5) {
pthread_once(&once, fun);
i++;
}
}复制代码
基础表现-所操做耗时
上图是常规的锁操做性能测试(iOS7.0SDK,iPhone6模拟器,Yosemite 10.10.5),垂直方向表示耗时,单位是秒,总耗时越小越好,水平方向表示不一样类型锁的锁操做,具体又分为两部分,左边的常规lock操做(好比NSLock)或者读read操做(好比ANReadWriteLock),右边则是写write操做,图上仅有ANReadWriteLock和ANRecursiveRWLock支持,其它不支持的则默认为0,图上看出,单从性能表现,原子操做是表现最佳的(0.057412秒),@synchronized则是最耗时的(1.753565秒) (测试代码) 。
多线程锁删除数组性能测试
经过测试发现模拟器和真机的区别仍是很大的,模拟器上明显的阶梯感,真机就没有,模拟器上NSConditionLock的性能很是差,我没有把它的参数加在表格上,否则其余的就看不到了。不过真机上面性能还好。
这些性能测试只是一个参考,不必非要去在乎这些,毕竟前端的编程通常线程要求没那么高,能够从其余的地方优化。线程安全中注意避坑,另外选择本身喜欢的方式,这样你能够研究的更深刻,使用的更熟练。
声明: 测试结果仅仅表明一个参考,由于各类因素的影响,并无那么准确。
综合比较
能够看到除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高的。有消息称,苹果在新的系统中已经优化了 pthread_mutex 的性能,全部它看上去和 dispatch_semaphore 差距并无那么大了。
首先要明确几个概念
在使用GCD的时候,咱们会把须要处理的任务放到Block中,而后将任务追加到相应的队列里面,这个队列,叫作 Dispatch Queue。然而,存在于两种Dispatch Queue,一种是要等待上一个任务执行完,再执行下一个的Serial Dispatch Queue,这叫作串行队列;另外一种,则是不须要上一个任务执行完,就能执行下一个的ConcurrentDispatch Queue,叫作并行队列。这两种,均遵循FIFO原则。
举一个简单的例子,在三个任务中输出一、二、3,串行队列输出是有序的一、二、3,可是并行队列的前后顺序就不必定了。
虽然能够同时多个任务的处理,可是并行队列的处理量,仍是要根据当前系统状态来。若是当前系统状态最多处理2个任务,那么一、2会排在前面,3何时操做,就看1或者2谁先完成,而后3接在后面。
串行和并行就简单说到这里,关于它们的技术点其实还有不少,能够自行了解。
串行与并行针对的是队列,而同步与异步,针对的则是线程。最大的区别在于,同步线程要阻塞当前线程,必需要等待同步线程中的任务执行完,返回之后,才能继续执行下一个任务;而异步线程则是不用等待。
GCD API不少,这里仅介绍本文用到的。
// 全局队列,也是一个并行队列
dispatch_get_global_queue
// 主队列,在主线程中运行,由于主线程只有一个,全部这是一个串行队列
dispatch_get_main_queue复制代码
// 从DISPATCH_QUQUE_SERIAL看出,这是串行队列
dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL)
// 同理,这是一个并行队列
dispatch_queue_create("com.demo.concurrentQueue", DISPATCH_QUEUE_CONCURRENT)复制代码
dispatch_sync(..., ^(block)) // 同步线程
dispatch_async(..., ^(block)) // 异步线程复制代码
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3复制代码
1复制代码
分析
首先执行任务1,这是确定没问题的,只是接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,而后执行任务3。但这是队列,有任务来,固然会将任务加到队尾,而后遵循FIFO原则执行任务。那么,如今任务2就会被加到最后,任务3排在了任务2前面,问题来了:
任务3要等任务2执行完才能执行,任务2由排在任务3后面,意味着任务2要在任务3执行完才能执行,因此他们进入了互相等待的局面。【既然这样,那干脆就卡在这里吧】这就是死锁。
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3复制代码
1
2
3复制代码
分析
首先执行任务1,接下来会遇到一个同步线程,程序会进入等待。等待任务2执行完成之后,才能继续执行任务3。从dispatch_get_global_queue能够看出,任务2被加入到了全局的并行队列中,当并行队列执行完任务2之后,返回到主队列,继续执行任务3。
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5复制代码
1
5
2
// 5和2的顺序不必定复制代码
分析
这个案例没有使用系统提供的串行或并行队列,而是本身经过dispatch_queue_create函数建立了一个DISPATCH_QUEUE_SERIAL的串行队列。
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2"); // 任务2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5复制代码
1
2
5
3
4
// 5和2的顺序不必定复制代码
分析
首先,将【任务一、异步线程、任务5】加入Main Queue中,异步线程中的任务是:【任务二、同步线程、任务4】。
因此,先执行任务1,而后将异步线程中的任务加入到Global Queue中,由于异步线程,因此任务5不用等待,结果就是2和5的输出顺序不必定。
而后再看异步线程中的任务执行顺序。任务2执行完之后,遇到同步线程。将同步线程中的任务加入到Main Queue中,这时加入的任务3在任务5的后面。
当任务3执行完之后,没有了阻塞,程序继续执行任务4。
从以上的分析来看,获得的几个结果:1最早执行;2和5顺序不必定;4必定在3后面。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
while (1) {
}
NSLog(@"5"); // 任务5复制代码
1
4
// 1和4的顺序不必定复制代码
分析
和上面几个案例的分析相似,先来看看都有哪些任务加入了Main Queue:【异步线程、任务四、死循环、任务5】。
在加入到Global Queue异步线程中的任务有:【任务一、同步线程、任务3】。
第一个就是异步线程,任务4不用等待,因此结果任务1和任务4顺序不必定。
任务4完成后,程序进入死循环,Main Queue阻塞。可是加入到Global Queue的异步线程不受影响,继续执行任务1后面的同步线程。
同步线程中,将任务2加入到了主线程,而且,任务3等待任务2完成之后才能执行。这时的主线程,已经被死循环阻塞了。因此任务2没法执行,固然任务3也没法执行,在死循环后的任务5也不会执行。
最终,只能获得1和4顺序不定的结果。
苹果为多线程、共享内存提供了多种同步解决方案(锁),对于这些方案的比较,大都讨论了锁的用法以及锁操做的开销。我的认为最优秀的选用仍是看应用场景,高频接口VS低频接口、有限冲突VS激烈竞争、代码片断耗时的长短,都是选择的重要依据,选择适用于当前应用场景的方案才是王道。
最后,因为时间匆促,若是有错误或者不足的地方请指正,最后附上Demo全部代码的集合,下面是个人github和博客。
联系方式:ed_sun0129@163.com