【iOS多线程(四)】线程安全+13种锁

news2024/9/23 7:30:34

线程安全+13种锁

    • 线程安全
    • 1. 为什么要线程安全
      • 出现线程安全的原理
      • 解决方法
    • 2. 自旋锁和互斥锁
      • 自旋锁(Spin lock)
      • 互斥锁
      • 两种锁的加锁原理
      • 对比
      • 两种锁的应用
    • 3. 13种锁
      • 1. OSSpinLock (已弃用)
      • 2. os_unfair_lock
      • 3.pthread_mutex
    • 4. NSLock
    • 5. NSRecursiveLock
    • 6. NSCondition
    • 7. NSConditionLock
    • 8.dispatch_semaphore
    • 9. dispatch_queue
    • 10. @synchronized
    • 11. atomic
    • 12. pthread_rwlock:读写锁
    • 13. dispatch_barrier_async
  • 锁的性能比较

线程安全

  1. 为什么要线程安全
  2. 自旋锁和互斥锁
  3. 锁的类型(13种锁)
    1. OSSpinLock
    2. os_unfair_lock
    3. pthread_mutex
    4. dispatch_semaphore
    5. dispatch_queue(DISPATCH_QUEUE_SERIAL)
    6. NSLock
    7. NSRecursiveLock
    8. NSCondition
    9. NSConditionLock
    10. @synchronized
    11. pthread_rwlock
    12. dispatch_barrier_async
    13. atomic
  4. 锁的性能比较

1. 为什么要线程安全

多个线程访问同一块资源的时候,很容易引发数据混乱,一个大家都喜欢拿来举的例子就是卖火车票,今天我使用这个案例,假设有100张票,同时开5个窗口卖票,看看结果如何


//卖票演示
- (void)ticketTest{
    self.ticketsCount = 50;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(queue, ^{
            for (int i = 0; i < 10; i++) {
                [self sellingTickets];
            }
        });
    }

}
//卖票
- (void)sellingTickets{
    int oldMoney = self.ticketsCount;
    sleep(.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;

    NSLog(@"当前剩余票数-> %d", oldMoney);
}

在这里插入图片描述

正常情况下我有50张票,然后卖了50次,剩余票数应该为0,但是打印结果是3,所以这里就存在了线程安全问题。

出现线程安全的原理

在这里插入图片描述

出现线程安全的原因就是在同一个时间,多个线程同时读取一个值,像线程A和B同时读取了当前票数为10,等于是卖了两张票,但是总票数其实就减少了一张。

解决方法

使用线程同步计数,按照预定的先后次序依次进行,常见的线程同步计数就是加锁

在这里插入图片描述

2. 自旋锁和互斥锁

自旋锁(Spin lock)

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就会一直循环在那里看是否该自旋锁的保持者已经释放了锁(轮询),“自旋”一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。虽然它的效率比互斥锁高,单一它也有些不足之处:

  1. 自旋锁会一直占用CPU,他在未获得锁的情况下,一直运行 — 自旋,所以占用着CPU,如果不能在很短时间内获得锁,这无疑会使CPU效率降低
  2. 在用自旋锁时很可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如copy_to_user()、copy_from_user()、kmalloc()等。 因此我们要慎重使用自旋锁,自旋锁只有在内核抢占式或SMP等情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空。自旋锁适用于锁保持着保持锁时间比较短的情况下。

互斥锁

互斥锁属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和Core1上(Core是处理器核心)。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正在被线程B锁持有,那么线程A就会被阻塞(blocking),Core0会在此时进行上下文切换(Contex Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而自旋锁则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在Core0上进行忙等待,并不停的动进行锁清秋,直到得到这个锁为止。互斥锁用于持锁时间比较长的操作

两种锁的加锁原理

互斥锁:线程会从sleep(加锁)-> running(解锁), 过程中有上下文的切换,cpu的抢占,信号的发送等开销。
自旋锁:线程一直是running(加锁->解锁),死循环检测锁的标志位(轮询),机制不复杂。

对比

互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu。起始开销虽然低于互斥锁,但随着持锁时间编程,加锁的开销是线程增长。

两种锁的应用

临界区 : 临界区是一段访问共享资源的代码,在同一时刻只能有一个线程/进程进入执行。如果有多个线程/进程同时进入,可能会导致数据竞争和不一致的问题。

互斥锁 :用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑

    1. 临界区有IO操作
    1. 临界区代码复杂或者循环量大
    1. 临界区竞争非常激烈
    1. 单核处理器

自旋锁:就主要用于临界区持锁时间非常短且CPU资源不紧张的情况下,自旋锁一般用于多核的处理器。

3. 13种锁

1. OSSpinLock (已弃用)

OSSpinLock叫做“自旋锁”,使用时需要导入头文件#import <libkern/OSAtomic.h>


//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//加锁
OSSpinLockLock(&lock);
//解锁
OSSpinLockUnlock(&lock);

OSSpinLock在iOS10.0以后就被弃用了,可以使用os_unfair_lock_lock替代。而且还有一些安全性问题,
不再安全的OSSpinLock

2. os_unfair_lock

os_unfair_lock用于取代不安全的OSSpinLock,从iOS开始才支持。从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,而并非忙等。使用需要导入头文件#import<os/lock.h>


//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);

demo


#import "os_unfair_lockDemo.h"
#import <os/lock.h>
@interface os_unfair_lockDemo()
@property (assign, nonatomic) os_unfair_lock ticketLock;
@end

@implementation os_unfair_lockDemo
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.ticketLock = OS_UNFAIR_LOCK_INIT;
    }
    return self;
}


