当前位置: 首页 > news >正文

iOS——锁与死锁问题

iOS中的锁

  • 什么是锁
  • 锁的分类
    • 互斥锁
      • 1. @synchronized
      • 2. NSLock
      • 3. pthread
    • 递归锁
      • 1. NSRecursiveLock
      • 2. pthread
    • 信号量Semaphore
      • 1. dispatch_semaphore_t
      • 2. pthread
    • 条件锁
      • 1. NSCodition
      • 2. NSCoditionLock
      • 3. POSIX Conditions
    • 分布式锁
      • NSDistributedLock
    • 读写锁
      • 1. dispatch_barrier_async / dispatch_barrier_sync
      • 2. pthread
    • 自旋锁
      • 1. OSSpinLock
      • 2. os_unfair_lock
    • atomic(property) set / get
    • ONCE
      • GCD
      • pthread
  • 死锁问题

在iOS开发里面,锁是为了保护共享资源的访问确保线程安全性和避免竞争条件。iOS的应用通常在多线程的环境下运行,之前学习的多线程GCDOperation Queue都是执行并发任务的,多个线程可能同时访问某个对象,所以锁可以确保每次只有一个线程能够修改或访问共享资源,保护了数据的安全,避免了资源冲突。

什么是锁

在过去几十年并发研究领域的出版物中,锁总是扮演着坏人的角色,锁背负的指控包括引起死锁、锁封护(luyang注:lock convoying,多个同优先级的线程重复竞争同一把锁,此时大量虽然被唤醒而得不到锁的线程被迫进行调度切换,这种频繁的调度切换相当影响系统性能)、饥饿、不公平、data races以及其他许多并发带来的罪孽。有趣的是,在共享内存并行软件中真正承担重担的是——锁。

在计算机科学中,锁是一种同步机制,用于多线程环境中对资源访问的限制。你可以理解成它用于排除并发的一种策略。

	if (lock == 0) {lock = myPID;}

上面这段代码并不能保证这个任务有锁,因此它可以在同一时间被多个任务执行。这个时候就有可能多个任务都检测到lock是空闲的,因此两个或者多个任务都将尝试设置lock,而不知道其他的任务也在尝试设置lock。这个时候就会出问题了。再看看下面这段代码:

	class Account {private(set) var val: Int = 0public 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了。

锁的分类

锁根据不同的性质可以分成不同的类。

在WiKiPedia介绍中,一般的锁都是建议锁,也就四每个任务去访问公共资源的时候,都需要取得锁的资讯,再根据锁资讯来确定是否可以存取。若存取对应资讯,锁的状态会改变为锁定,因此其他线程不会访问该资源,当结束访问时,锁会释放,允许其他任务访问。有些系统有强制锁,若未经授权的锁访问锁定的资料,在访问时就会产生异常。

在iOS中,锁分为互斥锁、递归锁、信号量、条件锁、自旋锁、读写锁(一种特所的自旋锁)、分布式锁

对于数据库的锁分类:

分类方式分类
按锁的粒度划分表级锁、行级锁、页级锁
按锁的级别划分共享锁、排他锁
按加锁的方式划分自动锁、显示锁
按锁的使用方式划分乐观锁、悲观锁
按操作划分DML锁、DDL锁

下面是各种锁性能的图表
在这里插入图片描述

互斥锁

在编程中,引入对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问对象。

1. @synchronized

  • @synchronized要一个参数,这个参数相当于信号量
// 用在防止多线程访问属性上比较多
- (void)setTestInt:(NSInteger)testInt {@synchronized (self) {_testInt = testInt;}
}

2. NSLock

  • block及宏定义
// 定义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);

在这里插入图片描述
隔一秒输出一次。

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:加锁线程 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.

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. 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

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量

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);});

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:加锁");});

条件锁

1. NSCodition

NSCondition 的对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。

  • NSCondition同样实现了NSLocking协议,所以它和NSLock一样,也有NSLocking协议的lock和unlock方法,可以当做NSLock来使用解决线程同步问题,用法完全一样。
