Krypt
@Krypt

Потокобезопасен ли вызов [NSMutableArray count]?

Есть код. Чужой. (в обоих смыслах, ага).

Есть поток, в котором руками организован цикл.

Буквально — while (running) { }. Никаких NSRunLoop, естественно, там нет. Про должном извращении можно, конечно, вставить, но не хочется.


Возникла необходимость выполнять операции в его контексте, при этом не убив быстродействие (цикл считает игровую логику, помимо прочего он исполняет lua-машину и рисует opengl-кадр. 60 fps и всё такое)


В результате родилось такое решение:

while (_running) {
        if ([tasks count] > 0)
        {
            NSArray *tasksCopy = nil;
            
            @synchronized(tasks)
            {
                tasksCopy = [tasks copy];
                [tasks removeAllObjects];
            }
            
            for (NSInvocation *invocation in tasksCopy)
            {
                [invocation invoke];
            }
            
            [tasksCopy release];
        }
    
        <...>
    }



Задания, естественно, добавляются из другого потока.

Насколько корректно такой вызов [NSMutableArray count]?
  • Вопрос задан
  • 3132 просмотра
Решения вопроса 1
corristo
@corristo
Судя по исходникам происходит просто возврат поля структуры, без сайд-эффектов. Если я не ошибаюсь, для переменной, размер которой равен машинному слову, второй поток всегда будет видеть валидное состояние (если бы длина массива была 64-битной в 32-битном рантайме, возможен бы был случай когда другой поток видел бы 4 байта текущего значения и 4 предыдущего).

Исходник CF(Mutable)ArrayRef: www.opensource.apple.com/source/CF/CF-476.10/CFArray.c

Как вариант можно сделать очередь задач без блокировок на атомиках: developer.apple.com/library/mac/documentation/System/Reference/OSAtomic_header_reference/Reference/reference.html (см. методы OSAtomicDequeue и OSAtomicEnqueue).
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
Yan169
@Yan169
  1. corristo всё правильно написал, NSMutableArray непотокобезопасен, но конкретно метод count наверняка возвращает значение атомарно, что однако не означает потокобезопасности, т.е. например код
    if ([tasks count] > 0){
        [tasks removeObjectAtIndex:0];
    }
    

    может вызвать исключение (например, поток, добавляющий элемент, успел инкрементировать count, но не полностью завершил добавление элемента к моменту вызова removeObjectAtIndex:).
    Но, как Вы правильно заметили, конкретно в вашем коде ничего страшного случиться не должно.

    Однако, как мне видится, вынос if ([tasks count] > 0) за пределы блока синхронизации — это преждевременная оптимизация, в данном коде есть другие места, на что в первую очередь стоит обратить внимание.
  2. Петля while (running){ } будет при отсутствии задач постоянно нагружать процессор «пустыми» циклами, когда как нормальный event loop — нет.
  3. Полноценный NSRunLoop Вам быть может и не обязателен, однако NSAutoreleasePool — наверняка. Причем, т.к. NSAutoreleasePool очищает себя по окончании каждого цикла event loop, которого у вас нет, то периодически создавать и очищать NSAutoreleasePool нужно внутри цикла while(running).
  4. Выбор NSInvocation для заданий несколько удивителен. Во-первых, это неудобно, гораздо удобнее использовать блоки, а во-вторых относительно медленно (что, в принципе наверняка не критично, надо профилировать). Вызов одного блока по времени занимает примерно столько же, сколько отправка одного сообщения, а вызов NSInvocation в ~20 раз медленней. Правда, если в блоке использовать weak переменные, это будет примерно как NSInvocation, но это не ваш случай, т.к. не используется ARC.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы