线程安全之加个锁

之前一直看到别人写的线程安全的文章, 却没有去实践, 反正那时候看着别人说的很有道理的样子, 其实你自己实践之后并没有觉得别人说的很对, 下面说的错误的地方欢迎指正,谢谢
先来看看属性系统申明本身的线程安全, 线程安全的官方解释: 意料之外的结果

@property (atomic, copy) NSString *string;// 原子性 , 加锁了, 线程安全
- (void)Thread_UnSafe {
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t gloabelQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //Thread A
    dispatch_group_async(group, gloabelQueue, ^{
        for (int i = 0; i < 100000; i ++) {
            if (i % 2 == 0) {
                self.string = @"a very long string";
            }
            else {
                self.string = @"string";
            }
            NSLog(@"Thread A: %@\n", self.string);
        }
    });
    
    //Thread B
    dispatch_group_async(group, gloabelQueue, ^{
        for (int i = 0; i < 100000; i ++) {
            if (self.string.length >= 10) {
                NSString *subStr = [self.string substringWithRange:NSMakeRange(0, 10)];
                NSLog(@"substring %@", subStr);
            }
            NSLog(@"Thread B: %@\n", self.string);
        }
    });
    
    dispatch_group_notify(group, gloabelQueue, ^{
        dispatch_sync(dispatch_get_main_queue(), ^{
            
        });
    });
    
}
崩溃啦.png
2019-05-15 11:55:55.246153+0800 ThreadLockDemo[4443:98414] *** Terminating app due to uncaught exception 'NSRangeException', reason: '-[__NSCFConstantString substringWithRange:]: Range {0, 10} out of bounds; string length 6'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010fa966fb __exceptionPreprocess + 331
    1   libobjc.A.dylib                     0x000000010f03aac5 objc_exception_throw + 48
    2   CoreFoundation                      0x000000010fa96555 +[NSException raise:format:] + 197
    3   CoreFoundation                      0x000000010f9e0bd3 -[__NSCFString substringWithRange:] + 163
    4   ThreadLockDemo                      0x000000010e75f638 __31-[ViewController Thread_UnSafe]_block_invoke.13 + 248
    5   libdispatch.dylib                   0x00000001112ffd7f _dispatch_call_block_and_release + 12
    6   libdispatch.dylib                   0x0000000111300db5 _dispatch_client_callout + 8
    7   libdispatch.dylib                   0x0000000111303954 _dispatch_queue_override_invoke + 1433
    8   libdispatch.dylib                   0x0000000111311632 _dispatch_root_queue_drain + 351
    9   libdispatch.dylib                   0x0000000111311fca _dispatch_worker_thread2 + 130
    10  libsystem_pthread.dylib             0x00000001116e96b3 _pthread_wqthread + 583
    11  libsystem_pthread.dylib             0x00000001116e93fd start_wqthread + 13
)
2019-05-15 11:55:55.307523+0800 ThreadLockDemo[4443:98398] Thread A: a very long string
2019-05-15 11:55:55.308603+0800 ThreadLockDemo[4443:98398] Thread A: string
2019-05-15 11:55:55.308720+0800 ThreadLockDemo[4443:98398] Thread A: a very long string
2019-05-15 11:55:55.308835+0800 ThreadLockDemo[4443:98398] Thread A: string
2019-05-15 11:55:55.308941+0800 ThreadLockDemo[4443:98398] Thread A: a very long string
libc++abi.dylib: terminating with uncaught exception of type NSException
2019-05-15 11:55:55.309038+0800 ThreadLockDemo[4443:98398] Thread A: string
(lldb) 

结果分析: 数组越界造成的崩溃
结果崩溃了, 然后就有人说了系统的原子性并不是线程安全的, 然后给出了如下的解决方案

@property (nonatomic, copy) NSString *string;// 非原子性 , 没有加锁了, 线程不安全
@property (nonatomic, strong) NSLock *lock;
self.lock = [[NSLock alloc] init];
[self Thread_Lock_Safe];
- (void)Thread_Lock_Safe {
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t gloabelQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
    dispatch_group_async(group, gloabelQueue, ^{
        [self->_lock lock];
        for (int i = 0; i < 100000; i ++) {
            if (i % 2 == 0) {
                self.string = @"a very long string";
            }
            else {
                self.string = @"string";
            }
            NSLog(@"Thread A: %@\n", self.string);
        }
        [self->_lock unlock];
    });
    
    dispatch_group_async(group, gloabelQueue, ^{
        [self->_lock lock];
        for (int i = 0; i < 100000; i ++) {
            if (self.string.length >= 10) {
                NSString *subStr = [self.string substringWithRange:NSMakeRange(0, 10)];
                NSLog(@"substring %@", subStr);
            }
            NSLog(@"Thread B: %@\n", self.string);
        }
        [self->_lock unlock];
    });
    
    dispatch_group_notify(group, gloabelQueue, ^{
        dispatch_sync(dispatch_get_main_queue(), ^{
            
        });
    });
}

这里我只展示了20次循环的结果, 100000次篇幅过长, 你可以自己试试