//卖票
- (void)sellingTickets{
    os_unfair_lock_lock(&_ticketLock);

    [super sellingTickets];

    os_unfair_lock_unlock(&_ticketLock);
}
@end

3.pthread_mutex

mutex叫做“互斥锁”,等待锁的线程会处于休眠状态。需要导入头文件#import <pthread.h>
使用步骤:

  1. 初始化锁的属性

pthread_mutexattr_t attr;  // 定义一个(锁的)属性 
pthread_mutexattr_init(&attr); // 初始化这个属性 
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 设置属性类型 

/*
* Mutex type attributes
*/
#define PTHREAD_MUTEX_NORMAL        0  //默认互斥锁 
#define PTHREAD_MUTEX_ERRORCHECK    1  //这种类型互斥锁提供了错误检查功能  
#define PTHREAD_MUTEX_RECURSIVE     2  // 支持递归的互斥锁 
#define PTHREAD_MUTEX_DEFAULT        PTHREAD_MUTEX_NORMAL

    1. PTHREAD_MUTEX_NORMAL:
      • 这是 POSIX 互斥锁的默认类型。
      • 当一个线程尝试获取已被另一个线程持有的锁时,该线程会阻塞。
      • 如果一个线程尝试重复获取已被自己持有的锁,行为是未定义的。这可能会导致死锁。
    1. PTHREAD_MUTEX_ERRORCHECK:
      • 这种类型的互斥锁提供了错误检查功能。
      • 当一个线程尝试获取已被自己持有的锁时,该函数会返回一个错误。
      • 这有助于避免因重复获取同一锁而导致的死锁问题。
    1. PTHREAD_MUTEX_RECURSIVE:
      • 这种类型的互斥锁支持递归获取
      • 当一个线程已经持有该锁时,它可以再次获取该锁而不会被阻塞。
      • 线程需要释放同等次数的锁,才能使得该锁完全释放。
    1. PTHREAD_MUTEX_DEFAULT:
      • 这是互斥锁的默认类型,等同于 PTHREAD_MUTEX_NORMAL。
      • 当没有显式指定互斥锁类型时,系统会使用这种默认类型。
  1. (用属性)初始化锁

// 初始化锁
pthread_mutex_init(mutex, &attr);

  1. 初始化锁结束以后,销毁属性

// 销毁属性
pthread_mutexattr_destroy(&attr);

  1. 加锁解锁

pthread_mutex_lock(&_mutex);
pthread_mutex_unlock(&_mutex);

  1. 销毁锁

pthread_mutex_destroy(&_mutex);

