Детали низкого уровня реализации PerformSelectorOnMainThread:

Было интересно, знает ли кто-нибудь или имеет указатели на хорошую документацию, в которой обсуждаются детали реализации низкоуровневого метода Cocoa 'performSelectorOnMainThread:'.

Моя лучшая догадка, и я думаю, что она, вероятно, довольно близка, заключается в том, что он использует порты mach или абстракцию поверх них для обеспечения связи внутри потока, передавая информацию о селекторе как часть сообщения mach.

Верно? Неправильно? Спасибо!

Обновление 09:39AMPST

Спасибо, Эван ДиБиасе и Меки, за ответы, но поясняю: я понимаю, что происходит в цикле выполнения, но я ищу ответ; "где метод ставится в очередь? как информация селектора передается в очередь?" Ищу больше, чем информацию о документах Apple: я их читал

Обновление 14:21 по тихоокеанскому времени

Крис Хэнсон поднимает хороший вопрос в комментарии: моя цель здесь не в том, чтобы изучить основные механизмы, чтобы использовать их в своем собственном коде. Скорее, меня просто интересует лучшее концептуальное понимание процесса подачи сигнала другому потоку для выполнения кода. Как я уже сказал, мои собственные исследования наводят меня на мысль, что для передачи информации о селекторе между потоками IPC использует обмен сообщениями Mach, но я специально ищу конкретную информацию о том, что происходит, поэтому я могу быть уверен, что понимаю вещи правильно. Спасибо!

Обновление от 06.03.09

Я открыл награду за этот вопрос, потому что очень хотел бы получить на него ответ, но если вы пытаетесь собрать, убедитесь, что вы прочитали все, включая все текущие ответы, комментарии к обоим эти ответы и на мой первоначальный вопрос, а также текст обновления, который я разместил выше. Я ищу детали самого низкого уровня механизма, используемого performSelectorOnMainThread: и подобными, и, как я упоминал ранее, я подозреваю, что это как-то связано с портами Mach, но я действительно хотел бы знать наверняка. Награда не будет присуждена, если я не смогу подтвердить правильность данного ответа. Всем спасибо!


person rpj    schedule 29.09.2008    source источник
comment
Было бы полезно, если бы вы объяснили, почему, по вашему мнению, вам необходимо знать точный используемый механизм. Если вы хотите или чувствуете, что вам нужно полагаться на него в коде уровня приложения, велика вероятность, что вы ошибаетесь...   -  person Chris Hanson    schedule 30.09.2008
comment
Хорошая точка зрения! Устранено в редактировании, которое, надеюсь, проливает больше света на то, что мне нужно.   -  person rpj    schedule 30.09.2008
comment
Я сомневаюсь, что вы получите конкретную информацию, так как рассматриваемый код не является открытым исходным кодом.   -  person Chris Hanson    schedule 30.09.2008
comment
Еще один отличный момент, но я надеялся, что кто-то все равно знает или провел подобное исследование. О, это стоило того, чтобы попробовать! :)   -  person rpj    schedule 30.09.2008
comment
Я тоже хотел бы это знать. Я заметил, что после вызова PerformSelectorOnMainThread из основного потока NSThread считает мое приложение многопоточным. Странно, нет?   -  person Rhythmic Fistman    schedule 21.10.2008


Ответы (4)


Да, он использует порты Mach. Что происходит, так это:

  1. Блок данных, инкапсулирующий информацию о выполнении (целевой объект, селектор, необязательный аргумент объекта для селектора и т. д.), ставится в очередь в информации о цикле выполнения потока. Это делается с помощью @synchronized, который в конечном итоге использует pthread_mutex_lock.
  2. CFRunLoopSourceSignal вызывается, чтобы сообщить, что источник готов к срабатыванию.
  3. CFRunLoopWakeUp вызывается, чтобы сообщить циклу выполнения основного потока, что пора просыпаться. Это делается с помощью mach_msg.

Из документов Apple:

Исходники версии 1 управляются циклом выполнения и ядром. Эти источники используют порты Маха для подачи сигнала о готовности источников к срабатыванию. Источник автоматически сигнализируется ядром, когда сообщение поступает на порт Mach источника. Содержимое сообщения передается источнику для обработки при запуске источника. Источники цикла выполнения для CFMachPort и CFMessagePort в настоящее время реализованы как источники версии 1.

Я смотрю на трассировку стека прямо сейчас, и вот что она показывает:

0 mach_msg
1 CFRunLoopWakeUp
2 -[NSThread _nq:]
3 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:]
4 -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:]

Установите точку останова на mach_msg, и вы сможете ее подтвердить.

person Tony    schedule 06.03.2009

Еще одно редактирование:

Чтобы ответить на вопрос комментария:

какой механизм IPC используется для передачи информации между потоками? Общая память? Розетки? Обмен сообщениями с Маха?

NSThread хранит внутреннюю ссылку на основной поток, и через эту ссылку вы можете получить ссылку на NSRunloop этого потока. NSRunloop внутренне представляет собой связанный список, и при добавлении объекта NSTimer в цикл выполнения создается и добавляется в список новый элемент связанного списка. Таким образом, вы могли бы сказать, что это разделяемая память, связанный список, который на самом деле принадлежит основному потоку, просто модифицируется из другого потока. Существуют мьютексы/блокировки (возможно, даже объекты NSLock), которые гарантируют, что редактирование связанного списка будет потокобезопасным.

Псевдокод:

// Main Thread

