Почему у меня не работает NSLock?

Я пишу код для рендеринга и поворота изображения, детали которого одновременно вычисляются и обновляются. Он работает без ошибок в одном потоке (со ссылкой на отображение), но выглядит неуклюже, и я не хочу, чтобы вычисления запускались по ссылке на отображение. Итак, я хочу выполнить весь код, связанный с OpenGL, в основном потоке (со ссылкой на отображение) и все вычисления во втором потоке (выполнение цикла while (YES)).

Я реализовал это с помощью NSThread. Некоторое время он прекрасно работает, а затем выходит из строя с «Поток 1: программа получила сигнал: «EXC_BAD_ACCESS»» во время glDrawArrays, а иногда появляются странные вспышки графики. Это то, что я ожидал, если основной поток считывает данные уровня модели в то же время, когда второй поток перезаписывает их.

Затем я определил NSLock в объекте модели и заблокировал его для всей записи (в моем классе модели) и чтения (в моем классе представления)... но это все равно может привести к той же ошибке, и графика все еще время от времени имеет странные вспышки .

Я сделал что-то не так здесь, или моя проблема где-то еще?

Во-вторых, как правильно остановить второй поток в этом случае? Ссылка на класс NSThread предлагает использовать отмену, проверку isCancelled и выход, если это так, но также говорит, что следует избегать вызова выхода.

Вот модификации кода - в моем классе контроллера (я использую XCode 4.2 с ARC, все мои ивары неатомарны):

@interface MyController : NSObject {
    NSThread *calcThread;
    ...
}
// (I do not give it an @property or @synthesize line)

@implementation MyController
- (void) awakeFromNib {
    calcThread = [[NSThread alloc] initWithTarget:self 
            selector:@selector(calcLoop:) object:nil];
    [calcThread start];
    ...
}
- (void) calcLoop:(id)arg { 
    @autoreleasepool {
        while (YES)
            [myModel calculate];
    }
}
...

Я поместил NSLock в свой класс модели:

@interface MyModel : NSObject {
    NSLock* oLock;
    ...
}
@property (nonatomic, strong) NSLock* oLock;

@implementation myModel
-(id) init {
    oLock = [[NSLock alloc] init];
    ...
}
-(void) changeModelAppearance {
    [oLock lock];
    ...
    [oLock unlock];
}
...

и в моем классе просмотра:

@implementation MyView
-(void) modelUpdated:(NSNotification *) notification {
// modelUpdated is called via the NSNotificationCenter
    MyModel* myModel = (MyModel*) [notification object];
    [myModel.oLock lock];
    ... // update OpenGL structures with data from myModel
    [myModel.oLock unlock];
}
...

Спасибо!