备注:我们可以不初始化属性,在传属性的时候直接传NULL,表示使用默认属性PTHREAD_MUTEX_NORMAL
pthread_mutex_init(mutex, NULL);

具体代码


#import "pthread_mutexDemo.h"
#import <pthread.h>
@interface pthread_mutexDemo()
@property (assign, nonatomic) pthread_mutex_t ticketMutex;
@end

@implementation pthread_mutexDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 初始化属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
        // 初始化锁
        pthread_mutex_init(&(_ticketMutex), &attr);
        // 销毁属性
        pthread_mutexattr_destroy(&attr);
    }
    return self;
}

//卖票
- (void)sellingTickets{
    pthread_mutex_lock(&_ticketMutex);

    [super sellingTickets];

    pthread_mutex_unlock(&_ticketMutex);
}
@end

死锁 : 我们稍微修改一下代码


//卖票
- (void)sellingTickets{
    pthread_mutex_lock(&_ticketMutex);
    [super sellingTickets];
    [self sellingTickets2];
    pthread_mutex_unlock(&_ticketMutex);
}


- (void)sellingTickets2{
    pthread_mutex_lock(&_ticketMutex);
    NSLog(@"%s",__func__);
    pthread_mutex_unlock(&_ticketMutex);
}

在这里插入图片描述

上面的代码就会造成线程死锁。由于 _ticketMutex已经被当前线程持有,根据 PTHREAD_MUTEX_NORMAL 类型的锁语义, 当线程再次尝试获取已被自己持有的锁时,会发生死锁。

解决方法:

    1. 使用的pthread_mutex_t的属性PTHREAD_MUTEX_RECURSIVE

PTHREAD_MUTEX_RECURSIVE递归锁:允许同一个线程对同一把锁进行重复加锁。
要考重点同一个线程和同一把锁

    1. 在方法sellingTickets2中重新再创建一把新的锁,两个方法的锁对象不同,就不会造成线程死锁了。(也就是不使用同一把锁)
    1. 让方法sellingTickets2在另一个线程上运行。(不在同一个线程加锁)但是这样会导致代码逻辑与之前不同了。

- (void)__saleTicket {
    pthread_mutex_lock(&_ticketMutex);
    [super __saleTicket];
    [self performSelectorInBackground:@selector(__saleTicket2) withObject:nil];
    pthread_mutex_unlock(&_ticketMutex);
}

- (void)__saleTicket2 {
    pthread_mutex_lock(&_ticketMutex);
    NSLog(@"%s", __func__);
    pthread_mutex_unlock(&_ticketMutex);
}

在这里插入图片描述

条件


// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(&_mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);

// 初始化条件
pthread_cond_t condition
pthread_cond_init(&_cond, NULL);

// 等待条件
pthread_cond_wait(&_cond, &_mutex);

//激活一个等待该条件的线程
pthread_cond_signal(&_cond);
//激活所有等待该条件的线程
pthread_cond_broadcast(&_cond);

//销毁资源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);

使用案例:假设我们有一个数组,里面有两个线程,一个是添加数组,一个是删除数组,我们先调用删除数组,再调用添加数组,但是在数组为空的时候不调用删除数组。


#import "pthread_mutexDemo1.h"
#import <pthread.h>

@interface pthread_mutexDemo1()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation pthread_mutexDemo1

- (instancetype)init
{
    if (self = [super init]) {
        // 初始化属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        // 初始化锁
        pthread_mutex_init(&_mutex, &attr);
        // 销毁属性
        pthread_mutexattr_destroy(&attr);

        // 初始化条件
        pthread_cond_init(&_cond, NULL);

        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];

    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 线程1
// 删除数组中的元素
- (void)__remove
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");

    if (self.data.count == 0) {
        // 等待
        pthread_cond_wait(&_cond, &_mutex);
    }

    [self.data removeLastObject];
    NSLog(@"删除了元素");

    pthread_mutex_unlock(&_mutex);
}

