iOS实现读写锁

3,049 阅读2分钟

1. 定义

读写锁实际是一种特殊的自旋锁,一个 读写锁 同时只能有一个写者或者多个读者,但不能既有读者又有写者,如果 读写锁 当前没有读者,也没有写者,那么写者可以立刻获得 读写锁,否则它必须自旋在那里, 直到没有任何写者或读者。如果 读写锁 没有写者,那么读者可以立刻获得读写锁pthread_rwlock)。知道了 读写锁 的工作原理后,我们可以发现,读写锁 在读多写少的场景,能发挥出优势。

  • 同一时间,只能有1个线程进行写的操作

  • 同一时间,允许有多个线程进行读的操作

  • 同一时间,不允许既有写的操作,又有读的操作

2. 实现方案

读写锁在 iOS 中的实现方案大致有两种:

2.1 读写锁:pthread_rwlock

//初始化锁
pthread_ rwlock_ t lock;
pthread_ rwlock_ init(&lock, NULL);
//读一加锁
pthread_ rwlock_ rdlock(&lock);
//读一尝试加锁
pthread_ rwlock_ tryrdlock(&lock); 
//写一加锁
pthread_ rwlock_ wrlock(&lock);
//写一尝试加锁
pthread_ rwlock_ trywrlock(&lock);
//解锁
pthread_ rwlock_ _unlock(&lock);
//销毁
pthread_ rwlock_ destroy(&lock)

2.2 dispatch_barrier_async

2.2.1 利用 dispatch_barrier_async 实现读写锁

这个函数传入的并发队列必须是自己通过 dispatch_queue_cretate 创建的,全局队列不可以,栅栏函数不能在全局并发队列中使用。

读写锁.png

 
#import "ViewController.h"
 
@interface ViewController ()
 
@property (nonatomic, copy) NSString *text;
@property (nonatomic, strong) dispatch_queue_t concurrentQueue;
 
@end
 
 
@implementation ViewController
 
@synthesize text = _text;
 
- (void)viewDidLoad {
    [super viewDidLoad];
 
 
    [self readWriteLock];    
 
}
 
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self readWriteLock];
 
}
 
- (void)readWriteLock {
    
    self.concurrentQueue = dispatch_queue_create("aaa", DISPATCH_QUEUE_CONCURRENT);
    // 测试代码,模拟多线程情况下的读写
    for (int i = 0; i<10; i++) {
        // 将 dispatch_async(或 dispatch_sync)用于不修改状态的操作。
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.text = [NSString stringWithFormat:@"噼里啪啦--%d",i];
        });
        
    }
    
    for (int i = 0; i<50; i++) {
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"读 %@ %@",[self text],[NSThread currentThread]);
        });
        
    }
    
    
    for (int i = 10; i<20; i++) {
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.text = [NSString stringWithFormat:@"噼里啪啦--%d",i];
        });
        
    }
}
 
// 写操作,栅栏函数是不允许并发的,所以"写操作"是单线程进入的,根据log可以看出来
- (void)setText:(NSString *)text {
    
    __weak typeof(self) weakSelf = self;
    // 将 dispatch_barrier_sync(或  dispatch_barrier_async)用于可能修改状态的操作。
    dispatch_barrier_sync(self.concurrentQueue, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        strongSelf->_text = text;
        NSLog(@"写操作 %@ %@",text,[NSThread currentThread]);
        // 模拟耗时操作,1个1个执行,没有并发
        sleep(1);
    });
}
// 读操作,这个是可以并发的,log在很快时间打印完
- (NSString *)text {
 
    __block NSString * t = nil ;
    __weak typeof(self) weakSelf = self;
    dispatch_sync(self.concurrentQueue, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        t = strongSelf->_text;
        // 模拟耗时操作,瞬间执行完,说明是多个线程并发的进入的
        sleep(1);
 
    });
    return t;
 
}
 
@end
2.2.2 AFN中读写锁的实现

AFN 中,requestHeaderModificationQueue 是一个由 AFHTTPRequestSerializer 创建的并发队列, 使用这个并发队列对一个 NSMutableDictionary 实现了多读单写的锁。

// 并发队列
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);

// 同步方法,可以并发获取字典
- (NSDictionary *)HTTPRequestHeaders {
    NSDictionary__ block *value;
    dispatch_sync(self.requestHeaderModificationQueue, ^{
    value = 【NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
    });
    return value;
}
// 异步方法,使用dispatch_barrier_async/dispatch_barrier_sync可以保证一次只有一个线程
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field
    dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
    [self . mutableHTTPRequestHeaders setValue:value forKey:field];
    });
}
// 同步方法,并发获取字典里的值
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    NSString __block *value;
    dispatch_sync(self.requestHeaderModificationQueue,^{
    value = [self.mutableHTTPRequestHeaders valueForKey:field];
    });
    return value;
}