person Racing Tadpole    schedule 27.12.2011    source источник
comment
Где ваш звонок glDrawArrays?   -  person 一二三    schedule 27.12.2011
comment
Вызов glDrawArrays находится в классе представления, в другом методе (render); это вообще не относится к модели.   -  person Racing Tadpole    schedule 28.12.2011
comment
Я понял, что одна проблема с моим кодом заключается в том, что метод modelUpdated может обновлять структуры OpenGL одновременно с вызовом метода render, поскольку NSNotificationCentre может вызывать его из любого потока. Так что теперь я вставил вторую блокировку в modelUpdated, а также поместил ее в метод рендеринга вокруг glDrawArrays. Но я все еще получаю ту же ошибку.   -  person Racing Tadpole    schedule 02.01.2012
comment
Возможно ли, что структуры OpenGL, установленные в modelUpdated:, изменяются/освобождаются до вызова render?   -  person 一二三    schedule 02.01.2012
comment
спасибо - нет, я так не думаю. Они изменяются только в modelUpdated (или если представление выпущено, чего не должно происходить). У меня была одна мысль на случай, если это уместно: в моем классе модели есть много переменных, включая один изменяемый массив другого модельного класса, который сам имеет множество переменных. Координаты и цвет взяты у некоторых из этих последних иваров. Я не использовал NSLock, когда другие ivars в любом классе модели читаются или записываются. (Я использую NSLock, если добавляется изменяемый массив.)   -  person Racing Tadpole    schedule 02.01.2012
comment
Во-первых, я бы посоветовал найти новый способ синхронизации потоков. А еще лучше не синхронизировать их. Возможно, вы сможете разбить обновления модели на те, которые влияют на видимое состояние, и те, которые не влияют. Сделайте последнее в фоновом потоке. Делайте первое в промежутках между рендерами кадров. Если есть некоторое совпадение, используйте метод двойной буферизации (две копии, одна для записи, одна для чтения). Уменьшите перекрытие до минимально возможного, чтобы сэкономить память. Кроме того, чтобы выйти из потока, просто выполните команду while(!cancel). Когда последний код завершается в потоке, поток должен уйти.   -  person Bored Astronaut    schedule 06.01.2012
comment
Я могу легко разбить обновления модели на две группы, как вы предлагаете, и, используя ваш подход, я думаю, что смогу обойтись только одним объектом NSLock вместо двух. Но я не вижу, как свести его к нулю - даже с двойной буферизацией мне все равно нужно будет обновить модель чтения, и в этот момент мне не понадобится NSLock, если модель записи записывается на в то же время? Спасибо за вашу помощь, очень признателен!   -  person Racing Tadpole    schedule 11.01.2012
comment
Я выполнил это до конца - я удалил modelUpdated: - фактически всю структуру NSNotification, которую я построил, - и теперь проверьте в верхней части render, было ли обновление. Если есть, то делаю то, что раньше было в modelUpdated:. Это означает, что мой класс представления теперь имеет ссылку на объект модели, чего я пытался избежать. Но это означает, что я могу избавиться от двух взаимосвязанных NSLock, и все заработает. Так что спасибо!   -  person Racing Tadpole    schedule 22.01.2012


Ответы (1)


Я думаю, что в этом случае вам было бы намного проще использовать Grand Central Dispatch.

@interface MyController : NSObject { // Not use why you're not inheriting from NSController here.
    dispatch_queue_t calQueue;
    ...
}

- (void) awakeFromNib {
    calcQueue = dispatch_queue_create("com.yourApp.calc", DISPATCH_QUEUE_SERIAL);
    dispatch_async(calcQueue, ^{
        while(YES) // This will peg the CPU at 100%
            [myModel calculate];
    });
}

класс модели

@interface MyModel : NSObject {
    dispatch_queue_t modelQueue;
    ...
}
@property dispatch_queue_t modelQueue;

@implementation myModel
-(id) init {
    modelQueue = dispatch_queue_create("com.yourApp.model", DISPATCH_QUEUE_SERIAL);
}

-(void) dealloc {
    dispatch_release(modelQueue);
}

-(void) changeModelAppearance {
    dispatch_async(modelQueue, ^{
        ...
    });
}
...

Вид

@implementation MyView
-(void) modelUpdated:(NSNotification *) notification {
// modelUpdated is called via the NSNotificationCenter
    MyModel* myModel = (MyModel*) [notification object];
    dispatch_async(model.modelQueue, ^{
        ... // update OpenGL structures with data from myModel 
    });
}
...

Чтобы приостановить любую из очередей, просто вызовите dispatch_suspend, а чтобы перезапустить любую очередь, используйте dispatch_resume.

Если вы используете таймер вместо бесконечного цикла, вы можете уменьшить количество используемого процессора.

calcTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(calcTimer, DISPATCH_TIME_NOW, DT, 1000);
dispatch_source_set_event_handler(calcTimer, ^{
    ...
});
dispatch_resume(calcTimer);

Это потребует гораздо меньших накладных расходов процессора.

person user1139069    schedule 14.01.2012
comment
Спасибо - я очень ценю вашу помощь - мне было интересно, поможет ли мне GCD. Ваш первый почти в скобках комментарий, не уверенный, почему вы не наследуете от NSController, заставил меня понять, как много мне нужно узнать о Cocoa... и, прочитав больше, заставил меня задуматься о рефакторинге для использования KVO и, в частности, шаблона Receptionist, который кажется подходит для этой проблемы. Спасибо, что показали мне свет! Описание шаблона Receptionist от Apple в Руководстве по основам Cocoa также включает некоторый код для выполнения в нескольких потоках с использованием NSOperationQueue ... - person Racing Tadpole; 22.01.2012