Низкоуровневые подробности реализации executeSelectorOnMainThread: - PullRequest
8 голосов
/ 29 сентября 2008

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

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

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

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

Спасибо Эвану ДиБиасе и Меки за ответы, но уточнить: я понимаю, что происходит в цикле выполнения, но я ищу ответ на этот вопрос; « где - метод, помещенный в очередь? как информация о селекторе передается в очередь?» Нужна не только информация от Apple: я прочитал их

Обновление 14: 21PST

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

Обновление 03/06/09

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

Ответы [ 4 ]

10 голосов
/ 06 марта 2009

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

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

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

Источники версии 1 управляются циклом выполнения и ядром. Эти источники используют порты Маха, чтобы сигнализировать, когда источники готовы к выстрелу. Источник автоматически сигнализирует ядро, когда на порт Маха источника поступает сообщение. Содержимое сообщения передается источнику для обработки при запуске источника. Источники цикла выполнения для 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, и вы сможете подтвердить ее.

2 голосов
/ 29 сентября 2008

Еще одна правка:

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

для чего используется механизм 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 для вызова с использованием строки таблица (следовательно, ему нужно имя строки метода) целевого объекта (следовательно, ему нужна ссылка на него).

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

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

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

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

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

2 голосов
/ 29 сентября 2008

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

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

0 голосов
/ 30 сентября 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...