Было интересно, знает ли кто-нибудь или имеет указатели на хорошую документацию, в которой обсуждаются детали низкоуровневой реализации метода Какао «performSelectorOnMainThread:».
Мое лучшее предположение, которое я считаю, вероятно, довольно близким, заключается в том, что он использует порты mach или абстракцию поверх них для обеспечения внутрипотоковой связи, передавая информацию селектора вместе как часть сообщения mach.
Верно? Неправильный? Спасибо!
Обновление 09:39 AMPST
Спасибо Evan DiBiase и Mecki за ответы, но для пояснения: я понимаю, что происходит в цикле выполнения, но я ищу ответ; «где - метод ставится в очередь? как - информация селектора передается в очередь?» Ищу больше, чем информацию о документах Apple: я их читал
Обновление 14: 21PST
Крис Хансон в своем комментарии поднимает хороший момент: моя цель здесь не в том, чтобы изучить основные механизмы, чтобы использовать их в своем собственном коде. Скорее, меня просто интересует лучшее концептуальное понимание процесса подачи сигналов другому потоку о выполнении кода. Как я уже сказал, мое собственное исследование заставляет меня поверить, что для передачи информации селектора между потоками IPC использует преимущества обмена сообщениями, но я специально ищу информацию конкретный о том, что происходит, поэтому я могу быть уверен, что понимаю вещи правильно. Спасибо!
Обновление 03/06/09
Я объявил награду по этому вопросу, потому что мне бы очень хотелось увидеть ответ на него, но если вы пытаетесь собрать, убедитесь, что вы прочитали все, включая все заданные на данный момент ответы, комментарии как к этим ответам, так и к моему исходному вопросу. , и текст обновления, который я разместил выше. Я ищу детализация самого низкого уровня механизма, используемого performSelectorOnMainThread: и т.п., и, как я упоминал ранее, я подозреваю, что он имеет какое-то отношение к портам Mach, но мне бы очень хотелось знать наверняка. Награда не будет присуждена, если я не смогу подтверждать дать правильный ответ. Спасибо всем!
Хорошая точка зрения! Рассмотрено в редактировании, которое, надеюсь, проливает больше света на то, что мне нужно.
Я сомневаюсь, что вы получите конкретную информацию, поскольку рассматриваемый код не является открытым исходным кодом.
Еще один замечательный момент, но я надеялся, что кто-то может знать или проводить подобное исследование в любом случае. Ну что ж, выстрел стоил! :)
Я тоже хотел бы это знать. Я заметил, что после вызова performSelectorOnMainThread из основного потока NSThread считает мое приложение многопоточным. Странно, а?





документация для метода performSelectorOnMainThread:withObject:waitUntilDone: NSObject говорит:
This method queues the message on the run loop of the main thread using the default run loop modes—that is, the modes associated with the NSRunLoopCommonModes constant. As part of its normal run loop processing, the main thread dequeues the message (assuming it is running in one of the default run loop modes) and invokes the desired method.
Еще одно редактирование:
Чтобы ответить на вопрос комментария:
what IPC mechanism is being used to pass info between threads? Shared memory? Sockets? Mach messaging?
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, такой как это, который срабатывает только один раз, и время срабатывания которого уже находится в прошлом (поэтому запускается немедленно), затем получает NSRunLoop основного потока и добавляет туда объект таймера. Как только основной поток переходит на праздный, он ищет в своем цикле выполнения следующее событие для обработки (или переходит в спящий режим, если нечего обрабатывать и снова просыпается, как только событие добавлено) и выполняет его. Либо основной поток занят, когда вы планируете вызов, и в этом случае он будет обрабатывать событие таймера, как только он завершит свою текущую задачу, либо он в данный момент спит, и в этом случае он будет разбужен добавлением события и обрабатывает его немедленно.
Хороший источник, чтобы узнать, как Apple представляет собой скорее всего делаю это (никто не может сказать наверняка, поскольку все-таки его исходный код закрыт), это GNUStep. Поскольку GCC может обрабатывать Objective-C (это не просто расширение, которое поставляет только Apple, с ним может справиться даже стандартный GCC), однако, наличие Obj-C без всех базовых классов, поставляемых Apple, довольно бесполезно, сообщество GNU попыталось повторно -реализуйте наиболее распространенные классы Obj-C, которые вы используете на Mac, и их реализация - OpenSource.
Здесь вы можете загрузить последний пакет с исходным кодом.
Распакуйте это и посмотрите на реализацию NSThread, NSObject и NSTimer для подробностей. Думаю, Apple не сильно отличается от этого, я, вероятно, мог бы доказать это с помощью gdb, но почему они сделали это иначе, чем этот подход? Это умный подход, который очень хорошо работает :)
Большое спасибо за согласованные усилия Mecki, и я комментирую, чтобы вы знали, почему я не помечаю ваш пост как правильный ответ: вопрос не в таймерах и селекторах; какой механизм IPC используется для передачи информации между потоками? Общая память? Розетки? Обмен сообщениями Mach?
И хотя GNUStep в целом является отличным ресурсом, он здесь не помогает, потому что он, по сути, написан поверх того, что выглядит как API POSIX, который в OS X реализован поверх подсистем более низкого уровня, таких как Mach. Как говорит Крис Хэнсон, ответ неуловим, потому что это, скорее всего, с закрытым исходным кодом.
На самом деле Mac OS - одна из немногих, действительно совместимых с POSIX систем, и предположение, что Apple не использует вызов POSIX, если он есть, а вместо этого использует какой-то неясный вызов Mach, - это чистая спекуляция. Фактически использование DTrace на Leopard показывает, что Obj-C использует множество вызовов POSIX.
Как сказал Меки, более общий механизм, который можно использовать для реализации -performSelectorOn…, - это NSTimer.
NSTimer является бесплатным мостом для подключения к CFRunLoopTimer. Реализацию CFRunLoopTimer - хотя не обязательно тот, который фактически используется для обычных процессов в OS X - можно найти в CFLite (подмножество CoreFoundation с открытым исходным кодом; пакет CF-476.14 в Исходный код Darwin 9.4. (CF-476.15, соответствующий OS X 10.5). .5, пока недоступен.)
Да, он использует порты Mach. Происходит вот что:
@synchronized, который в конечном итоге использует pthread_mutex_lock.Из документов Apple:
Version 1 sources are managed by the run loop and kernel. These sources use Mach ports to signal when the sources are ready to fire. A source is automatically signaled by the kernel when a message arrives on the source’s Mach port. The contents of the message are given to the source to process when the source is fired. The run loop sources for CFMachPort and CFMessagePort are currently implemented as version 1 sources.
Я сейчас смотрю на трассировку стека, и вот что она показывает:
0 mach_msg
1 CFRunLoopWakeUp
2 -[NSThread _nq:]
3 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:]
4 -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:]
Установите точку останова на mach_msg, и вы сможете ее подтвердить.
Было бы полезно, если бы вы объяснили, почему считаете, что вам нужно знать точный используемый механизм. Если вы хотите или чувствуете, что должны полагаться на него в коде уровня приложения, велика вероятность, что вы ошиблись ...