for (;;) {
    lock(runloop->runloopLock);
    task = NULL;
    do {
        task = getNextTask(runloop);
        if (!task) {
            // function below unlocks the lock and
            // atomically sends thread to sleep.
            // If thread is woken up again, it will
            // get the lock again before continuing
            // running. See "man pthread_cond_wait"
            // as an example function that works
            // this way
            wait_for_notification(runloop->newTasks, runloop->runloopLock);
        }
    } while (!task);
    unlock(runloop->runloopLock);
    processTask(task);
}


// Other thread, perform selector on main thread
// selector is char *, containing the selector
// object is void *, reference to object

timer = createTimerInPast(selector, object);
runloop = getRunloopOfMainThread();
lock(runloop->runloopLock);
addTask(runloop, timer);
wake_all_sleeping(runloop->newTasks);
unlock(runloop->runloopLock);

Конечно, это слишком упрощенно, большинство деталей здесь скрыто между функциями. Например. getNextTask вернет таймер только в том случае, если он уже должен был сработать. Если дата срабатывания для каждого таймера все еще находится в будущем и нет другого события для обработки (например, события клавиатуры, мыши из пользовательского интерфейса или отправленное уведомление), он вернет NULL.


Я все еще не уверен, в чем вопрос. селектор — это не что иное, как строка C, содержащая имя вызываемого метода. Каждый метод является обычной функцией C, и существует таблица строк, содержащая имена методов в виде строк и указателей на функции. Это самые основы того, как на самом деле работает Objective-C.

Как я написал ниже, создается объект NSTimer, который получает указатель на целевой объект и указатель на строку C, содержащую имя метода, и когда таймер срабатывает, он находит правильный метод C для вызова с помощью таблицы строк (следовательно, ему нужно строковое имя метода) целевого объекта (следовательно, ему нужна ссылка на него).

Не совсем реализация, но довольно близко к ней:

У каждого потока в Cocoa есть NSRunLoop (он всегда там, вам никогда не нужно создавать поток). PerformSelectorOnMainThread создает объект NSTimer, например этот, который срабатывает только один раз и где время срабатывания уже находится в прошлом (поэтому требуется немедленное срабатывание), затем получает NSRunLoop основного потока и добавляет туда объект таймера. Как только основной поток переходит в режим бездействия, он ищет следующее событие в своем цикле выполнения для обработки (или переходит в спящий режим, если обрабатывать нечего, и снова просыпается, как только событие добавляется). ) и выполняет его. Либо основной поток занят, когда вы планируете вызов, и в этом случае он будет обрабатывать событие таймера, как только завершит свою текущую задачу, либо в данный момент он спит, и в этом случае он будет разбужен путем добавления события и обрабатывает его немедленно.

Хорошим источником информации о том, вероятнее всего, делает это Apple (никто не может сказать наверняка, поскольку исходный код закрыт), является GNUStep. Поскольку GCC может работать с Objective-C (это не просто расширение, поставляемое только Apple, с ним может справиться даже стандартный GCC), однако наличие Obj-C без всех базовых классов, поставляемых Apple, довольно бесполезно, сообщество GNU попыталось переделать его. - реализовать наиболее распространенные классы Obj-C, которые вы используете на Mac, и их реализация — OpenSource.

Здесь вы можете загрузить последний исходный пакет.

Распакуйте его и взгляните на реализацию NSThread, NSObject и NSTimer для деталей. Я предполагаю, что Apple не сильно отличается от этого, я, вероятно, мог бы доказать это с помощью gdb, но почему они должны делать это так, как этот подход? Это умный подход, который работает очень хорошо :)

person Mecki    schedule 29.09.2008
comment
Большое спасибо за совместные усилия, Меки, и я комментирую только для того, чтобы вы знали, почему я не помечаю ваш пост как правильный ответ: вопрос не в таймерах и селекторах, а скорее; какой механизм IPC используется для передачи информации между потоками? Общая память? Розетки? Обмен сообщениями с Маха? - person rpj; 30.09.2008
comment
И, хотя GNUStep в целом является отличным ресурсом, здесь он не помогает, потому что он, по сути, написан поверх того, что выглядит как POSIX API, который в OS X реализован поверх подсистем более низкого уровня, таких как Mach. Как говорит Крис Хэнсон, ответ неуловим, потому что он, скорее всего, с закрытым исходным кодом. - person rpj; 30.09.2008
comment
На самом деле Mac OS является одной из небольших систем, соответствующих POSIX, и предположения о том, что Apple не использует вызов POSIX, если он есть, а вместо этого использует какой-то неясный вызов Mach, - это просто предположение. На самом деле использование DTrace на Leopard показывает, что Obj-C использует много вызовов POSIX. - person Mecki; 01.10.2008

документация для метода performSelectorOnMainThread:withObject:waitUntilDone: NSObject гласит:

Этот метод ставит сообщение в очередь в цикле выполнения основного потока, используя режимы цикла выполнения по умолчанию, то есть режимы, связанные с NSRunLoopCommonModes. В рамках своей обычной обработки цикла выполнения основной поток извлекает сообщение из очереди (при условии, что оно выполняется в одном из режимов цикла выполнения по умолчанию) и вызывает нужный метод.

person Evan DiBiase    schedule 29.09.2008

Как сказал Меки, более общий механизм, который можно использовать для реализации -performSelectorOn…, — это NSTimer.

NSTimer подключен к CFRunLoopTimer по бесплатному номеру. Реализация CFRunLoopTimer — хотя и не обязательно та, которая фактически используется для обычных процессов в OS X — может быть найдена в CFLite (подмножество CoreFoundation с открытым исходным кодом; пакет CF-476.14 в Исходный код Darwin 9.4. (CF-476.15, соответствующий OS X 10.5.5, пока недоступен.)

person Jens Ayton    schedule 29.09.2008