- (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提供更高级的用法。wait和signal,和条件信号量类似。比如我们要监听imageNames数组的个数,当imageNames的个数大于0的时候就执行清空操作。思路是这样的,当imageNames个数大于0时执行清空操作,否则,wait等待执行清空操作。当imageNames个数增加的时候发生signal信号,让等待的线程唤醒继续执行。
  • NSConditionNSLock@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次后会出现。单纯的使用lockunlock是没有问题的。
- (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];}

2. NSCoditionLock

  • lock不分条件,如果锁没被申请,直接执行代码
  • unlock不会清空条件,之后满足条件的锁还会执行
  • unlockWithCondition 我的理解就是设置解锁条件(同一时刻只有一个条件,如果已经设置条件,相当于修改条件)
  • lockWhenCondition满足特定条件,执行相应代码
  • NSConditionLock同样实现了NSLocking协议,试验过程中发现性能很低。
  • NSConditionLock也可以像NSCondition一样做多线程之间的任务等待调用,而且是线程安全的。
- (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. POSIX Conditions

  • POSIX条件锁需要互斥锁和条件两项来实现,虽然看起来没有什么关系,但在运行时中,互斥锁将会与条件结合起来。线程将被一个互斥和条件结合的信号来唤醒。
  • 首先初始化条件和互斥锁,当ready_to_gofalse的时候,进入循环,然后线程将会被挂起,直到另一个线程将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);
}

分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

NSDistributedLock

  • 处理多个进程或多个程序之间互斥问题。
  • 一个获取锁的进程或程序在是否锁之前挂掉,锁不会被释放,可以通过breakLock方式解锁。
  • iOS很少用到,暂不详细研究。

读写锁

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。

1. dispatch_barrier_async / dispatch_barrier_sync

先来一个需求:假设我们原先有6个任务要执行,我们现在要插入一个任务0,这个任务0要在1、2、4都并发执行完之后才能执行,而4、5、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");});
}
  • 共同点:
  1. 等待在它前面插入队列的任务先执行完;
  2. 等待他们自己的任务执行完再执行后面的任务。
  • 不同点:
  1. dispatch_barrier_sync将自己的任务插入到队列的时候,需要等待自己的任务结束之后才会继续插入被写在它后面的任务,然后执行它们;
  2. dispatch_barrier_async将自己的任务插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务插入队列,然后等待自己的任务结束后才执行后面的任务。

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:解锁");});

自旋锁

何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

1. OSSpinLock

  • 使用方式
// 初始化
spinLock = OS_SPINKLOCK_INIT;
// 加锁
OSSpinLockLock(&spinLock);
// 解锁
OSSpinLockUnlock(&spinLock);

不过,自旋锁存在优先级反转的问题。

2. os_unfair_lock

自旋锁已经不再安全,然后苹果推出了 os_unfair_lock_t ,这个锁解决了优先级反转的问题。

	os_unfair_lock_t unfairLock;unfairLock = &(OS_UNFAIR_LOCK_INIT);os_unfair_lock_lock(unfairLock);os_unfair_lock_unlock(unfairLock);

atomic(property) set / get

利用setter / getter 接口的属性实现原子操作,进而确保“被共享”的变量在多线程中读写安全,这已经是不能满足部分多线程同步要求。

  • 在定义 property 的时候, 有atomic 和 nonatomic的属性修饰关键字。
  • 对于atomic的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。
  • 而nonatomic就没有这个保证了。所以,nonatomic的速度要比atomic快。

atomic

  • 是默认的
  • 会保证 CPU 能在别的线程来访问这个属性之前,先执行完当前流程
  • 速度不快,因为要保证操作整体完成

nonatomic

  • 不是默认的
  • 更快
  • 线程不安全
  • 如有两个线程访问同一个属性,会出现无法预料的结果

注意,atomic一定线程安全吗?答案是否定的

因为atomic只是对属性的setter/getter方法加锁,所以说只能保证在调用setter/getter方法时线程安全。

