IOS中使用GCD与信号量机制实现读者写者(读者优先)
一、读者写者问题是操作系统中非常经典的线程同步问题,像jdk中有读写锁用来处理这类问题。在读者写者模式中又有几种不同的同步模式,如:
- 读者优先 式的读者写者类型
- 写者优先 式的读者写者类型
- 公平竞争 式的读者写者类型
但不管是哪种类型,在处理 这个问题时都遵循下列几个互斥条件,否则会出现“假死”即都处于等待,或出现脏数据问题。
- 允许读者–读者之间同时执行读操作临界资源
- 不允许写者–写者之间同时操作操作临界资源
- 不允许读者–写者同时操作操作临界资源
读者优先解决方法(其他类型看官们自己类推):
读者访问临界资源的逻辑:
- 无其他读者、写者,该读者可以读
- 若已有写者等待,但有其他读者正在读,则该读者也可以读
- 若有写者正在写,则该读者必须等如果写者执行
- 使用一个计数器记录当前读者访问临界资源的数量,在访问完临界资源的时候,对这个计数器进行减一操作,当计数器值为0时,释放读者写者之间的锁。
写者访问临界资源的逻辑:
- 无其他读者、写者,该写者可以写
- 若有读者正在读,该写者等待
- 若有其他读者正在写,该写者等待
测试页面的.h文件
//
// ReadWriteVC.h
// MultiThread
//
// Created by liuxiaobing on 2018/11/1.
// Copyright © 2018 liuxiaobing. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "ReaderWrite.h"
NS_ASSUME_NONNULL_BEGIN
/**
6、读者写者问题:
(1)满足条件:①写写互斥;②读写互斥;③读读同时
(2)单纯使用信号量不能解决读者与写者问题,必须引入计数器readcount对读进程计数;
rmutex是用于对计数器readcount操作的互斥信号量;wmutex表示是否允许写的信号量;
*/
@interface ReadWriteVC : UIViewController
//写者线程跑的队列,这个队列可以控制读者的执行是并行还是串行
@property(strong,nonatomic) dispatch_queue_t readQueue;
//读者线程跑的队列,这个队列可以控制写者的执行是并行还是串行
@property(strong,nonatomic) dispatch_queue_t writeQueue;
@property(strong,nonatomic) ReaderWrite* readWriteMgr;
@end
NS_ASSUME_NONNULL_END
.m
//
// ReadWriteVC.m
// MultiThread
//
// Created by liuxiaobing on 2018/11/1.
// Copyright © 2018 liuxiaobing. All rights reserved.
//
#import "ReadWriteVC.h"
@interface ReadWriteVC ()
@end
@implementation ReadWriteVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.readWriteMgr = [[ReaderWrite alloc] init];
[self initView];
}
-(void) initView{
UIButton* button2 = [UIButton buttonWithType:UIButtonTypeCustom];
[button2 setTitle:@"读数据" forState:UIControlStateNormal];
button2.frame = CGRectMake(20, 80, 200, 40);
button2.backgroundColor = [UIColor brownColor];
[button2 addTarget:self action:@selector(read:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button2];
UIButton* button3 = [UIButton buttonWithType:UIButtonTypeCustom];
[button3 setTitle:@"写数据" forState:UIControlStateNormal];
button3.frame = CGRectMake(20, 180, 200, 40);
button3.backgroundColor = [UIColor brownColor];
[button3 addTarget:self action:@selector(write:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button3];
self.readQueue = dispatch_queue_create("producer", DISPATCH_QUEUE_CONCURRENT); //读者可以使用 并发队列,不限定读者的顺序
self.writeQueue = dispatch_queue_create("consumer", DISPATCH_QUEUE_SERIAL);
}
-(void) read:(UIButton*) button{
for(int i = 0; i < 5; i ++){
dispatch_async(self.readQueue, ^{
while(1){
[self.readWriteMgr readData];
sleep(1);
}
});
}
}
-(void) write:(UIButton*) button{
for(int i = 0; i < 5; i ++){
dispatch_async(self.writeQueue, ^{
while(1){
[self.readWriteMgr writeData];
sleep(1);
}
});
}
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
读者写者核心类.h文件:
//
// ReaderWrite.h
// MultiThread
//
// Created by liuxiaobing on 2018/11/1.
// Copyright © 2018 liuxiaobing. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
(1)满足条件:①写写互斥;②读写互斥;③读读同时
(2)单纯使用信号量不能解决读者与写者问题,必须引入计数器readcount对读进程计数;
rmutex是用于对计数器readcount操作的互斥信号量;
wmutex表示是否允许写的信号量;
*/
@interface ReaderWrite : NSObject
@property(assign,nonatomic) int board; //临界资源
@property(strong,nonatomic) dispatch_semaphore_t rmutex; //读者计数器 互斥信号量
@property(strong,nonatomic) dispatch_semaphore_t wmutex; //写者-读者, 写者-写者
@property(nonatomic,assign) int readcount;
-(void) readData;
-(void) writeData;
@end
NS_ASSUME_NONNULL_END
.m文件
//
// ReaderWrite.m
// MultiThread
//
// Created by liuxiaobing on 2018/11/1.
// Copyright © 2018 liuxiaobing. All rights reserved.
//
#import "ReaderWrite.h"
@implementation ReaderWrite
- (instancetype)init
{
self = [super init];
if (self) {
//初始化生产对象--消费者标记,初始为0表示什么都没有
self.rmutex = dispatch_semaphore_create(1);
//初始化临界区互斥访问信号量,用信号量实现互斥,特殊初始值为1.
//控制同一时刻只有一个线程对象在访问临界资源
self.wmutex = dispatch_semaphore_create(1);
self.readcount = 0; //当前正在读数据的读者数量
}
return self;
}
-(void) readData{
long baseCount = dispatch_semaphore_wait(self.rmutex, 5 * NSEC_PER_SEC);
if(baseCount != 0){
NSLog(@"36----------临界区正在被写者使用,读者处于等待");
}else{
if(self.readcount == 0){
long tmp = dispatch_semaphore_wait(self.wmutex, 5 * NSEC_PER_SEC);
if(tmp != 0){
NSLog(@"36----------我是第一个读者,我想在临界区读取数据,但是当前有写者,读者处于等待");
}else{
}
}
self.readcount ++;
dispatch_semaphore_signal(self.rmutex); //释放读者计数器的锁
NSLog(@"50----------读者开始读数据:%d",self.board);
dispatch_semaphore_wait(self.rmutex, 5 * NSEC_PER_SEC); //读完数据之后还需要再申请使用一次读者之间的x互斥锁,
self.readcount --; //将读者计数器 减一操作
if(self.readcount == 0){ //如果是最后一个t读者操作,需要通知 写者可以 进行写操作了
dispatch_semaphore_signal(self.wmutex);
}
dispatch_semaphore_signal(self.rmutex); //最后再释放 读者之间的锁
}
}
-(void) writeData{
long count = dispatch_semaphore_wait(self.wmutex, 5 * NSEC_PER_SEC);
if(count != 0){
NSLog(@"50----------当前有人在使用临界区在读数据,写者等待....");
}else{
// self.board = (arc4random() % 100) + 1;
// NSLog(@"71----------写者写数据:%d",self.board);
}
self.board = (arc4random() % 100) + 1;
NSLog(@"71----------写者写数据:%d",self.board);
dispatch_semaphore_signal(self.wmutex);
}
@end
测试结果 :