// 线程2
// 往数组中添加元素
- (void)__add
{
    pthread_mutex_lock(&_mutex);

    sleep(1);

    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");

    // 激活一个等待该条件的线程
    pthread_cond_signal(&_cond);

    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

  1. pthread_cond_wait(&_cond, &_mutex):

    • 作用:
      • 该函数用于当前线程等待一个条件变量 (_cond) 的信号。
      • 在调用该函数时,当前线程会释放关联的互斥锁 (_mutex),并进入休眠状态,直到另一个线程发出条件变量的信号。
    • 用法:
      • 在调用该函数之前,必须先获取关联的互斥锁 (_mutex)。
      • 当条件变量被信号唤醒时,线程会重新获取互斥锁 (_mutex),然后继续执行后续的代码。
  2. pthread_cond_signal(&_cond):

    • 作用:
      • 该函数用于向一个条件变量 (_cond) 发出信号,通知等待该条件变量的一个线程可以继续执行。
      • 如果有多个线程正在等待该条件变量,则只唤醒其中一个线程。
    • 用法:
      • 通常在完成某些操作后,或者满足某些条件时,使用该函数向等待的线程发出信号。
      • 在调用该函数之前,必须先获取关联的互斥锁 (_mutex)。

4. NSLock

NSLock是对mutex普通锁的封装。pthread_mutex_init(mutex, NULL);

NSLock 遵循 NSLocking 协议。lock 方法是加锁,unlock 是解锁,tryLock 是尝试加锁,如果失败的话返回 NO,lockBeforeDate: 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。


@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

@interface NSLock : NSObject <NSLocking> {
    @private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name
@end

使用起来也是十分的简单


#import "LockDemo.h"
@interface LockDemo()
@property (strong, nonatomic) NSLock *ticketLock;
@end

@implementation LockDemo
//卖票
- (void)sellingTickets{
    [self.ticketLock lock];
    [super sellingTickets];
    [self.ticketLock unlock];
}

@end

5. NSRecursiveLock

NSRecursiveLock是对mutex递归锁的封装,API跟NSLock基本一致


#import "RecursiveLockDemo.h"
@interface RecursiveLockDemo()
@property (nonatomic,strong) NSRecursiveLock *ticketLock;
@end

@implementation RecursiveLockDemo
//卖票
- (void)sellingTickets{
    [self.ticketLock lock];
    [super sellingTickets];
    [self.ticketLock unlock];
}
@end

6. NSCondition

NSCondition是对mutexcond的封装,更加面向对象,我们使用起来也更加的方便简洁


@interface NSCondition : NSObject <NSLocking> {
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name 
@end

对于上面那个数组操作的案例我们就可以变成这个样子了


// 线程1
// 删除数组中的元素
- (void)__remove
{
    [self.condition lock];
    if (self.data.count == 0) {
        // 等待
        [self.condition wait];
    }
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    [self.condition unlock];
}

// 线程2
// 往数组中添加元素
- (void)__add
{
    [self.condition lock];
    sleep(1);
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 信号
    [self.condition signal];
    [self.condition unlock];
}

7. NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值


@interface NSConditionLock : NSObject <NSLocking> {
 
- (instancetype)initWithCondition:(NSInteger)condition;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name;
@end

里面有三个常用的方法

    1. initWithCondition: 初始化Condition,并且设置状态值
    1. lockWhenCondition:(NSInteger)condition: 当前状态值为condition的时候加锁
    1. unLockWithCondition:(NSInterger)condition解锁,并将状态值设置为condition

@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end
@implementation NSConditionLockDemo
- (instancetype)init
{
    if (self = [super init]) {
        self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
}

- (void)__one
{
    [self.conditionLock lock];
    NSLog(@"__one");
    sleep(1);
    [self.conditionLock unlockWithCondition:2];
}

- (void)__two
{
    [self.conditionLock lockWhenCondition:2];
    NSLog(@"__two");
    [self.conditionLock unlockWithCondition:3];
}
@end

8.dispatch_semaphore

  • semaphore叫做“信号量”,设计目的是用来控制对共享资源的访问,确保资源不会被过度使用。
  • 信号量的初始值可以用来控制线程并发访问的最大数量。
  • 信号量的初始值为1,代表同时只允许一条线程访问资源,保证线程同步。
  • 信号量的值不能超过它的初始值。信号量的初始值代表了系统中可用的某种资源的数量。当线程获取资源时,信号量的值会减少;当线程释放资源时,信号量的值会增加。
//表示最多开启5个线程
dispatch_semaphore_create(5);
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);

@interface dispatch_semaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@end

@implementation dispatch_semaphoreDemo
- (instancetype)init
{
    if (self = [super init]) {
        self.semaphore = dispatch_semaphore_create(1); //信号量初始值为1,相当于锁 
    }
    return self;
}
- (void)otherTest
{
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}
- (void)test
{

    //dispatch_semaphore_wait:
    // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);

    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);

    // 让信号量的值+1
    dispatch_semaphore_signal(self.semaphore);
}
@end

我们在运行代码打印的时候发现,每隔一秒出现一次打印。虽然我们同时开启20个线程,但是一次只能访问一条线程的资源。

9. dispatch_queue

使用GCD的串行队列也可以实现线程同步


dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
    // 追加任务1
    for (int i = 0; i < 2; ++i) {
        NSLog(@"1---%@",[NSThread currentThread]);
    }
});

dispatch_sync(queue, ^{
    // 追加任务2
    for (int i = 0; i < 2; ++i) {
        NSLog(@"2---%@",[NSThread currentThread]);
    }
});

10. @synchronized

@synchronized是对mutex递归锁的封装,@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作


//卖票
- (void)sellingTickets{
    @synchronized ([self class]) {
        [super sellingTickets];
    }
}

对于实现底层我们可以在objc4的objc-sync.mm文件中找到synchronized就是在开始和结束的时候调用了objc_sync_enter & objc_sync_exit方法

objc_sync_enter实现


int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;

    if (obj) {
        SyncData* data = id2data(obj, ACQUIRE);
        assert(data);
        data->mutex.lock();
    } else {
        // @synchronized(nil) does nothing
        if (DebugNilSync) {
            _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
        }
        objc_sync_nil();
    }

    return result;
}

