首页 关于 微信公众号
欢迎关注我的微信公众号

多线程中的资源同步

关于资源同步

所谓的资源同步就是在多个线程中,共享同一资源,由于每个线程都对这一公共的资源都有操作,所以有可能回发生:A线程正在利用资源r做操作时,而资源r却在此时被线程B所修改了。这就有可能会引起一系列的问题。所以资源同步就是指:当线程A利用资源r的时候,不允许其它线程访问资源r,这就是多线程中的资源同步。

资源同步发生的场景

在平常的编码中,当我们声明某一个变量时,我们首先要想到这个变量的使用场景:即是不是在多线程环境下,该变量(资源)是不是在不同的线程中都被操作(包括读取和修改),如果存在,那么就需要对这个资源做线程同步。

OC中的资源同步

OC中的资源同步有多种方式,下面我具体罗列出来:

资源同步问题场景

用多线程来模拟一个买票的程序,如果不加资源同步的话,买票的结果就是乱七八糟的,100张票有可能被卖了150次。在这里:

实现以上场景:

@interface ViewController ()
@property (nonatomic,assign) int ticketsCounts;  // 票总张数
@property (nonatomic,strong) NSThread *sellThread1;  // 卖票线程1
@property (nonatomic,strong) NSThread *sellThread2; // 卖票线程2

@property (nonatomic,strong) NSLock *ticketsLock;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.ticketsCounts = 100;
    
    self.sellThread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellWindow01_NotThreadSelf) object:nil];
    self.sellThread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellWindow02_NotThreadSelf) object:nil];
    NSLog(@"开始卖票..");
    [self.sellThread1 start];
    [self.sellThread2 start];
    
    self.ticketsLock = [[NSLock alloc] init];
}

- (void)sellWindow01_NotThreadSelf
{
    while (self.ticketsCounts) {

        if (self.ticketsCounts == 0) {
            [self.sellThread1 cancel];
            NSLog(@"window01: 票卖完了... ");
            return;
        }
        self.ticketsCounts -= 1;
        NSLog(@"window01 卖了一张票,剩余票数:%d ",self.ticketsCounts);
        usleep(1000*1000); // 休眠1s

    }

}

- (void)sellWindow02_NotThreadSelf
{
    while (self.ticketsCounts) {
        
        if (self.ticketsCounts == 0) {
            [self.sellThread1 cancel];
            NSLog(@"window02: 票卖完了... ");
            return;
        }
        self.ticketsCounts -= 1;
        NSLog(@"window02 卖了一张票,剩余票数:%d ",self.ticketsCounts);
        usleep(1000*1000); // 休眠1s
        
    }
    
}


/*
  触摸屏幕停止卖票
 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.sellThread1 cancel];
    [self.sellThread2 cancel];
    NSLog(@"停止买票,现在剩余票数=%d",self.ticketsCounts);
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


我们看一下运行结果,很显然-剩余票数是混乱的。

解决资源不同步问题

我们这里只介绍在OC语言中资源同步的方法。

@synchronized

直接在卖票操作时,使用@synchronized关键字同步资源。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.ticketsCounts = 100;
        
    self.sellThread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellWindow01) object:nil];
    self.sellThread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellWindow02) object:nil];
    NSLog(@"开始卖票..");
    [self.sellThread1 start];
    [self.sellThread2 start];
    
    
    self.ticketsLock = [[NSLock alloc] init];
}

- (void)sellWindow01
{

/* 同步代码块  */
    @synchronized (self) {
        
        while (self.ticketsCounts) {
            
            if (self.ticketsCounts == 0) {
                [self.sellThread1 cancel];
                NSLog(@"window01: 票卖完了... ");
                return;
            }
            self.ticketsCounts -= 1;
            NSLog(@"window01 卖了一张票,剩余票数:%d ",self.ticketsCounts);
            usleep(1000*1000); // 休眠1s
            
        }
        
    }
}

- (void)sellWindow02
{
/** 同步代码块  **/
    @synchronized (self) {
        
        while (self.ticketsCounts) {
            
            if (self.ticketsCounts == 0) {
                [self.sellThread2 cancel];
                NSLog(@"window02: 票卖完了... ");
                return;
            }
            self.ticketsCounts -= 1;
            NSLog(@"window02 卖了一张票,剩余票数:%d ",self.ticketsCounts);
            usleep(1000*1000); // 休眠1s
            
        }

        
    }
}

我们可以看到,此时资源就同步了。

NSLock

OC为我们提供了NSLoc,我们可以在对引发资源不同步的操作加上锁,这样也可以保证资源同步。

- (void)sellWindow01
{    
/** 加锁 **/
    [self.ticketsLock lock];
        while (self.ticketsCounts) {

            if (self.ticketsCounts == 0) {
                [self.sellThread1 cancel];
                NSLog(@"window01: 票卖完了... ");
                return;
            }
            self.ticketsCounts -= 1;
            NSLog(@"window01 卖了一张票,剩余票数:%d ",self.ticketsCounts);
            usleep(1000*1000); // 休眠1s

        }
    [self.ticketsLock unlock];
    

}

- (void)sellWindow02
{    
 /** 加锁 **/
        [self.ticketsLock lock];
        while (self.ticketsCounts) {

            if (self.ticketsCounts == 0) {
                [self.sellThread2 cancel];
                NSLog(@"window02: 票卖完了... ");
                return;
            }
            self.ticketsCounts -= 1;
            NSLog(@"window02 卖了一张票,剩余票数:%d ",self.ticketsCounts);
            usleep(1000*1000); // 休眠1s

        }
        [self.ticketsLock unlock];
    

}

利用宏定义,来简化加锁操作并带有日志:

 
#define TVULOCK(weblock)\
do{\
[weblock lock];\
NSLog(@"%s----lock-----%d",__func__,__LINE__);\
}while(0)
	
#define TVUUNLOCK(weblock)\
do{\
[weblock unlock];\
NSLog(@"%s----unlock-----%d",__func__,__LINE__);\
}while(0)
	

运行结果如图所示:

利用GCD串行队列

使用GCD串行队列也可以做资源同步。具体做法是:

@property (nonatomic,strong) dispatch_queue_t phoneQueue;


- (dispatch_queue_t)phoneQueue
{
    if (!_phoneQueue) {
        _phoneQueue = dispatch_queue_create("phone_queue", DISPATCH_QUEUE_SERIAL);
    }
    return _phoneQueue;
}



- (void)sellWindow01
{      
    /** 放入串行队列中 **/
    dispatch_async(self.phoneQueue, ^{
        while (self.ticketsCounts) {
            
            if (self.ticketsCounts == 0) {
                [self.sellThread1 cancel];
                NSLog(@"window01: 票卖完了... ");
                return;
            }
            self.ticketsCounts -= 1;
            NSLog(@"window01 卖了一张票,剩余票数:%d ",self.ticketsCounts);
            usleep(1000*1000); // 休眠1s
            
        }
    });
    

}

- (void)sellWindow02
{
    /** 放入串行队列中 **/
    
    dispatch_async(self.phoneQueue, ^{
        while (self.ticketsCounts) {

            if (self.ticketsCounts == 0) {
                [self.sellThread2 cancel];
                NSLog(@"window02: 票卖完了... ");
                return;
            }
            self.ticketsCounts -= 1;
            NSLog(@"window02 卖了一张票,剩余票数:%d ",self.ticketsCounts);
            usleep(1000*1000); // 休眠1s
        }
        
    });
    

}

运行结果如图所示:

Blog

Opinion

Project