2019-05-15 13:51:54.533463+0800 ThreadLockDemo[5193:126549] Thread A: a very long string
2019-05-15 13:51:54.533648+0800 ThreadLockDemo[5193:126549] Thread A: string
2019-05-15 13:51:54.533743+0800 ThreadLockDemo[5193:126549] Thread A: a very long string
2019-05-15 13:51:54.533834+0800 ThreadLockDemo[5193:126549] Thread A: string
2019-05-15 13:51:54.533931+0800 ThreadLockDemo[5193:126549] Thread A: a very long string
2019-05-15 13:51:54.534014+0800 ThreadLockDemo[5193:126549] Thread A: string
2019-05-15 13:51:54.534099+0800 ThreadLockDemo[5193:126549] Thread A: a very long string
2019-05-15 13:51:54.534207+0800 ThreadLockDemo[5193:126549] Thread A: string
2019-05-15 13:51:54.534324+0800 ThreadLockDemo[5193:126549] Thread A: a very long string
2019-05-15 13:51:54.534481+0800 ThreadLockDemo[5193:126549] Thread A: string
2019-05-15 13:51:54.534636+0800 ThreadLockDemo[5193:126549] Thread A: a very long string
2019-05-15 13:51:54.534789+0800 ThreadLockDemo[5193:126549] Thread A: string
2019-05-15 13:51:54.535001+0800 ThreadLockDemo[5193:126549] Thread A: a very long string
2019-05-15 13:51:54.535363+0800 ThreadLockDemo[5193:126549] Thread A: string
2019-05-15 13:51:54.605683+0800 ThreadLockDemo[5193:126549] Thread A: a very long string
2019-05-15 13:51:54.605807+0800 ThreadLockDemo[5193:126549] Thread A: string
2019-05-15 13:51:54.605903+0800 ThreadLockDemo[5193:126549] Thread A: a very long string
2019-05-15 13:51:54.606000+0800 ThreadLockDemo[5193:126549] Thread A: string
2019-05-15 13:51:54.606105+0800 ThreadLockDemo[5193:126549] Thread A: a very long string
2019-05-15 13:51:54.606227+0800 ThreadLockDemo[5193:126549] Thread A: string
2019-05-15 13:51:54.606337+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.606424+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.606548+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.606655+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.606742+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.606838+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.606929+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.607031+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.607137+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.607219+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.607299+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.607398+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.607482+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.607586+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.607727+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.607875+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.608018+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.608171+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.608347+0800 ThreadLockDemo[5193:126550] Thread B: string
2019-05-15 13:51:54.608585+0800 ThreadLockDemo[5193:126550] Thread B: string

结果分析: 然后别人就说这样就安全了
参考文献: http://mrpeak.cn/blog/ios-thread-safety/

1、这样的结果我个人不太赞同, 不知道大家有没有发现, 他的这种加锁方式, 直接把并行执行变成了串行执行, 你干脆把异步执行并行队列去掉就好了, 在主线程里面代码也是依次执行的, 这么做没有多大意义了, 也许有人说有意义, 我开辟了新的线程处理耗时的操作, 这个我赞同, 那你干嘛非要开辟两条新线程呢, 你直接在线程A后面接着写就是咯, 开辟线程就不浪费资源了吗?
2、还有就是说系统的原子性并不是绝对的线程安全, 而且特别耗性能, 这个我也赞同, 我也赞同他说原子性的属性申明只是系统对属性的setter 和 getter加了锁, 但是上面的代码并不能说明,上面的代码是对一个属性的读和写的操作,即使读和写都分别加了锁,按照上面的做法也不是线程安全的,因为你的读和写的操作之间并没有关联,就是说你在读上一次写入的数据的时候并没有阻止这一次数据的写入, 读和写没有互斥,于是我就自己模仿者给属性的setter 、getter 方法加锁来验证.

方式1

@property (nonatomic, copy) NSString *string;
- (void)Thread_UnSafe {
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t gloabelQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //Thread A
    dispatch_group_async(group, gloabelQueue, ^{
        for (int i = 0; i < 100000; i ++) {
            if (i % 2 == 0) {
                self.string = @"a very long string";
            }
            else {
                self.string = @"string";
            }
            NSLog(@"Thread A: %@\n", self.string);
        }
    });
    
    //Thread B
    dispatch_group_async(group, gloabelQueue, ^{
        for (int i = 0; i < 100000; i ++) {
            if (self.string.length >= 10) {
                NSString *subStr = [self.string substringWithRange:NSMakeRange(0, 10)];
                NSLog(@"substring %@", subStr);
            }
            NSLog(@"Thread B: %@\n", self.string);
        }
    });
    
    dispatch_group_notify(group, gloabelQueue, ^{
        dispatch_sync(dispatch_get_main_queue(), ^{
            
        });
    });    
}

- (void)setString:(NSString *)string {
    if (string) {
        [self.lock lock];
        _string = string;
        [self.lock unlock];
    }
}
崩溃啦.png

结果分析: 不安全

方式2

- (void)setString:(NSString *)string {
    if (string) {
        dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
        _string = string;
        dispatch_semaphore_signal(_semaphore);
    }
}
崩溃啦.png

