竞争条件与否?代表和多线程

时间:2010-07-09 22:14:13

标签: iphone objective-c multithreading delegates

我很困惑多线程如何与代表合作。

主线程有一个对象“A”,它创建了一个对象“B”。对象“A”是对象“B”的委托。对象“B”使用线程来运行代码。

当对象“B”想要通知代理时,它会:

[[self delegate] performSelectorOnMainThread:@selector(didFinish:) withObject:self waitUntilDone:[NSThread isMainThread]];

“delegate”属性是一个assign,atomic @property。因此,根据objective c manual,生成的getter似乎会[[delegate retain] autorelease]。

“A”的dealloc方法是:

- (void)dealloc
{
    [b setDelegate:nil];
    [b release];
    [super dealloc];
}

这似乎会导致线程运行的可能情况如下:

  1. 主线程:调用[A dealloc](由于调用[a release])
  2. 其他主题:b调用[保留](由于对[self delegate]的调用)
  3. 主线程:调用[b setDelegate:nil]
  4. 其他线程:调用performSelectorOnMainThread

在第2步,似乎保留不能成功,因为dealloc已经承诺 - 这种竞争条件?如果在正在解除分配的对象上调用retain,会发生什么?它真的会发生吗?

如果是竞争条件,代表的多线程对象通常如何避免它?

(这源于我之前提到的how to handle setDelegate with multiple threads稍微相似但更简单的问题/答案。)

更新

这是一种竞争条件,正如公认的答案所证明的那样。

原始问题的解决方案是一起避免这种情况,我已更新How to handle setDelegate: when using multipe threads以显示此内容。

2 个答案:

答案 0 :(得分:2)

我认为deallocretain / 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];
}

A:

#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的releaseDelegate方法中与EXC_BAD_ACCESS崩溃。以下是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 dealloc])。现在,如果我们首先修改A的-dealloc以将B的委托设置为nil,则该程序可以正常工作,但这只是因为我们在delegate中的B中只有releaseDelegate

我不知道这是否能回答你的问题,但是假设sleep()不会以某种方式破坏线程锁定,当dealloc在{{1}之前被调用时,应该会发生完全相同的行为。 }}

如果你想玩它,可以使用XCode项目:http://www.megaupload.com/?d=P02152EM

答案 1 :(得分:0)

这就像我将要给出的堆栈溢出一样疯狂猜测,但是这里有: 我认为-dealloc被同步到与-retain-release相同的锁定,这对于不是原子的来说会很疯狂。这个锁在dealloc中并没有神奇地得到,显然充满了你自己的代码,而是在释放时,它只是在它的dealloc时保持相同的锁。 (这可能是你不应该直接调用dealloc的原因之一)

现在在对象B中,[self delegate]调用对象A的保留,如果我是对的,就dealloc和release而言是原子的,并且要么在之前发生 - [dealloc]因为它将在之前发生 - [发布],或将发生在[[dealloc]之后,取决于其时间。

在第一种情况下, - [保留]发生在 - [释放]之前,结果是显而易见的:对象A将不会被解除分配,直到以下 - 来自同一访问者的[自动释放],对象B将在静止对象A上调用委托方法。

第二种情况要复杂得多,从这一点来说,我们将离开事实的坚实基础,通过浑浊的记忆一起旅行到最疯狂的猜测丛林中。我相信在第二种情况下, - [dealloc]尝试将对象B的委托(如前所述,而另一个线程等待获取其委托上的锁)设置为nil。但是,对于原子属性,A必须获取已获取的锁B并在等待用于保留/释放/ dealloc的锁A时使用,这显然在使用中。

我认为这会造成僵局,虽然我完全不确定,这个答案很大程度上是基于对锁定内容和时间的猜测。再一次,我唯一可行的解​​决方案(但不要停止查看,必须有更好的方法)是保留委托,至少在第二个线程运行时,并防止它首先被解除分配。