就是根据id2data方法找到一个data对象,然后在对data对象进行mutex.lock()加锁操作。我们点击进入id2data方法继续查找

#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;

发现获取data对象的方法其实就是根据sDataLists[obj].data这个方法来实现的,也就是一个哈希表。

@synchronized详解

11. atomic

    1. atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
    1. 可以参考源码objc4的objc-accessors.mm
    1. 它并不能保证属性的过程是线程安全的

12. pthread_rwlock:读写锁

pthread_rwlock经常用于文件等数据的读写操作,需要导入头文件#import <pthread.h>

iOS中的读写安全方案需要注意一下场景,即多读单写

    1. 同一时间,只能有1个线程进行写的操作
    1. 同一时间,允许有多个线程进行读的操作
    1. 同一时间,不允许既有写的操作,由有读的操作

//初始化锁
pthread_rwlock_t lock;
pthread_rwlock_init(&_lock, NULL);

//读加锁
pthread_rwlock_rdlock(&_lock);
//读尝试加锁
pthread_rwlock_trywrlock(&_lock)

//写加锁
pthread_rwlock_wrlock(&_lock);
//写尝试加锁
pthread_rwlock_trywrlock(&_lock)

//解锁
pthread_rwlock_unlock(&_lock);
//销毁
pthread_rwlock_destroy(&_lock);


#import <pthread.h>
@interface pthread_rwlockDemo ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end

@implementation pthread_rwlockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 初始化锁
        pthread_rwlock_init(&_lock, NULL);
    }
    return self;
}

- (void)otherTest{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            [self read];
        });
        dispatch_async(queue, ^{
            [self write];
        });
    }
}
- (void)read {
    pthread_rwlock_rdlock(&_lock);
    sleep(1);
    NSLog(@"%s", __func__);
    pthread_rwlock_unlock(&_lock);
}
- (void)write
{
    pthread_rwlock_wrlock(&_lock);
    sleep(1);
    NSLog(@"%s", __func__);
    pthread_rwlock_unlock(&_lock);
}
- (void)dealloc
{
    pthread_rwlock_destroy(&_lock);
}
@end

在这里插入图片描述

我们可以发现读操作1s有可能出现多次,但是写操作不会。

