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 = 0
    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了。

锁的分类

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

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

使用异步执行主队列,async允许等待当前队列的任务先执行,也不会阻塞。


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

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

  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"); // 任务2
    dispatch_sync(queue, ^{  
        NSLog(@"3"); // 任务3
    });
    NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/55825.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++设计模式之责任链设计模式

C责任链设计模式 什么是责任链设计模式 责任链设计模式是一种行为型设计模式&#xff0c;它允许多个处理请求的对象串联起来&#xff0c;形成一个处理请求的链。每个对象都有机会处理请求&#xff0c;如果该对象不能处理请求&#xff0c;则将请求传递给链中的下一个对象。 该…

2023年华数杯建模思路 - 案例:随机森林

## 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是随机森林&#xff1f; 随机森林属于 集成学习 中的 Bagging&#xff08;Bootstrap AGgregation 的简称&#xff09; 方法。如果用图来表示他们之…

51单片机学习--蜂鸣器播放音乐

由原理图可知&#xff0c;蜂鸣器BEEP与P1_5 相关&#xff0c;但其实这个原理图有错&#xff0c;实测接的是P2_5 下面这个代码就是以500HZ的频率响500ms的例子 sbit Buzzer P2^5;unsigned char KeyNum; unsigned int i;void main() {while(1){KeyNum Key();if(KeyNum){for(i …

亚马逊买家账号ip关联怎么处理

对于亚马逊买家账号&#xff0c;同样需要注意IP关联问题。在亚马逊的眼中&#xff0c;如果多个买家账号共享相同的IP地址&#xff0c;可能会被视为潜在的操纵、违规或滥用行为。这种情况可能导致账号受到限制或处罚。 处理亚马逊买家账号IP关联问题&#xff0c;建议采取以下步骤…

实用干货!一文读懂Salesforce中6种数据关系类型!

Salesforce中对象之间的数据关系可能是一个棘手的话题。对于创建自定义对象的业务场景&#xff0c;需要决定使用哪些关系类型来扩展Salesforce数据模型。 01 查找关系 查找关系&#xff08;Lookup Relationships&#xff09;是一种松散耦合&#xff08;loosely coupled&…

[React]生命周期

前言 学习React&#xff0c;生命周期很重要&#xff0c;我们了解完生命周期的各个组件&#xff0c;对写高性能组件会有很大的帮助. Ract生命周期 React 生命周期分为三种状态 1. 初始化 2.更新 3.销毁 初始化 1、getDefaultProps() 设置默认的props&#xff0c;也可以用duf…

【Quartus FPGA】EMIF DDR3 读写带宽测试

在通信原理中&#xff0c;通信系统的有效性用带宽来衡量&#xff0c;带宽定义为每秒传输的比特数&#xff0c;单位 b/s&#xff0c;或 bps。在 DDR3 接口的产品设计中&#xff0c;DDR3 读/写带宽是设计者必须考虑的指标。本文主要介绍了 Quartus FPGA 平台 EMIF 参数配置&#…

Tomcat 创建https

