IOS 多线程编程_NSLock,NSCondition,synchronized和生产者消费者模型

1.NSLock 线程锁,
任何两个线程访问同一共享资源(变量,数组)都需要加锁,保证同一时刻只能有一个线程访问共享资源

一个银行账户:有1000块钱,有两个线程同时做一次取钱操作,取钱的金额为800,这是就需要加锁,英文访问的是同一个银行账号
如果不加锁

@implementation FKAccount
- (id)initWithAccountNo:(NSString*)aAccount
                balance:(CGFloat)aBalance
{
    self = [super init];
    if (self) {
        _accountNo = aAccount;
        _balance = aBalance;
    }
    return self;
}
// 提供一个draw方法来完成取钱操作
- (void) draw:(CGFloat)drawAmount
{
    // 账户余额大于取钱数目
    if (self.balance >= drawAmount)
    {
        // 吐出钞票
        NSLog(@"%@取钱成功!吐出钞票:%g", [NSThread currentThread].name
            , drawAmount);
//      [NSThread sleepForTimeInterval:0.001];
        // 修改余额
        _balance = _balance - drawAmount;
        NSLog(@"\t余额为: %g" , self.balance);
    }
    else
    {
        NSLog(@"%@取钱失败!余额不足!", [NSThread currentThread].name);
    }
}
- (NSUInteger) hash
{
    return [self.accountNo hash];
}
- (BOOL)isEqual:(id)anObject
{
    if(self == anObject)
        return YES;
    if (anObject != nil
        && [anObject class] == [FKAccount class])
    {
        FKAccount* target = (FKAccount*)anObject;
        return [target.accountNo isEqualToString:self.accountNo];
    }
    return NO;
}

运行结果
这里写图片描述
这样的结果是不安全的

所以银行账户需要加锁


// 封装账户编号、账户余额两个属性
@implementation FKAccount
NSLock* lock;
- (id)init
{
    self = [super init];
    if (self) {
        lock = [[NSLock alloc] init];
    }
    return self;
}
- (id)initWithAccountNo:(NSString*)aAccount
                balance:(CGFloat)aBalance
{
    self = [super init];
    if (self) {
        lock = [[NSLock alloc] init];
        _accountNo = aAccount;
        _balance = aBalance;
    }
    return self;
}
// 提供一个线程安全的draw方法来完成取钱操作
- (void) draw:(CGFloat)drawAmount
{
    // 显式锁定lock对象
    [lock lock];
    // 账户余额大于取钱数目
    if (self.balance >= drawAmount)
    {
        // 吐出钞票
        NSLog(@"%@取钱成功!吐出钞票:%g", [NSThread currentThread].name
            , drawAmount);
        [NSThread sleepForTimeInterval:0.001];
        // 修改余额
        _balance = _balance - drawAmount;
        NSLog(@"\t余额为: %g" , self.balance);
    }
    else
    {
        NSLog(@"%@取钱失败!余额不足!", [NSThread currentThread].name);
    }
    // 释放lock的锁定
    [lock unlock];
}
- (NSUInteger) hash
{
    return [self.accountNo hash];
}
- (BOOL)isEqual:(id)anObject
{
    if(self == anObject)
        return YES;
    if (anObject != nil
        && [anObject class] == [FKAccount class])
    {
        FKAccount* target = (FKAccount*)anObject;
        return [target.accountNo isEqualToString:self.accountNo];
    }
    return NO;
}
@end

调用

FKAccount* account;
- (void)viewDidLoad
{
    [super viewDidLoad];
    // 创建一个账号
    account = [[FKAccount alloc] initWithAccountNo:@"321231" balance: 1000.0];
}
- (IBAction)draw:(id)sender
{
    // 创建第1个线程对象
    NSThread* thread1 = [[NSThread alloc] initWithTarget:self
        selector:@selector(drawMethod:)
        object:[NSNumber numberWithInt:800]];
    // 创建第2个线程对象
    NSThread* thread2 = [[NSThread alloc] initWithTarget:self
        selector:@selector(drawMethod:)
        object:[NSNumber numberWithInt:800]];
    // 启动2条线程
    [thread1 start];
    [thread2 start];
}
- (void) drawMethod:(NSNumber*) drawAmount
{
    // 直接调用account对象的draw方法来执行取钱
    [account draw:drawAmount.doubleValue];
}

运行结果:
这里写图片描述

2.同步锁synchronized


@implementation FKAccount
- (id)initWithAccountNo:(NSString*)aAccount
                balance:(CGFloat)aBalance
{
    self = [super init];
    if (self) {
        _accountNo = aAccount;
        _balance = aBalance;
    }
    return self;
}
// 提供一个线程安全的draw方法来完成取钱操作
- (void) draw:(CGFloat)drawAmount
{
    // 使用self作为同步监视器,任何线程进入下面同步代码块之前,
    // 必须先获得对self账户的锁定——其他线程无法获得锁,也就无法修改它
    // 这种做法符合:“加锁 → 修改 → 释放锁”的逻辑    
    @synchronized(self)
    {
        // 账户余额大于取钱数目
        if (self.balance >= drawAmount)
        {
            // 吐出钞票
            NSLog(@"%@取钱成功!吐出钞票:%g", [NSThread currentThread].name
                , drawAmount);
            [NSThread sleepForTimeInterval:0.001];
            // 修改余额
            _balance = _balance - drawAmount;
            NSLog(@"\t余额为: %g" , self.balance);
        }
        else
        {
            NSLog(@"%@取钱失败!余额不足!", [NSThread currentThread].name);
        }
    }//同步代码块结束,该线程释放同步锁
}
- (NSUInteger) hash
{
    return [self.accountNo hash];
}
- (BOOL)isEqual:(id)anObject
{
    if(self == anObject)
        return YES;
    if (anObject != nil
        && [anObject class] == [FKAccount class])
    {
        FKAccount* target = (FKAccount*)anObject;
        return [target.accountNo isEqualToString:self.accountNo];
    }
    return NO;
}