13. dispatch_barrier_async

这个函数传入的并发队列必须是自己通过dispatch_queue_creat创建的,如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果


//初始化
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
//读操作
dispatch_async(self.queue, ^{
});
//写操作
dispatch_barrier_async(self.queue, ^{
 
});

锁的性能比较

性能由高到低排序:

1、os_unfair_lock (重点看)
2、OSSpinLock(弃用)(重点看)
3、dispatch_semaphore (重点看)
4、pthread_mutex (重点看)
5、dispatch_queue(DISPATCH_QUEUE_SERIAL)
6、NSLock(对mutex普通锁的封装)
7、NSCondition (对mutex和cond的封装,更加面向对象)
8、pthread_mutex(recursive)(重点看)
9、NSRecursiveLock (对mutex递归锁的封装)
10、NSConditionLock (对NSCondition的进一步封装)
11、@synchronized (对mutex递归锁的封装)

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ IOStream

IOStream 类流特性 不可赋值和复制缓冲重载了<< >> 状态位 示例 状态位操作函数coutcin getget(s,n)/get(s,n,d):getline otherif(!fs)/while(cin) operator void*()与 operator!()代码示例 File Stream open 函数 文件打开方式 文件读写 读写接口 一次读一个字符…

SpringBoot学习之EasyExcel解析合并单元格(三十九)

本解析主要采用反射来修改EasyExcel 返回的默认数据结构实现。 一、待解析表格 二、依赖 全部pom.xml文件如下,仅作参考: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLo…

LLM - 使用 HuggingFace + Ollama 部署最新大模型 (GGUF 格式 与 Llama 3.1)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/141028040 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 Ollama…

创建一个自己的列表窗口

文章目录 背景&#xff1a;在QT的设计中&#xff0c;对于控件库提供的控件满足不了项目的需求&#xff0c;就像自定义一些控件&#xff0c;本文是自定义一个列表窗口。效果展示 一、创建基本的QT模板&#xff1a;1.创建mainwindow2.创建VerticalTextDelegate 二&#xff1a; 插…

零拷贝的发展历程

零拷贝 零拷贝是指计算机执行 IO 操作时&#xff0c;CPU 不需要将数据从一个存储区域复制到另一个存储区域&#xff0c;从而可以减少上下文切换以及 CPU的拷贝时间。它是一种I/O 操作优化技术。 传统IO的执行流程&#xff1a;传统的 IO 流程&#xff0c;包括 read 读 和 write…

2024.8.08(python)

一、搭建python环境 1、检查是否安装python [rootpython ~]# yum list installed | grep python [rootpython ~]# yum list | grep python3 2、安装python3 [rootpython ~]# yum -y install python3 安装3.12可以使用源码安装 3、查看版本信息 [rootpython ~]# python3 --vers…

C++(类和对象.下)

