8

想知道是否有人知道或有指向讨论 Cocoa 的“performSelectorOnMainThread:”方法的低级实现细节的好文档的指针。

我最好的猜测,我认为可能非常接近的猜测是,它使用 mach 端口或在它们之上的抽象来提供线程内通信,将选择器信息作为 mach 消息的一部分传递。

对?错误的?谢谢!

更新 09:39AMPST

谢谢 Evan DiBiase 和 Mecki 的回答,但要澄清一下:我了解运行循环中发生的情况,但我正在寻找的答案是;“方法在哪里排队?选择器信息如何传递到队列中?” 寻找的不仅仅是 Apple 的文档信息:我读过它们

太平洋标准时间 14:21 更新

Chris Hanson 在评论中提出了一个很好的观点:我的目标不是为了在我自己的代码中利用它们而学习底层机制。相反,我只是对更好地理解向另一个线程发出信号以执行代码的过程的概念性理解感兴趣。正如我所说,我自己的研究使我相信它利用了 IPC 的 mach 消息传递在线程之间传递选择器信息,但我专门寻找关于正在发生的事情的具体信息,所以我可以确定我理解事情正确。谢谢!

2009 年 3 月 6 日更新

我在这个问题上开了一个赏金,因为我真的很想看到它得到回答,但如果你想收集,请确保你阅读了所有内容,包括所有当前提出的答案,对这些答案和我的原始问题的评论,以及我在上面发布的更新文本。我正在寻找by 等使用的机制的最低级别细节performSelectorOnMainThread:,正如我之前提到的,我怀疑它与 Mach 端口有关,但我真的很想知道。除非我能确认给出的答案是正确的,否则不会授予赏金。谢谢大家!

4

4 回答 4

10

Yes, it does use Mach ports. What happens is this:

  1. A block of data encapsulating the perform info (the target object, the selector, the optional object argument to the selector, etc.) is enqueued in the thread's run loop info. This is done using @synchronized, which ultimately uses pthread_mutex_lock.
  2. CFRunLoopSourceSignal is called to signal that the source is ready to fire.
  3. CFRunLoopWakeUp is called to let the main thread's run loop know it's time to wake up. This is done using mach_msg.

From the Apple docs:

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.

I'm looking at a stack trace right now, and this is what it shows:

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

Set a breakpoint on mach_msg and you'll be able to confirm it.

于 2009-03-06T20:30:09.390 回答
2

NSObjectperformSelectorOnMainThread:withObject:waitUntilDone:方法的文档说:

此方法使用默认的运行循环模式(即与NSRunLoopCommonModes常量关联的模式)在主线程的运行循环上对消息进行排队。作为其正常运行循环处理的一部分,主线程将消息出列(假设它以默认运行循环模式之一运行)并调用所需的方法。

于 2008-09-29T16:21:12.000 回答
2

又一编辑:

要回答评论的问题:

使用什么 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 将只返回一个计时器,如果计时器应该已经触发。如果每个计时器的触发日期仍在未来,并且没有其他要处理的事件(如键盘、来自 UI 的鼠标事件或发送的通知),它将返回 NULL。


我仍然不确定问题是什么。选择器只不过是一个包含被调用方法名称的 C 字符串。每个方法都是一个普通的 C 函数,并且存在一个字符串表,其中包含方法名称作为字符串和函数指针。这就是 Objective-C 实际工作的基本原理。

正如我在下面写的,创建了一个 NSTimer 对象,它获取一个指向目标对象的指针和一个指向包含方法名称的 C 字符串的指针,当计时器触发时,它会使用字符串表找到要调用的正确 C 方法(因此它需要目标对象的方法的字符串名称)(因此它需要对它的引用)。

不完全是实现,但非常接近它:

Cocoa 中的每个线程都有一个 NSRunLoop(它总是在那里,你永远不需要为线程创建)。PerformSelectorOnMainThread 创建一个像这样的 NSTimer 对象,它只触发一次并且触发时间已经位于过去(因此它需要立即触发),然后获取主线程的 NSRunLoop 并在那里添加计时器对象。一旦主线程空闲,它会在其 Runloop 中搜索下一个要处理的事件(如果没有要处理的内容,则进入睡眠状态,并在添加事件后立即再次唤醒)并执行它。调度调用时主线程正忙,在这种情况下,它会在完成当前任务后立即处理计时器事件,或者此时它正在休眠,在这种情况下,它将通过添加事件来唤醒并立即处理。

了解 Apple最有可能如何做到这一点的一个很好的来源(没有人可以肯定地说,毕竟它是封闭的源代码)是 GNUStep。由于 GCC 可以处理 Objective-C(它不仅仅是 Apple 提供的扩展,甚至标准的 GCC 也可以处理),然而,没有 Apple 提供的所有基本类的 Obj-C 是相当无用的,GNU 社区试图重新- 实现您在 Mac 上使用的最常见的 Obj-C 类,它们的实现是开源的。

在这里您可以下载最近的源包。

将其解压缩并查看 NSThread、NSObject 和 NSTimer 的实现以了解详细信息。我猜 Apple 的做法并没有太大的不同,我可能可以使用 gdb 来证明这一点,但为什么他们的做法与这种方法有很大不同呢?这是一种非常有效的聪明方法:)

于 2008-09-29T16:27:13.373 回答
0
于 2008-09-29T23:20:33.037 回答