执行结果
这里写图片描述

以上使用的主函数调用都一样的,只是FKAccount的实现方式不一样


3.NSCondition

使用NSCondition,实现多线程的同步,即,可实现生产者消费者问题。

基本思路是,首先要创建公用的NSCondition实例。然后:

  • 消费者取得锁,取产品,如果没有,则wait,这时会释放锁,直到有线程唤醒它去消费产品;
  • 生产者制造产品,首先也是要取得锁,然后生产,再发signal,这样可唤醒wait的消费者。

以银行账号为例子
有存钱,有取钱操作,这个时候需要NSCondition


@implementation FKAccount
NSCondition* cond;
BOOL flag;
- (id)init
{
    self = [super init];
    if (self) {
        cond = [[NSCondition alloc] init];
    }
    return self;
}
- (id)initWithAccountNo:(NSString*)aAccount
                balance:(CGFloat)aBalance
{
    self = [super init];
    if (self) {
        cond = [[NSCondition alloc] init];
        _accountNo = aAccount;
        _balance = aBalance;
    }
    return self;
}
// 提供一个线程安全的draw方法来完成取钱操作
- (void) draw:(CGFloat)drawAmount
{
    // 加锁
    [cond lock];
    // 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
    if (!flag)
    {
        [cond wait];
    }
    else
    {
        // 执行取钱
        NSLog(@"%@ 取钱:%g" , [NSThread currentThread].name
            ,drawAmount);
        _balance -= drawAmount;
        NSLog(@"账户余额为:%g" , self.balance);
        // 将标识账户是否已有存款的旗标设为NO。
        flag = NO;
        // 唤醒其他线程
        [cond broadcast];
    }
    [cond unlock];
}
- (void) deposit:(CGFloat) depositAmount
{
    [cond lock];
    // 如果flag为YES,表明账户中已有人存钱进去,则存钱方法阻塞
    if(flag) // ①
    {
        [cond wait];
    }
    else
    {
        // 执行存款
        NSLog(@"%@ 存款:%g" , [NSThread currentThread].name
            , depositAmount);
        _balance += depositAmount;
        NSLog(@"账户余额为:%g" , self.balance);
        // 将表示账户是否已有存款的旗标设为YES
        flag = YES;
        // 唤醒其他线程
        [cond broadcast];
    }
    [cond unlock];
}
- (NSUInteger) hash
{
    return [self.accountNo hash];
}
- (BOOL)isEqual:(id)anObject
{
    if(self == anObject)
        return YES;
    if (anObject != nil
        && [anObject class] == [FKAccount class])
    {
        FKAccount* target = (FKAccount*)anObject;
        return [target.accountNo isEqualToString:self.accountNo];
    }
    return NO;
}
@end

存取钱操作

@implementation FKViewController
FKAccount* account;
- (void)viewDidLoad
{
    [super viewDidLoad];
    // 创建一个账号
    account = [[FKAccount alloc] initWithAccountNo:@"321231" balance: 1000.0];
}

- (IBAction)depositDraw:(id)sender
{
    // 创建、启动3个存钱的线程
    [NSThread detachNewThreadSelector:@selector(drawMethod:) toTarget:self
        withObject:[NSNumber numberWithDouble:800.0]];
    [NSThread detachNewThreadSelector:@selector(drawMethod:) toTarget:self
        withObject:[NSNumber numberWithDouble:800.0]];
    // 创建、启动存钱的线程
    [NSThread detachNewThreadSelector:@selector(drawMethod:) toTarget:self
        withObject:[NSNumber numberWithDouble:800.0]];
    // 创建、启动取钱的线程
    [NSThread detachNewThreadSelector:@selector(depositMethod:) toTarget:self
        withObject:[NSNumber numberWithDouble:800.0]];
}
- (void) drawMethod:(NSNumber*) drawAmount
{
    [NSThread currentThread].name = @"甲";
    // 重复100次执行取钱操作
    for (int i = 0 ; i < 100 ; i++ )
    {
        [account draw:drawAmount.doubleValue];
    }
}
- (void) depositMethod:(NSNumber*) depositAmount
{
    [NSThread currentThread].name = @"乙";
    // 重复100次执行存款操作
    for (int i = 0 ; i < 100 ; i++ )
    {
        [account deposit:depositAmount.doubleValue];
    }
}
@end

执行的结果为
这里写图片描述
无论线程执行多少次存取,结果总是对的

<script type="text/javascript"> $(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('<ul/>').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('<li/>').text(i)); }; $numbering.fadeIn(1700); }); }); </script>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值