我很困惑多线程是如何与代表一起工作的。
主线程有一个对象"A",它创建了一个对象"B“。对象"A“是对象"B”的委托。对象"B“使用线程运行代码。
当对象"B“想通知委托时,它会通知:
[[self delegate] performSelectorOnMainThread:@selector(didFinish:) withObject:self waitUntilDone:[NSThread isMainThread]];
“委托”属性是一个赋值的原子@属性。因此,根据目标c手册的说法,生成的getter将执行[委托保留自动释放]。
"A“的脱敏方法是:
- (void)dealloc
{
[b setDelegate:nil];
[b release];
[super dealloc];
}
这似乎会导致线程像这样运行的可能情况:
在第二步,保留似乎不可能成功,因为dealloc已经承诺--这是比赛条件吗?如果您对正在被释放的对象调用retain,会发生什么情况?它真的会发生吗?
如果是争用条件,带委托的多线程对象通常是如何避免的?
(这源于我之前问过的一个稍微类似但更简单的问题/答案,如何使用多个线程处理setDelegate。)
更新
正如公认的答案所证明的那样,这是一种种族行为。
解决我原来的问题的方法是避免这种情况,我已经更新了如何处理setDelegate:当使用多线程时以说明这一点。
发布于 2010-07-10 10:12:56
我不认为dealloc
和retain
/release
之间存在锁。下面的示例中有一个dealloc
方法,其中包含一个sleep()
(是否有人知道sleep()
是否中断了锁?我不认为是这样,但你永远不会知道)。一个更好的例子可能是反复实例化/销毁A和B的实例,直到出现这里提到的情况,而不使用sleep()
。
在我的例子中,视图控制器可以是任何东西:
-(void)septhreadRetainDel
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"[thread#2] sleep(1.f);");
sleep(1.f);
NSLog(@"[thread#2] [b retainDelegate];");
[b retainDelegate];
NSLog(@"[thread#2] sleep(2.f);");
sleep(2.f);
NSLog(@"[thread#2] [b release];");
[b release];
[pool release];
}
- (void)viewDidLoad {
NSLog(@"-(void)viewDidLoad:");
[super viewDidLoad];
NSLog(@"a = [[A alloc] init];");
a = [[A alloc] init];
NSLog(@"[a autorelease];");
[a autorelease];
NSLog(@"b = [[B alloc] init];");
b = [[B alloc] init];
NSLog(@"b.delegate = a;");
b.delegate = a;
NSLog(@"[NSThread detachNewThreadSelector:@selector(septhreadRetainDel) toTarget:self withObject:nil];");
[NSThread detachNewThreadSelector:@selector(septhreadRetainDel) toTarget:self withObject:nil];
}
答:
#import "A.h"
@implementation A
-(void)dealloc
{
NSLog(@"A: dealloc; zzz for 2s");
sleep(2.f);
NSLog(@"A: dealloc; waking up in time for my demise!");
[super dealloc];
}
-(id)retain
{
NSLog(@"A retain (%d++>%d)", self.retainCount, self.retainCount+1);
return [super retain];
}
-(void)release
{
NSLog(@"A release (%d-->%d)", self.retainCount, self.retainCount-1);
[super release];
}
@end
B (.h):
#import "A.h"
@interface B : NSObject {
A *delegate;
}
-(void) retainDelegate;
@property (nonatomic, assign) A *delegate;
@end
B (.m):
#import "B.h"
@implementation B
@synthesize delegate;
-(void)retainDelegate
{
NSLog(@"B:: -(void)retainDelegate (delegate currently has %d retain count):", delegate.retainCount);
NSLog(@"B:: [delegate retain];");
[delegate retain];
}
-(void)releaseDelegate
{
NSLog(@"B releases delegate");
[delegate release];
delegate = nil;
}
-(void)dealloc
{
NSLog(@"B dealloc; closing shop");
[self releaseDelegate];
[super dealloc];
}
-(id)retain
{
NSLog(@"B retain (%d++>%d)", self.retainCount, self.retainCount+1);
return [super retain];
}
-(void)release
{
NSLog(@"B release (%d-->%d)", self.retainCount, self.retainCount-1);
[super release];
}
@end
该程序最终在B的EXC_BAD_ACCESS
releaseDelegate方法中崩溃。以下是NSLogs的输出:
2010-07-10 11:49:27.044 race[832:207] -(void)viewDidLoad:
2010-07-10 11:49:27.050 race[832:207] a = [[A alloc] init];
2010-07-10 11:49:27.053 race[832:207] [a autorelease];
2010-07-10 11:49:27.056 race[832:207] b = [[B alloc] init];
2010-07-10 11:49:27.058 race[832:207] b.delegate = a;
2010-07-10 11:49:27.061 race[832:207] [NSThread detachNewThreadSelector:@selector(septhreadRetainDel) toTarget:self withObject:nil];
2010-07-10 11:49:27.064 race[832:4703] [thread#2] sleep(1.f);
2010-07-10 11:49:27.082 race[832:207] A release (1-->0)
2010-07-10 11:49:27.089 race[832:207] A: dealloc; zzz for 2s
2010-07-10 11:49:28.066 race[832:4703] [thread#2] [b retainDelegate];
2010-07-10 11:49:28.072 race[832:4703] B:: -(void)retainDelegate (delegate currently has 1 retain count):
2010-07-10 11:49:28.076 race[832:4703] B:: [delegate retain];
2010-07-10 11:49:28.079 race[832:4703] A retain (1++>2)
2010-07-10 11:49:28.081 race[832:4703] [thread#2] sleep(2.f);
2010-07-10 11:49:29.092 race[832:207] A: dealloc; waking up in time for my demise!
2010-07-10 11:49:30.084 race[832:4703] [thread#2] [b release];
2010-07-10 11:49:30.089 race[832:4703] B release (1-->0)
2010-07-10 11:49:30.094 race[832:4703] B dealloc; closing shop
2010-07-10 11:49:30.097 race[832:4703] B releases delegate
Program received signal: “EXC_BAD_ACCESS”.
一旦调用了-dealloc
,保留计数就不再重要了。该对象将被销毁(这可能是显而易见的,但我想知道,如果您检查了self的retainCount,如果对象保留了,则没有调用super,会发生什么情况?(疯狂的想法)。现在,如果我们将A的-dealloc
修改为首先将B的委托设置为nil
,则程序可以工作,但这只是因为我们在releaseDelegate
中的B中没有delegate
。
我不知道这是否真的回答了您的问题,但是假设睡眠()不是以某种方式破坏线程锁,那么在dealloc
被调用时,在retain
之前应该会发生完全相同的行为。
发布于 2010-07-10 00:41:25
这与我对堆栈溢出的猜测差不多,但我认为-dealloc
与-retain
和-release
同步到相同的锁上,如果不是原子的,那就太疯狂了。这个锁并不是在dealloc中神奇地获得的,因为很明显,这个锁是由您自己的代码填充的,而是在发布时,它在执行其dealloc操作时只持有相同的锁。(这可能是你不应该直接打电话给dealloc的原因之一)
现在,在对象B中,self委托调用对象A的respect,如果我说得对,这是关于dealloc和retain的原子化,并且要么发生在-A dealloc之前,因为它将发生在-A发布之前,或者发生在-A dealloc之后,这取决于它的时间。
在第一种情况下,-A保留发生在-A发布之前,其结果是显而易见的:对象A将在从同一个访问器获得以下-A自动释放之前不会被释放,而对象B将在保持不变的对象A上调用委托方法。
第二种情况要复杂得多,从这一点开始,我们将离开事实的坚实基础,一起穿越记忆的黑暗沼泽,进入一堆最疯狂的猜测。我认为,在第二种情况下,-A dealloc试图将对象B的委托(正如前面所说的,而另一个线程正在等待获取其委托的锁)设置为零。但是,有了原子属性,A就必须在等待用于保留/释放/去分配的锁A时获得已获得和正在使用的锁B,这显然是在使用中。
因此,我认为这将导致僵局,尽管我再次完全不确定,这一答案主要是基于对什么是锁定和何时锁定的猜测。同样,我唯一可行的解决方案(但不要停止寻找,必须有更好的方法)是保留委托,至少在第二个线程运行时是这样,并防止它首先被释放。
https://stackoverflow.com/questions/3216888
复制相似问题