打开CMD,按下列输入 keytool -genkeypair -alias www.bo.org -keyalg RSA -keystore d:\ambition.keystore -storetype pkcs12 输入密钥库口令:123456 再次输入新口令:123456 您的名字与姓氏是什么? [Unknown]: www.ambition.com 您的组织单位名称是什么? [Unknown…

cc2652在使用过程中的一些注意事项

可能不只是cc2652有这些坑&#xff0c;估计cc26xx系列都存在。 CCS的预编译宏配置位置 时钟获取 时钟获取__STATIC_INLINE uint32_t SysCtrlClockGet( void )在sys_ctrl.h中&#xff0c;sys_ctrl.h没有在工程路径下面&#xff0c;在其sdk中 节拍时间获取 ICall_getTicks(); …

IPv6 over IPv4隧道配置举例

配置IPv6 over IPv4手动隧道示例 组网需求 如图1所示&#xff0c;两台IPv6主机分别通过SwitchA和SwitchC与IPv4骨干网络连接&#xff0c;客户希望两台IPv6主机能通过IPv4骨干网互通。 图1 配置IPv6 over IPv4手动隧道组网图 配置思路 配置IPv6 over IPv4手动隧道的思路如下&…

roop 视频换脸

roop: one click face swap. 只用一张人脸图片&#xff0c;就能完成视频换脸。 项目地址&#xff1a; https://github.com/s0md3v/roopColab 部署&#xff1a; https://github.com/dream80/roop_colab 本文是本地部署的实践记录。 环境基础 OS: Ubuntu 22.04.2 LTSKernel: 5…

Redis实战(4)——Redisson分布式锁

1 基于互斥命令实现分布式锁的弊端 根据上篇文章基于redis互斥命令实现的分布式锁任然存在一定的弊端 1无法重入: 同一个线程无法重新获得同一把锁2超时删除 &#xff1a;会因为超时、任务阻塞而自动释放锁&#xff0c;出现其他线程抢占锁出现并行导致线程不安全的问题3 不可…

unity行为决策树实战详解

一、行为决策树的概念 行为决策树是一种用于游戏AI的决策模型&#xff0c;它将游戏AI的行为分解为一系列的决策节点&#xff0c;并通过节点之间的连接关系来描述游戏AI的行为逻辑。在行为决策树中&#xff0c;每个节点都代表一个行为或决策&#xff0c;例如移动、攻击、逃跑等…

LInux的安装(VMware,网卡设置,SSH连接工具)

Linux的安装 1、安装方式介绍 1.安装方式: 物理机安装:直接将操作系统安装到服务器硬件上 虚拟机安装:通过虚拟机软件安装 **虚拟机( Virtual Machine&#xff09;**指通过软件模拟的具有完整硬件系统功能、运行在完全隔离环境中的完整计算机系统。 2、安装Linux 在官网将…

20款奔驰S350升级原厂HUD抬头显示系统,提升您的行车安全

HUD是平视显示器的简称&#xff0c;它原先是运用在航空器上的飞行辅助仪器。指飞行员不需要低头&#xff0c;就能够看到他需要的重要资讯。由于HUD的方便性以及能够提高飞行安全&#xff0c;这项技术后来也发展到汽车行业。汽车搭载的HUD抬头数字显示功能&#xff0c;是利用光学…

Toolformer :让AI学会使用工具

paper: 《Toolformer: Language Models Can Teach Themselves to Use Tools 》 核心思想&#xff0c; 1. Sampling API Calls &#xff1a;设计设计prompt,让模型生成含API调用的文本&#xff08;如图3&#xff09;&#xff0c;只保留K个概率最高的API调用 2. Executing API …

MySql006——基本的SELECT查询语句

在《MySql003——结构化查询语言SQL基础知识》中&#xff0c;我们学习了有关SQL的基础知识&#xff0c;也知道SQL中查询语句SELECT使用最为频繁 接下来我们将学习一些基本的SELECT查询语句 一、SELECT语句的通用语法 在MySQL数据库中&#xff0c;使用SELECT语句可以查询数据…

Android Studio多渠道打包

使用环境&#xff1a; Android studio 多渠道打包 使用方法&#xff1a; 1 APP下build.gradle文件 flavorDimensions "default"productFlavors {huawei {dimension "default"manifestPlaceholders [ channel:"huawei" ]}xiaomi {dimension &…

Nginx学习教程(基础篇)

目录 一、Nginx安装 二、Nginx基本使用 2.1、目录结构 conf html logs sbin 2.2、基本运行原理 2.3、nginx.conf最小配置解析 worker_processes worker_connections include mime.types default_type application/octet-stream sendfile on keepalive_timeout…

Python实现GA遗传算法优化循环神经网络分类模型(LSTM分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;最早是由美国的 John holland于20世…