线程安全之加个锁

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

@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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,107评论 1 32
  • 线程概述 有些程序是一条直线,起点到终点;有些程序是一个圆,不断循环,直到将它切断一个运行着的程序就是一个进程或者...
    褪而未变阅读 302评论 0 0
  • Q:为什么出现多线程? A:为了实现同时干多件事的需求(并发),同时进行着下载和页面UI刷新。对于处理器,为每个线...
    幸福相依阅读 1,582评论 0 2
  • 基础 1. 为什么说Objective-C是一门动态的语言? 2. 讲一下MVC和MVVM,MVP? 3. 为...
    波妞和酱豆子阅读 3,327评论 0 46
  • 三国时,华夏儿女就在踢足球了, 那时候,它叫蹴鞠,当时,只有中国人会踢。 后来,人们忙于吟诗做对,写词谱曲,没空去...
    慎言特工阅读 235评论 0 2