0

My app crashed, suspected to be caused by multi-threaded operation of an attribute in a singleton object. So I wrote a small piece of code and successfully reproduced the problem, but I still couldn't understand it. I have defined the property as @property, which is atomic. Why does it still crash when accessed by multiple threads? Below is my code snippet:

Audio.h
@interface Audio : NSObject
@property NSString *audioName;
@property NSString *audioData;
@end


Audio.m
#import "Audio.h"
@implementation Audio
- (instancetype)init{
    self = [super init];
    if (self) {
        _audioData = @"";
        _audioName = nil;
    }
    return self;
}
@end
AudioManager.h
@interface AudioManager : NSObject
+(instancetype)shareInstance;
@property Audio *curAudio;
-(void) play;
-(void) clearCurAudio;
@end


AudioManager.m
#import "AudioManager.h"
@implementation AudioManager
static id sharedInstance = nil;
+(instancetype)shareInstance {
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
-(void) play {
    NSLog(@"Current Audio name : %@",_curAudio.audioName);
    NSLog(@"Current Audio name : %@",_curAudio.audioData);
    NSLog(@"Current Audio name : %@",_curAudio.audioName);//crahed here!
    NSLog(@"Current Audio name : %@",_curAudio.audioData);
}
-(void) clearCurAudio {
    _curAudio = nil;
}
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    dispatch_queue_t thread1 = dispatch_queue_create("queue1", nil);
    dispatch_queue_t thread2 = dispatch_queue_create("queue2", nil);
    dispatch_queue_t thread3 = dispatch_queue_create("queue3", nil);
    
    dispatch_async(thread1, ^{
        for (int i = 0; i < 1000; i++) {
            Audio *newAudio = [[Audio alloc] init];
            newAudio.audioName = @"na";
            [[AudioManager shareInstance] setCurAudio:newAudio];
        }
    });
    
//
    dispatch_async(thread2, ^{
        for (int i = 0; i < 1000; i++) {
            [[AudioManager shareInstance] play];
        }
    });
//
    dispatch_async(thread3, ^{
        for (int i = 0; i < 1000; i++) {
            AudioManager * audioManager = [AudioManager shareInstance];
            [[AudioManager shareInstance] clearCurAudio];
        }

    });
}

Here is the crash EXC_BAD_ACCESS: enter image description here

leowang
  • 23
  • 4
  • It's not so much that it's accessed by multiple threads as it is that one of those threads set it to nil. The crash says you're trying to access 0x52 as an address, which is likely the offset of `audioName` within the object. – Phillip Mills Dec 26 '21 at 16:15
  • See [Is it thread-safe to read an instance variable while calling a setter from another thread?](https://stackoverflow.com/questions/40512366/is-it-thread-safe-to-read-an-instance-variable-while-calling-a-setter-from-anoth) and [Objective-C atomic property thread safe](https://stackoverflow.com/questions/45295354/objective-c-atomic-property-thread-safe) – Willeke Dec 26 '21 at 21:50
  • I delete `_audioName = nil` and `_curAudio = nil`,but still crashed. – leowang Dec 27 '21 at 02:21
  • @Willeke thanks a lot, I use `self.curAudio.audioName` ,and it runs well,crash gone – leowang Dec 27 '21 at 02:26

1 Answers1

0

Thank you guys,problem solved!!, as @Willeke posted. use self.curAudio rather than _curAudio.

leowang
  • 23
  • 4
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 27 '21 at 05:18
  • Yes, you should directly use ivars only in initializers. That's why you can't directly use ivars in Swift – Cy-4AH Jan 02 '22 at 15:01