【问题标题】:a race condition or not? delegates & multiple threads比赛条件与否?委托和多线程
【发布时间】:2010-07-09 22:14:12
【问题描述】:

我对多线程如何与委托一起工作感到困惑。

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

当对象“B”想要通知委托时,它会这样做:

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

“delegate”属性是一个分配的原子@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 调用 [A 保留](由于调用 [self delegate])
  3. 主线程:调用 [b setDelegate:nil]
  4. 其他线程:调用 performSelectorOnMainThread

在第 2 步,retain 似乎无法成功,因为 dealloc 已经承诺 - 这是竞争条件吗?如果你对一个正在被释放的对象调用retain会发生什么?真的会发生吗?

如果是竞态条件,带委托的多线程对象通常如何避免呢?

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

更新

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

我最初的问题的解决方案是一起避免这种情况,我已经更新了How to handle setDelegate: when using multipe threads 来显示这个。

【问题讨论】:

    标签: iphone objective-c multithreading delegates


    【解决方案1】:

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

    答:

    #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] 并没有调用 [super dealloc] 会发生什么......疯狂的想法)。现在,如果我们修改 A 的 -dealloc 以首先将 B 的委托设置为 nil,则程序可以工作,但这只是因为我们在 releaseDelegate 中的 B 中将 delegate 置零。

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

    【讨论】:

    • 我认为你已经最终证明这是一个竞争条件!非常好的工作。
    【解决方案2】:

    这几乎是一个疯狂的猜测,因为我将给出堆栈溢出,但这里是: 我认为-dealloc-retain-release 同步到同一个锁,如果不是原子的,那就太疯狂了。这个锁在dealloc中并没有神奇地获得,很明显它充满了你自己的代码,而是在释放时,它只是在执行dealloc时持有相同的锁。 (这可能是你不应该直接调用 dealloc 的原因之一)

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

    在第一种情况下,-[A retain] 发生在 -[A release] 之​​前,结果很明显:对象 A 不会被释放,直到来自同一访问器的以下 -[A autorelease],对象 B 将在静止对象 A 上调用委托方法。

    第二种情况要复杂得多,从这一点开始,我们将离开事实的坚实基础,一起穿越黑暗的记忆沼泽,进入最疯狂的猜测丛林。我相信在第二种情况下,-[A dealloc] 尝试将对象 B 的委托(如前所述,而另一个线程正在等待获取其委托上的锁)设置为零。但是,如果使用原子属性,A 将不得不获取 B 已获取并正在使用的锁,同时等待用于保留/释放/释放的锁 A,这显然正在使用中。

    因此,我认为这会导致死锁,尽管我完全不确定,而且这个答案主要基于对锁定的内容和时间的猜测。再一次,我唯一可行的解​​决方案(但不要停止寻找,必须有更好的方法)是保留委托,至少在第二个线程运行时,并防止它首先被释放。

    【讨论】:

    • 感谢您的回答杰瑞德。我同意在第二个线程处于活动状态的整个过程中保留委托可以解决它,但这似乎特别违背了 Cocoa 基础指南中的建议,其中指出“委托对象不(也不应该)保留他们的委托”。正如你所说,我会继续寻找。如果/当我找到其他解决方案时,我会报告。
    • 不知道这是否为时已晚,但我只是对如何解决这个问题有了另一个想法:与其在主线程上调用 -[delegate didFinish:],不如调用类似于主线程上的 [self alertDelegateDidFinish],其内容将是 [self.delegate didFinish:self]
    • 谢谢 - 这与我最终所做的非常接近 - 我将使用我使用的最终方法更新我的原始问题 (stackoverflow.com/questions/3158356/…),效果很好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-10
    • 1970-01-01
    • 1970-01-01
    • 2012-03-18
    • 1970-01-01
    • 1970-01-01
    • 2012-07-13
    相关资源
    最近更新 更多