假设有一个 atomic 的属性 “name”,如果线程 A 调[self setName:@"A"],线程 B 调[self setName:@"B"],线程 C 调[self name],那么所有这些不同线程上的操作都将依次顺序执行——也就是说,如果一个线程正在执行 getter/setter,其他线程就得等待。因此,属性 name 是读/写安全的。
但是,如果有另一个线程 D 同时在调[name release],那可能就会crash,因为 release 不受 getter/setter 操作的限制。也就是说,这个属性只能说是读/写安全的,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。

ONCE

GCD

多用于创建单例。

+ (instancetype) sharedInstance {static id __instance = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{__instance = [[self alloc] init];});return __instance;
}

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++;}
}

死锁问题

死锁是一个典型的并发问题,它在iOS中可能会发生。在iOS中,如果一个线程在等待一个任务完成,而这个任务又在等待该线程释放某种资源,那么就会产生一个死锁。尤其是在使用GCD(Grand Central Dispatch)NSOperation时,死锁问题可能会更频繁地出现。

Objective-C中的一个典型死锁示例是在主线程上使用同步调度块:

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{// Do some work...
});

在上面的例子中,我们要求主队列同步执行一个任务。但是,因为这是在主线程上发生的,并且主线程正在等待该任务完成,所以就产生了一个死锁。由于主线程已经被阻塞,所以它不能执行队列中的任务,而我们又在等待这个任务完成。

为了防止死锁,我们应该尽量避免在已经在执行任务的线程上同步调度任务。相反,我们应该用异步调度替代,或者使用并行队列来进行同步调度。

以下是一个可以避免死锁的改进示例:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{// Do some work...
});

在这个例子中,我们使用全局并行队列而不是主队列来同步执行任务。因此,主线程可以继续执行其他任务,而不会被阻塞。

或者:

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{// Do some work...
});

我们使用dispatch_async将一个任务放入了主队列中,并且没有设置任何等待这个任务完成的操作。这意味着任务会在主队列中异步执行,而主队列会一直处理任务直到任务完成,而不会等待任务执行完毕。

由于任务是在主队列中执行的,而且没有任何等待操作,主线程会继续往下执行代码,而不会被这个任务阻塞。


  1. 同步(sync)方式调用主队列(Main Dispatch Queue),因为主线程始终等待主队列来进行任务调度。但由于我们使用了 sync,这就意味着当前的主线程会等待我们的任务
    完成才能进行下一步,这样就形成了循环等待,造成了死锁。例如:
dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"This is a deadlock");
});

这种情况与第四种死锁是一致的,满足sync中向当前未完成的串行队列发送任务这一条件,不过这是在主线程中的主队列。

当然,凡事都有例外

// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行 selector 任务
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];/*** 同步执行 + 主队列* 特点(主线程调用):互等卡主不执行。* 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。*/
- (void)syncMain {NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程NSLog(@"syncMain---begin");dispatch_queue_t queue = dispatch_get_main_queue();dispatch_sync(queue, ^{// 追加任务 1[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程});dispatch_sync(queue, ^{// 追加任务 2[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程});dispatch_sync(queue, ^{// 追加任务 3[NSThread sleepForTimeInterval:2];              // 模拟耗时操作NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程});NSLog(@"syncMain---end");
}

当我们使用NSThread创建一个新的线程,而不是在主线程中“同步+主队列”,也不会发生卡死。

所以应该在这里默认一个前提:就是在主线程中执行。

  1. 递归锁中的死锁,比如使用 NSRecursiveLock。在同一个线程中,多次调用 lock,而比较少的或者忘记 unLock,由于一直在等待 unLock 的释放,这样同样会造成死锁情况:
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];[lock lock];// some code...[lock lock]; // this will cause deadlock!// some code...[lock unlock];
[lock unlock];
  1. 多线程操作同一资源导致的死锁。这是在多个线程之间出现的,比如线程 A 锁住了资源 X,想使用资源 Y,但资源 Y 正好被线程 B 锁住,线程 B 正好等待线程 A 的资源 X,这样也会形成死锁。
- (void)methodA {// locking resource X
}- (void)methodB {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// locking resource Y[self methodA]; // waiting for resource X to be released});[self methodB]; // waiting for resource Y to be released, hence causing deadlock
}
  1. 同步操作中向当前串行队列发送任务

由于我们自己创建并使用queue这个串行队列,但是queue中已经有了任务块,在块内添加任务3,会导致任务块不能完成导致死锁。所以最终结果是打印1,5,2,然后崩溃。这种情况是第一种死锁的父集。

dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{NSLog(@"2"); // 任务2dispatch_sync(queue, ^{  NSLog(@"3"); // 任务3});NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5

这些例子都揭示了产生死锁的可能性,避免死锁的方式最主要的就是避免同步调用,合理的使用锁以保证线程安全,对于资源的使用,注意资源的请求顺序,尽量减少资源的请求数等等。

相关文章:

iOS——锁与死锁问题

iOS中的锁 什么是锁锁的分类互斥锁1. synchronized2. NSLock3. pthread 递归锁1. NSRecursiveLock2. pthread 信号量Semaphore1. dispatch_semaphore_t2. pthread 条件锁1. NSCodition2. NSCoditionLock3. POSIX Conditions 分布式锁NSDistributedLock 读写锁1. dispatch_barri…...

恒运资本:股票总市值是什么意思?

职业新手可能会疑惑地问&#xff0c;股票总市值到底是什么意思&#xff1f;究竟&#xff0c;这是普通出资者常常看到的词汇&#xff0c;要了解股票总市值的含义&#xff0c;是需求了解金融商场的基本概念的。 股票总市值简介 股票的总市值是由公司一切的股票的数量乘以现在的价…...

Selenium Chrome Webdriver 如何获取 Youtube 悬停文本

导语 Youtube 是一个非常流行的视频分享平台&#xff0c;有时候我们可能想要爬取一些视频的信息&#xff0c;比如标题、播放量、点赞数等。但是有些信息并不是直接显示在网页上的&#xff0c;而是需要我们将鼠标悬停在某个元素上才能看到&#xff0c;比如视频的时长、上传时间…...

【LeetCode每日一题】——766.托普利茨矩阵

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【题目进阶】八【解题思路】九【时间频度】十【代码实现】十一【提交结果】 一【题目类别】 矩阵 二【题目难度】 简单 三【题目编号】 766.托普利茨矩阵 四【题目描述…...

第三方材料检测实验室LIMS系统源码 lims源码

实验室LIMS系统采用国际规范的业务管理流程和严格的质量控制体系&#xff0c;对每个检测流程节点采用 “人、机、料、法、环、测”进行质量控制&#xff0c;可记录&#xff0c;可追溯。强大的数据查询和统计分析能力&#xff0c;提高工作效率&#xff1b;自动化地采集实验室原始…...

【数据结构与算法——TypeScript】数组、栈、队列、链表

【数据结构与算法——TypeScript】 算法(Algorithm)的认识 解决问题的过程中&#xff0c;不仅仅 数据的存储方式会影响效率&#xff0c;算法的优劣也会影响效率 什么是算法&#xff1f; 定义&#xff1a; &#x1f7e2; 一个有限指令集&#xff0c;每条指令的描述不依赖于言语…...

[运维|中间件] Apache APISIX使用笔记

简介 Apache APISIX 是一个现代化、高性能、可扩展的开源 API 网关和微服务管理平台。 安装 快速安装 curl -sL https://run.api7.ai/apisix/quickstart | sh...

Android Intent 使用(详细版)

经典好文推荐,通过阅读本文,您将收获以下知识点: 一、通过组件名启动 二、通过包名、类名启动 三、通过类启动 四、打电话 五、发短信 六、打开网页 七、播放音乐 八、打开图片 九、创建闹钟 十、创建定时器 十一、添加日历事件 十二、拍照 十三、打开Camera 十四、打开视频录…...

【Clion 2】多行TODO使用

一、TODO: 说明&#xff1a; 有时需要标记部分代码以供将来参考&#xff1a; 优化和改进的领域、可能的更改、要讨论的问题等等。 支持&#xff1a; TODO和FIXME小写和大写。这些模式可以在任何受支持的文件类型的行注释和块注释内使用。 创建TODO项 在要添加注释的代码行中…...

【运维】hive 终端突然不能使用:Hive Schema version does not match metastore‘s schema version

文章目录 一. 问题描述二. 常规排查1. 元数据库2. hive-site.xml相关meta连接信息检查 三. 正解 一. 问题描述 进入hive终端&#xff0c;执行如下命令报错&#xff1a; hive> show tables; FAILED: SemanticException org.apache.hadoop.hive.ql.metadata.HiveException: …...

P1049 [NOIP2001 普及组] 装箱问题

题目描述 有一个箱子容量为 V&#xff0c;同时有 n 个物品&#xff0c;每个物品有一个体积。 现在从 n 个物品中&#xff0c;任取若干个装入箱内&#xff08;也可以不取&#xff09;&#xff0c;使箱子的剩余空间最小。输出这个最小值。 输入格式 第一行共一个整数 V&#…...

QCustomPlot获取选点坐标

QCustomPlot版本&#xff1a;Version: 2.1.1 设置点选择模式 customPlot->setInteractions(QCP::iSelectPlottables);2.绑定点击事件 connect(customPlot, &QCustomPlot::plottableClick, this, &CCustomPlot::onPlotClick);3.读取点位置 void CustomPlot::onP…...

Qt配置Android开发

1.使用Qt5.14.2 2.安装java和SDK&#xff0c;NDK 具体参考该博客【原创】基于Qt5.14的一站式安卓开发环境搭建_qt安卓开发环境搭建_Jamie.T的博客-CSDN博客 3.后续可能会遇到的问题&#xff1a; ①SDK配置问题&#xff1a; 若出现以下编译错误&#xff0c;是build-tools 2…...

花费7元训练自己的GPT 2模型

在上一篇博客中&#xff0c;我介绍了用Tensorflow来重现GPT 1的模型和训练的过程。这次我打算用Pytorch来重现GPT 2的模型并从头进行训练。 GPT 2的模型相比GPT 1的改进并不多&#xff0c;主要在以下方面&#xff1a; 1. GPT 2把layer normalization放在每个decoder block的前…...

性能分析工具

性能分析工具 valgrind 交叉编译 android arm/arm64 平台 valgrind android32 #!/usr/bin/env bashexport NDKROOT~/opt/android-ndk-r14b/ export AR$NDKROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar export LD$NDKROO…...

1.netty介绍

1.介绍 是JBOSS通过的java开源框架是异步的,基于事件驱动(点击一个按钮调用某个函数)的网络应用框架,高性能高可靠的网络IO程序基于TCP,面向客户端高并发应用/点对点大量数据持续传输的应用是NIO框架 (IO的一层层封装) TCP/IP->javaIO和网络编程–>NIO—>Netty 2.应用…...

【Jmeter】压测mysql数据库中间件mycat

目录 背景 环境准备 1、下载Jmeter 2、下载mysql数据库的驱动包 3、要进行测试的数据库 Jmeter配置 1、启动Jmeter图形界面 2、加载mysql驱动包 3、新建一个线程组&#xff0c;然后如下图所示添加 JDBC Connection Configuration 4、配置JDBC Connection Configurati…...

leetcode原题 路径总和 I II III(递归实现)

路径总和 I &#xff1a; 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。…...

【css】css设置表格样式-边框线合并

<style> table, td, th {border: 1px solid black;//设置边框线 }table {width: 100%; }td {text-align: center;//设置文本居中 } </style> </head> <body><table><tr><th>Firstname</th><th>Lastname</th><t…...

使用Flutter的image_picker插件实现设备的相册的访问和拍照

文章目录 需求描述Flutter插件image_picker的介绍使用步骤1、添加依赖2、导入 例子完整的代码效果 总结 需求描述 在应用开发时&#xff0c;我们有很多场景要使用到更换图片的功能&#xff0c;即将原本的图像替换设置成其他的图像&#xff0c;从设备的相册或相机中选择图片或拍…...

数学建模体系

1评价类 主观求权重&#xff1a;层次分析法客观求权重&#xff1a;TOPSIS综合评价&#xff1a;典型相关分析 2预测插值算法拟合多元回归分析时间序列分析、ARCH和garch模型岭回归和lasso回归 3关系相关系数典型相关分析多元回归分析灰色关联分析 4图最短路径&#xff1a;迪杰斯…...

13.7 CentOS 7 环境下大量创建帐号的方法

13.7.1 一些帐号相关的检查工具 pwck pwck 这个指令在检查 /etc/passwd 这个帐号配置文件内的信息&#xff0c;与实际的主文件夹是否存在等信息&#xff0c; 还可以比对 /etc/passwd /etc/shadow 的信息是否一致&#xff0c;另外&#xff0c;如果 /etc/passwd 内的数据字段错…...

HTML5 Canvas(画布)

<canvas>标签定义图形&#xff0c;比如图表和其他图像&#xff0c;你必须用脚本来绘制图形。 在画布上&#xff08; Canvas &#xff09;画一个共红色矩形&#xff0c;渐变矩形&#xff0c;彩色矩形&#xff0c;和一些彩色文字。 什么是 Canvas&#xff1f; HTML5<c…...

io的异常处理以及properties

try(流对象的创建) { 对象的处理逻辑} catch(IOException e) { 异常的处理逻辑} public static void test4(){try(FileWriter fwnew FileWriter("a.txt",true); ){char[] cbuf{a};//写入一个字符串组fw.write(cbuf);}catch(IOException e){e.printStackTrace();}}上面…...

Linux下基于Dockerfile构建镜像应用(1)

目录 基于已有容器创建镜像 Dockerfile构建SSHD镜像 构建镜像 测试容器 可以登陆 Dockerfile构建httpd镜像 构建镜像 测试容器 Dockerfile构建nginx镜像 构建镜像 概述&#xff1a; Docker 镜像是Docker容器技术中的核心&#xff0c;也是应用打包构建发布的标准格式。…...

JS中常见的模块管理规范梳理

一、CommonJS规范 CommonJS规范是一种用于JavaScript模块化开发的规范&#xff0c;它定义了模块的导入、导出方式和加载机制&#xff0c;主要用在Node开发中。 1. 使用场景 服务器端开发&#xff1a;Node.js是使用CommonJS规范的&#xff0c;因此在服务器端开发中&#xff0…...

3维空间下按平面和圆柱面上排版设计

AR空间中将若干平面窗口排列在指定平面或圆柱体面上 平面排版思路 指定平面方向向量layout_centre ,平面上的一点作为排版版面的中心layout_position float3 layout_position = float3(0,0,-10); float3 layout_centre = float3(0,0,1...

【Spring框架】Spring AOP

目录 什么是AOP&#xff1f;AOP组成Spring AOP 实现步骤Spring AOP实现原理JDK Proxy VS CGLIB 什么是AOP&#xff1f; AOP&#xff08;Aspect Oriented Programming&#xff09;&#xff1a;⾯向切⾯编程&#xff0c;它是⼀种思想&#xff0c;它是对某⼀类事情的集中处理。⽐如…...

寻找旋转排序数组中的最小值——力扣153

文章目录 题目描述解法 二分法 题目描述 解法 二分法 int findMin(vector<int>& nums){int l0, rnums.size()-1;while(l<r){int mid (lr)/2;if(nums[mid]<nums[r]) rmid;else lmid1;}return nums[l];}...

安卓逆向 - 基础入门教程

一、引言 1、我们在采集app数据时&#xff0c;有些字段是加密的&#xff0c;如某麦网x-mini-wua x-sgext x-sign x-umt x-utdid等参数&#xff0c;这时候我们需要去分析加密字段的生成。本篇将以采集的角度讲解入门安卓逆向需要掌握的技能、工具。 2、安卓&#xff08;Androi…...