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.
Ответ написан
Ваш ответ на вопрос

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

Войти через центр авторизации
Похожие вопросы