为什么在多个线程中修改 NSMutableSet 时会崩溃,而在同一线程中修改自定义对象 Person 不会崩溃?
Why it is crashing while modifying NSMutableSet inside multiple threads, however modifying custom object Person inside same threads does not crash?
下面是代码。在街区内,我尝试了 3 个案例。一次只评论一个案例。
set = [[NSMutableSet alloc] init];
Person* p = [[Person alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
for (int index = 0; index < 100; index++) {
dispatch_async(queue, ^{
//Case 1: adding constant value each time
//[self->set addObject:@"0"];
//Case 2: adding new value on each call.
[self->set addObject:[NSNumber numberWithInt:index]];
//Case 3: Modifying custom object
// p.firstname = [NSString stringWithFormat:@"%d", index];
});
}
我知道如果我在块中放置一个 lock/@syncronize 它就可以正常工作。
问题 1:为什么第一个 运行s 即使我尝试索引范围从 0 到 100000 也没有任何问题。可变集不是线程安全的,所以它一定是在某个地方崩溃了。那么为什么它在没有同步的情况下工作?
问题 2:因为情况 1 运行 每次都很好,所以情况 2 也应该 运行 很好,因为正在修改同一组。但它在每个 运行.
上崩溃
问题 3:案例 3 没有崩溃,即使我每次都输入新值,就像案例二一样。
注意:所有属性都是非原子的。
案例 2 崩溃日志:
**malloc: *** **error for object 0x6000028e9260: pointer being freed was not allocated****
首先,让我们明确一点,none 是线程安全的。如果您真的要从多个线程执行此操作,则需要同步。
其次,你也应该犹豫是否从你无法产生特定崩溃中得出结论。众所周知,它们很难表现出来。另外,您正在测试一组非常狭窄的行为,从不测试读取是否成功、返回的对象是否在内部一致等。
关于将同一对象重复添加到集合中,这不是一个很好的测试,因为后续添加无疑会确定该对象已经在集合中,因此实际上没有发生变化。
对于您的自定义对象示例,您所做的只是改变单个指针,但从未使用过它。再加上某些硬件功能将使任何问题都难以显现。这并不意味着您不需要同步,只是表明问题将变得非常困难。
此外,像“人”这样的自定义对象的同步通常需要在更高的抽象层次上进行(例如,如果更改一个人的全名,同时读取确实应该等待所有三个属性,first,middle,和姓氏,最后,否则你可能会在某种不确定的状态下看到它)。
最重要的是,如果你要从多个线程访问它们,你真的应该同步你与可变集和可变 person 对象的交互。
我建议您调查线程清理器(TSAN,对它的朋友)。因此,在 Xcode 中,转到“编辑器”»“方案”»“编辑方案...”并打开线程清理程序:
这将帮助您识别来自多个线程的不安全访问。如需更多信息,请观看 Thread Sanitizer and Static Analyzer 视频。
但正如 Apple 在上面的视频中建议的那样,“没有所谓的‘良性’种族。”
下面是代码。在街区内,我尝试了 3 个案例。一次只评论一个案例。
set = [[NSMutableSet alloc] init];
Person* p = [[Person alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
for (int index = 0; index < 100; index++) {
dispatch_async(queue, ^{
//Case 1: adding constant value each time
//[self->set addObject:@"0"];
//Case 2: adding new value on each call.
[self->set addObject:[NSNumber numberWithInt:index]];
//Case 3: Modifying custom object
// p.firstname = [NSString stringWithFormat:@"%d", index];
});
}
我知道如果我在块中放置一个 lock/@syncronize 它就可以正常工作。
问题 1:为什么第一个 运行s 即使我尝试索引范围从 0 到 100000 也没有任何问题。可变集不是线程安全的,所以它一定是在某个地方崩溃了。那么为什么它在没有同步的情况下工作?
问题 2:因为情况 1 运行 每次都很好,所以情况 2 也应该 运行 很好,因为正在修改同一组。但它在每个 运行.
上崩溃问题 3:案例 3 没有崩溃,即使我每次都输入新值,就像案例二一样。
注意:所有属性都是非原子的。
案例 2 崩溃日志:
**malloc: *** **error for object 0x6000028e9260: pointer being freed was not allocated****
首先,让我们明确一点,none 是线程安全的。如果您真的要从多个线程执行此操作,则需要同步。
其次,你也应该犹豫是否从你无法产生特定崩溃中得出结论。众所周知,它们很难表现出来。另外,您正在测试一组非常狭窄的行为,从不测试读取是否成功、返回的对象是否在内部一致等。
关于将同一对象重复添加到集合中,这不是一个很好的测试,因为后续添加无疑会确定该对象已经在集合中,因此实际上没有发生变化。
对于您的自定义对象示例,您所做的只是改变单个指针,但从未使用过它。再加上某些硬件功能将使任何问题都难以显现。这并不意味着您不需要同步,只是表明问题将变得非常困难。
此外,像“人”这样的自定义对象的同步通常需要在更高的抽象层次上进行(例如,如果更改一个人的全名,同时读取确实应该等待所有三个属性,first,middle,和姓氏,最后,否则你可能会在某种不确定的状态下看到它)。
最重要的是,如果你要从多个线程访问它们,你真的应该同步你与可变集和可变 person 对象的交互。
我建议您调查线程清理器(TSAN,对它的朋友)。因此,在 Xcode 中,转到“编辑器”»“方案”»“编辑方案...”并打开线程清理程序:
这将帮助您识别来自多个线程的不安全访问。如需更多信息,请观看 Thread Sanitizer and Static Analyzer 视频。
但正如 Apple 在上面的视频中建议的那样,“没有所谓的‘良性’种族。”