iOS-GCD相关之多线程同时对一个变量进行写操作:什么情况下会崩溃?又什么情况下不会崩溃?

使用GCD中并发队列下异步执行NSString的set操作,是否会发生崩溃?

        Data *p1 = [[Data alloc]init];
        dispatch_queue_t queue = dispatch_queue_create("temp", DISPATCH_QUEUE_CONCURRENT);
        for(int i = 1;i<10000;i++)
        {
            dispatch_async(queue, ^{
                [p1 setStr:[NSString stringWithFormat:@"dddddd:%d",i]];
            });
        }

其中,str的声明是这样的:

@property (nonatomic,strong) NSString * str;

运行后报错:

 

下面来分析,为什么会报错?

其实原因很简单,str的声明中指定属性nonatomic,表示为非线程安全的,set方法没有上锁。多线程同时进入到set方法进行操作时会引发问题,只要改换为atomic即可避免该问题。

 

那为什么使用nonatomic就不行?多线程同时进入set方法执行时为什么会出错?

原因也很简单,因为在ARC下编译器自动生成的set方法中,需要先对之前持有的对象进行release释放操作,如果此时没有加锁,就有可能发生多个线程执行同一段代码,导致release语句被执行了多次,从而导致对同一块内存多次发送release消息,导致的对已经回收的内存进行重复回收,即会报错。

 

那什么情况下使用nonatomic时也不会报错呢?

做点改动,str的字符串长度控制在8个字节之内:

 

结论:此时就算strnonatomic也不会造成程序崩溃。

原因如下:

苹果当中为了节省内存和提高执行效率,提出的一种Tagged Pointer的概念,在8个字节之内能存放的范围之内,系统就会以Tagged Pointer的方式生成指针,如果 8 字节承载不了就会用普通的方式来生成普通指针。这个TaggedPointer与普通指针不同,普通指针是一个变量,存放在栈上,其内容是一块内存块的基地址,所以说指针指向一个内存块,但TaggedPointer并不是一个真正的对象,实际上他的内容就是真正的值,而不是内存地址,所以他本质上就是一个普通变量,也不持有某个内存块。所以在set方法中,对这样的并没有持有内存块的普通变量进行赋值是不需要提前release的,是完全没有问题的,所以不会报错。

补充:

观察源码中发现,实际上在release或者retain方法中,在方法中会多加一步判断当前对象是否为taggedPointer,如果是,则直接返回,如果不是,则修改其引用计数。

posted @ 2021-08-01 16:16  γGama  阅读(464)  评论(0编辑  收藏  举报