结果分析:不安全

方式3

- (void)setString:(NSString *)string {
    if (string) {
        //异步栏栅 并行队列 注意上面用的全局队列
        dispatch_barrier_async(_concurrentQueue, ^{
            self->_string = string;
        });
    }
}

结果不出意料又崩溃了

方式4

- (void)setString:(NSString *)string {
    if (string) {
        //异步栏栅 全局队列
        dispatch_barrier_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            self->_string = string;
        });
    }
}

结果显示原因同上

2019-05-15 14:23:45.463839+0800 ThreadLockDemo[5513:141514] Thread B: (null)
2019-05-15 14:23:45.463846+0800 ThreadLockDemo[5513:141513] Thread A: (null)
2019-05-15 14:23:45.464034+0800 ThreadLockDemo[5513:141514] substring a very lon
2019-05-15 14:23:45.464061+0800 ThreadLockDemo[5513:141513] Thread A: a very long string
2019-05-15 14:23:45.464123+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.464176+0800 ThreadLockDemo[5513:141513] Thread A: string
2019-05-15 14:23:45.464199+0800 ThreadLockDemo[5513:141514] substring a very lon
2019-05-15 14:23:45.464270+0800 ThreadLockDemo[5513:141513] Thread A: a very long string
2019-05-15 14:23:45.464291+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.464371+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.464436+0800 ThreadLockDemo[5513:141513] Thread A: string
2019-05-15 14:23:45.464634+0800 ThreadLockDemo[5513:141514] substring a very lon
2019-05-15 14:23:45.464816+0800 ThreadLockDemo[5513:141513] Thread A: a very long string
2019-05-15 14:23:45.464982+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.465286+0800 ThreadLockDemo[5513:141513] Thread A: string
2019-05-15 14:23:45.465598+0800 ThreadLockDemo[5513:141514] substring a very lon
2019-05-15 14:23:45.467228+0800 ThreadLockDemo[5513:141514] Thread B: a very long string
2019-05-15 14:23:45.467238+0800 ThreadLockDemo[5513:141513] Thread A: a very long string
2019-05-15 14:23:45.467364+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.467371+0800 ThreadLockDemo[5513:141513] Thread A: string
2019-05-15 14:23:45.467447+0800 ThreadLockDemo[5513:141514] substring a very lon
2019-05-15 14:23:45.467495+0800 ThreadLockDemo[5513:141513] Thread A: a very long string
2019-05-15 14:23:45.467532+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.467590+0800 ThreadLockDemo[5513:141513] Thread A: string
2019-05-15 14:23:45.467673+0800 ThreadLockDemo[5513:141514] substring a very lon
2019-05-15 14:23:45.467831+0800 ThreadLockDemo[5513:141513] Thread A: a very long string
2019-05-15 14:23:45.467977+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.468145+0800 ThreadLockDemo[5513:141513] Thread A: string
2019-05-15 14:23:45.468283+0800 ThreadLockDemo[5513:141514] substring a very lon
2019-05-15 14:23:45.471436+0800 ThreadLockDemo[5513:141514] Thread B: a very long string
2019-05-15 14:23:45.471445+0800 ThreadLockDemo[5513:141513] Thread A: a very long string
2019-05-15 14:23:45.471549+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.471563+0800 ThreadLockDemo[5513:141513] Thread A: string
2019-05-15 14:23:45.471654+0800 ThreadLockDemo[5513:141514] substring a very lon
2019-05-15 14:23:45.471682+0800 ThreadLockDemo[5513:141513] Thread A: string
2019-05-15 14:23:45.471758+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.471837+0800 ThreadLockDemo[5513:141513] Thread A: string
2019-05-15 14:23:45.472000+0800 ThreadLockDemo[5513:141514] substring a very lon
2019-05-15 14:23:45.472150+0800 ThreadLockDemo[5513:141513] Thread A: a very long string
2019-05-15 14:23:45.472299+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.472434+0800 ThreadLockDemo[5513:141513] Thread A: string
2019-05-15 14:23:45.472565+0800 ThreadLockDemo[5513:141514] substring a very lon
2019-05-15 14:23:45.472716+0800 ThreadLockDemo[5513:141513] Thread A: a very long string
2019-05-15 14:23:45.472849+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.473140+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.473324+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.473570+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.473709+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.473892+0800 ThreadLockDemo[5513:141514] Thread B: string
2019-05-15 14:23:45.474088+0800 ThreadLockDemo[5513:141514] Thread B: string

结果分析: 终于不崩溃了, 而且也不是串行队列的执行顺序
方式还有很多种, 就不一一贴出来,你可以去尝试

总结

atomic 原子性 只是对属性的 setter 和 getter 方法加了锁, 并不一定线程安全, 而且很耗性能, 建议开发者使用nonatomic, 然后自己去在需要的地方加锁, 如果可以的话, 尽量避免多线程的设计,除非你拥有足够的实力.

GCD传送门:https://www.jianshu.com/p/8321f35e30b9
转载注明出处, 谢谢
原文链接:https://www.jianshu.com/p/7ada4581838c

你可能感兴趣的:(线程安全之加个锁)