类型转换 先给出以下代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class B { private:int _a1; public:B(int a){_a1 a;}void Print() {cout << _a1<< endl;} }; int main() {//实例化对象1B b(1);b.Pri…

video-retalking部署安装,在服务器Ubuntu22.04系统下

video-retalking部署安装&#xff0c;在服务器Ubuntu22.04系统下 一、ubuntu基本环境配置1.更新包列表&#xff1a;2. 安装英伟达显卡驱动2.1 使用wget在命令行下载驱动包2.2 更新软件列表和安装必要软件、依赖2.2 卸载原有驱动2.3 安装驱动2.4 安装CUDA2.5 环境变量配置 二、安…

基于YOLOv10深度学习的交通信号灯检测识别系统【python源码+Pyqt5界面+数据集+训练代码】红绿灯检测、目标检测、人工智能

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

易语言窗口1以及其它子窗口在任务栏显示的方法以及源代码

大家都知道在启动子程序中载入窗口里&#xff0c;窗口就不在任务栏中显示了 用什么办法可以解决这个问题呢 本例程教源码教你如何解决

零拷贝:原理与实现

1. 简介 I/O 或输入/输出通常意味着中央处理器 (CPU) 与外部设备&#xff08;如磁盘、鼠标、键盘等&#xff09;之间的读写。在深入研究零拷贝之前&#xff0c;有必要指出磁盘 I/O&#xff08;包括磁盘设备和其他块导向设备&#xff09;和网络 I/O 之间的区别。 磁盘 I/O 的常…

Vue3简单介绍和快速体验

目录 前言 1. Vue3介绍 1.1 Vue的两个核心功能&#xff1a; 1.2 Vue作者介绍 2. Vue3快速体验(非工程化方式) 2.1 所有代码 2.2 导入js 2.3 一些基本指令 2.4 app对象的创建和挂载 ​ 前言 在学习Vue3之前,自己已经学习过的vue2了,在学习Vue3后还是能感觉到2和3在使用…

媒体资讯视频数据采集-yt-dlp-python实际使用-下载视频

对于视频二创等一些业务场景&#xff0c;可能要采集youtube等的相关媒体视频资源&#xff0c;使用[yt-dlp](https://github.com/yt-dlp/yt-dlp)是一个不错的选择&#xff0c;提供的命令比较丰富&#xff0c;场景比较全面yt-dlp 是一个用 Python 编写的命令行工具&#xff0c;主…

数据结构和算法|递归算法那些事(递归算法的时间复杂度和尾递归优化)

对于文章的第一部分&#xff0c;递归算法的时间复杂度&#xff0c;来自于代码随想录文章:通过一道面试题目&#xff0c;讲一讲递归算法的时间复杂度&#xff01; 对于第二节尾递归优化来自于B站&#xff1a;尾递归优化&#xff1a;你的递归调用是如何被优化的&#xff1f; 关于…

什么是占空比?什么是周期?什么是频率?

一、什么是占空比&#xff1f; 占空比(Duty Cycle)是脉冲信号中高电平持续时间与整个周期时间的比率。它通常用于描述脉冲宽度调制(PWM)信号&#xff0c;其中信号在一定频率下在高电平和低电平之间切换。 图1.1 60%占空比信号 占空比计算公式如下&#xff1a; 脉冲高电平持续时…

护眼首选,一线智联!AOC Q27E12C商用显示器,摆脱束缚高效办公!

摘要&#xff1a;QHD广色域护眼显示器&#xff0c;减负提效商务利器&#xff01; 不管是日常处理报表、制作方案还是进行文字创作、设计剪辑&#xff0c;都离不开商用显示器的身影。相较于传统显示器&#xff0c;商用显示器往往更有助于减负提效&#xff0c;提高企业生产力&am…

开源24Mhz采样逻辑分析仪-信息搜集

引用&#xff1a; 开源4Mhz采样逻辑分析仪-信息搜集&#xff1a; Sigrok逻辑分析仪软件(基于CY7C68013A)-CSDN博客 迷你逻辑分析仪TYPE C接口单片机ARM FPGA调试工具24M采样8通道_逻辑分析仪24m能测stm32吗-CSDN博客 nanoDLA逻辑分析仪上手教程-CSDN博客 基于cy7c68013的逻…

嵌入式初学-C语言-练习四

一维数组练习题 1.键盘录入一组数列&#xff0c;利用冒泡排序将数据由大到小排序 代码&#xff1a; 1 /*2 需求&#xff1a;一维数组案例-冒泡排序3 */4 #include <stdio.h>5 6 int main()7 {8 //创建一个数组&#xff0c;用来存储排序的数列9 int arr[10];1…

Redis远程字典服务器(2) —— 全局命令

一&#xff0c;使用官方文档 学会使用文档&#xff0c;是一个优秀程序员的必备技能。Redis的命令非常多&#xff08;上百个&#xff09;&#xff0c;因为Redis是通过键值对存储数据的&#xff0c;key为string类型&#xff0c;但是value可以是其它的数据类型&#xff08;字符串…

javascript-动态增加和删除表格的行

本地环境&#xff1a;win10 / centos6 &#xff0c; python3 实现效果 点击添加峰图按钮即可增加一行&#xff0c;点击每行右侧的删除按钮即可删除行。 初始状态&#xff1a; 点击后&#xff1a; 实际生成的html内容类似下图&#xff0c;可以看到&#xff0c;只有id这样需要…