состояние гонки или нет?делегаты и несколько потоков - PullRequest
3 голосов
/ 10 июля 2010

Я ломаю голову над тем, как многопоточность может работать с делегатами.

В главном потоке есть объект "A", который создал объект "B".Объект "A" является делегатом для объекта "B".Объект "B" использует поток для запуска кода.

Когда объект "B" хочет уведомить делегата, он делает:

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

Свойство "делегата" является назначением,atomic @property.Следовательно, может показаться, что сгенерированный геттер выполнит [[делегат сохранить] авто-релиз] в соответствии с target c manual .

Метод dealloc для «A»:

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

Это может привести к возможной ситуации, когда потоки будут работать следующим образом:

  1. Основной поток: call [A dealloc] (из-за вызова [release])
  2. Другой поток: b вызывает [A retain] (из-за вызова [self делегат])
  3. Основной поток: вызывает [b setDelegate: nil]
  4. Другой поток: вызывает executeSelectorOnMainThread

На этапе2, казалось бы, что retain не может преуспеть, так как dealloc уже привержен этому - это условие гонки?Что произойдет, если вы вызовете retain для объекта, который находится в процессе освобождения?Может ли это на самом деле произойти?

Если это условие гонки, как многопоточные объекты с делегатами обычно этого избегают?

(Это возникло из-за немного похожего, но более простого вопроса / ответа, который я ранее спросил, как обрабатывать setDelegate с несколькими потоками .)

Обновление

Это условие гонки, как подтверждает принятый ответ.

Решение моей первоначальной проблемы состоит в том, чтобы избежать этого случая все вместе, я обновил Как обрабатывать setDelegate: при использовании многопоточности , чтобы показать это.

Ответы [ 2 ]

2 голосов
/ 10 июля 2010

Я не думаю, что есть блокировка 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];
}

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

Программа заканчивается сбоем с 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 dealloc], если объект сохранил ... безумную идею).Теперь, если мы изменим -dealloc для A, чтобы сначала установить делегат B на nil, программа будет работать, но только потому, что мы обнуляем delegate в B в releaseDelegate.

Я не знаю, действительно ли это отвечает на ваш вопрос, но если предположить, что sleep () не каким-то образом нарушают блокировки потоков, то же самое должно произойти, когда dealloc вызывается прямо перед retain.

0 голосов
/ 10 июля 2010

Это настолько приблизительное предположение, какое я собираюсь дать при переполнении стека, но здесь: я думаю, что -dealloc синхронизируется с той же блокировкой, что и -retain и -release, чтобыть сумасшедшим, чтобы не быть атомным.Эта блокировка не получена волшебным образом в dealloc, так как ясно, что она заполнена вашим собственным кодом, а скорее в выпуске, она просто удерживает ту же блокировку, что и ее dealloc.(это может быть одной из причин того, что вы не должны вызывать dealloc напрямую)

Теперь в объекте B [self Delegate] вызывает объект A, который, если я прав, атомарен в отношениидля освобождения и освобождения, и произойдет либо до [A dealloc], потому что это произойдет до - [A release], или произойдет после - [A dealloc], в зависимости от его времени.

В первомВ случае, когда - [A retain] происходит раньше - [A release], результат очевиден: объект A не будет освобожден до тех пор, пока не будет получено следующее - [A autorelease] от того же средства доступа, а объект B вызовет метод делегата дляНеподвижный объект А.

Второй случай гораздо сложнее, и с этого момента мы оставим прочную основу факта и будем вместе путешествовать по темным болотам памяти в заросли самых смелых догадок.Я полагаю, что во втором случае - [A dealloc] пытается установить делегат объекта B (как было сказано ранее, в то время как другой поток ожидает получения блокировки своего делегата) равным nil.Однако, с атомарным свойством, A должен был бы затем получить блокировку, которую B приобрел и использовал во время ожидания блокировки A, используемой для retain / release / dealloc, которая, очевидно, используется.

Поэтому я думаю, чтоэто может привести к тупику, хотя, опять же, я совершенно не уверен, и этот ответ в значительной степени основан на догадках о том, что заблокировано и когда.Еще раз, мое единственное жизнеспособное решение (но не переставайте искать, должен быть лучший способ) - сохранить делегата, по крайней мере, во время работы второго потока, и предотвратить его освобождение в первую очередь.

...