Using NSThreads in command-line apps

  • Hi all,

    I have a bit of a dilemma. I'm working on a Foundation-based command-
    line utility that needs to manage a few threads. The obvious choice is
    NSThread, since it's nice and clean.

    Actually, I'm able to spawn new threads perfectly well. The problem is
    that I can't (?) use performSelectorOnMainThread: because I'm not an
    NSApplication, and so I don't get the default "main" thread that loops
    for user input...

    Am I busted? I would like to be able to run this app without the
    window server running (ie, in Single User Mode), because it's server
    software.

    I can always fall back to POSIX threads, but I'd rather use NSThread
    if possible. Is there an alternate way to have my spawned NSThreads
    call back to a controller thread (that doesn't require 10.5)?

    Thanks!

    - ben
  • On May 15, 2008, at 11:42 AM, ben syverson wrote:
    > Actually, I'm able to spawn new threads perfectly well. The problem
    > is that I can't (?) use performSelectorOnMainThread: because I'm not
    > an NSApplication, and so I don't get the default "main" thread that
    > loops for user input...
    >
    > Am I busted? I would like to be able to run this app without the
    > window server running (ie, in Single User Mode), because it's server
    > software.
    >
    > I can always fall back to POSIX threads, but I'd rather use NSThread
    > if possible. Is there an alternate way to have my spawned NSThreads
    > call back to a controller thread (that doesn't require 10.5)?

    Run an NSRunLoop on your main thread.  That'll support -
    performSelectorOnMainThread:.

    b.bum
  • On May 15, 2008, at 12:42 PM, ben syverson wrote:

    > I have a bit of a dilemma. I'm working on a Foundation-based command-
    > line utility that needs to manage a few threads. The obvious choice
    > is NSThread, since it's nice and clean.
    >
    > Actually, I'm able to spawn new threads perfectly well. The problem
    > is that I can't (?) use performSelectorOnMainThread: because I'm not
    > an NSApplication, and so I don't get the default "main" thread that
    > loops for user input...
    >
    > Am I busted? I would like to be able to run this app without the
    > window server running (ie, in Single User Mode), because it's server
    > software.
    >
    > I can always fall back to POSIX threads, but I'd rather use NSThread
    > if possible. Is there an alternate way to have my spawned NSThreads
    > call back to a controller thread (that doesn't require 10.5)?

    Can't you cache the main thread [NSThread +currentThread] when you
    start up, and then use NSObject's -
    performSelector:onThread:withObject:waitUntilDone: method?
  • Am 15.05.2008 um 20:42 schrieb ben syverson:
    > Actually, I'm able to spawn new threads perfectly well. The problem
    > is that I can't (?) use performSelectorOnMainThread: because I'm not
    > an NSApplication, and so I don't get the default "main" thread that
    > loops for user input...

      Check out NSRunLoop. I think if you create one of those at startup
    and run it, that will become your main loop and will take care of
    performSelectorOnMainThread: and the likes.

    Cheers,
    -- Uli Kusterer
    "The Witnesses of TeachText are everywhere..."
    http://www.zathras.de
  • On 15 May '08, at 11:42 AM, ben syverson wrote:

    > Actually, I'm able to spawn new threads perfectly well. The problem
    > is that I can't (?) use performSelectorOnMainThread: because I'm not
    > an NSApplication, and so I don't get the default "main" thread that
    > loops for user input...

    You don't need an NSApplication; all you need is an NSRunLoop on the
    main thread.

    To get one, you should structure your main thread's code like
    ... initialization ...
    [[NSRunLoop currentRunLoop] run];
    ... teardown ...

    The runloop call will cause the thread to block and repeatedly wait
    for and then handle events, until there are no more runloop sources.
    Runloop sources are things like NSTimers and the various -
    performSelector... utilities declared in NSRunLoop and NSThread
    ("after delay", "on main thread", etc.)

    If you know exactly when to quit and don't want to deal with removing
    all runloop sources, you can just call exit(0) at the point where you
    want to exit (after any necessary cleanup.)

    —Jens
  • On May 15, 2008, at 1:47 PM, Bill Bumgarner wrote:
    > Run an NSRunLoop on your main thread.  That'll support -
    > performSelectorOnMainThread:.

    Okay -- interesting. One follow-up question... my core loop is
    basically:

    while(_running) {
    // do stuff
    }

    I don't want to listen to any input, and I want the loop to execute as
    fast as possible. However, NSRunLoop says I need to set a timer or an
    input...

    - ben
  • On May 15, 2008, at 1:49 PM, Randall Meadows wrote:

    > Can't you cache the main thread [NSThread +currentThread] when you
    > start up, and then use NSObject's -
    > performSelector:onThread:withObject:waitUntilDone: method?

    Yes, that would be ideal! Unfortunately that method is 10.5 only...

    - ben
  • On May 15, 2008, at 12:16 PM, ben syverson wrote:
    > On May 15, 2008, at 1:47 PM, Bill Bumgarner wrote:
    >> Run an NSRunLoop on your main thread.  That'll support -
    >> performSelectorOnMainThread:.
    >
    > Okay -- interesting. One follow-up question... my core loop is
    > basically:
    >
    > while(_running) {
    > // do stuff
    > }
    >
    > I don't want to listen to any input, and I want the loop to execute
    > as fast as possible. However, NSRunLoop says I need to set a timer
    > or an input...

    Your main thread loop?

    If that is the design you want to use, then you can use NSLock --
    NSConditionLock, typically -- to do the synchronization between
    threads.  If your loop really is running flat out, then using a
    condition lock will be marginally faster.

    b.bum
  • On May 15, 2008, at 12:17 PM, ben syverson wrote:
    > On May 15, 2008, at 1:49 PM, Randall Meadows wrote:
    >> Can't you cache the main thread [NSThread +currentThread] when you
    >> start up, and then use NSObject's -
    >> performSelector:onThread:withObject:waitUntilDone: method?
    >
    > Yes, that would be ideal! Unfortunately that method is 10.5 only...

    And it'd require a run loop in the target thread...

    b.bum
  • On May 15, 2008, at 2:25 PM, Bill Bumgarner wrote:

    > Your main thread loop?
    >
    > If that is the design you want to use, then you can use NSLock --
    > NSConditionLock, typically -- to do the synchronization between
    > threads.  If your loop really is running flat out, then using a
    > condition lock will be marginally faster.
    >
    > b.bum

    Maybe... I should be more specific, so it's clear what I'm trying to
    do. The application is a lightweight HTTP server which reads requests
    on a socket and uses two or three worker threads to generate the
    responses. I'm using kqueue / kevent instead of the Cocoa stuff.

    I have one thread which runs the server's infinite loop. It can't be
    the main thread, because it calls kevent, which doesn't return a value
    until there's an event. So that thread's loop stops while kevent is
    waiting.

    When the server thread gets a complete request, it needs to be picked
    up by a worker thread. Perhaps the way to do this is with an
    NSConditionalLock? As in, the server thread would lock an
    NSMutableArray, add the new requests, and that would trigger the
    worker thread? If I go with that, I guess I wouldn't have to a
    performSelectorOnMainThread:...

    I want to go with whatever will give me the best performance. (I know,
    I know, ObjC adds some overhead, but the advantages in readability
    outweigh the messaging overhead for me. If I need to recode some parts
    as ANSI later, I will.)

    Thanks for all the responses so far!

    - ben
  • On 15 May '08, at 12:16 PM, ben syverson wrote:

    > I don't want to listen to any input, and I want the loop to execute
    > as fast as possible. However, NSRunLoop says I need to set a timer
    > or an input...

    If you're just running a loop of your own code (or blocking in a
    system call like kqueue) there's no way for -
    performSelectorOnMainThread to send an event to that thread. (It would
    have to use something asynchronous like a signal, which is really
    nasty to deal with.)

    NSRunLoop provides the outer-level loop for a thread, and
    (effectively) manages an event queue for it. To do that, it has to be
    in control of the thread. Foundation and AppKit are built on top of
    this model, using asynchrony instead of blocking, and generally not
    relying much on multi-threading.

    > I have one thread which runs the server's infinite loop. It can't be
    > the main thread, because it calls kevent, which doesn't return a
    > value until there's an event. So that thread's loop stops while
    > kevent is waiting.

    You can wire up kqueues to a runloop fairly easily. Instead of
    blocking, the kqueue will post an event to the runloop when something
    happens. Then instead of waiting in kevent, you just handle events in
    your runloop, and your callback will be invoked as necessary.

    I used this technique in PubSub; I believe I got the information from
    the "Advanced Mac OS X Programming" book.

    (My advice: unless you're seriously trying to write The Fastest Web
    Server In The World, use NSStream and/or CFNetwork and follow their
    asynchronous/runloop model.)

    —Jens
  • On May 15, 2008, at 4:17 PM, Jens Alfke wrote:

    > You can wire up kqueues to a runloop fairly easily. Instead of
    > blocking, the kqueue will post an event to the runloop when
    > something happens. Then instead of waiting in kevent, you just
    > handle events in your runloop, and your callback will be invoked as
    > necessary.

    That's interesting... Although I think for now, having kevent in a
    separate thread is working okay. When it gets a request, it locks a
    shared NSMutableArray queue and sets an NSConditionLock indicating
    that a worker thread should copy and clear the queue and then process
    the requests... It seems to be working very well.

    > (My advice: unless you're seriously trying to write The Fastest Web
    > Server In The World, use NSStream and/or CFNetwork and follow their
    > asynchronous/runloop model.)

    It's not that I'm trying to write The Fastest Web Server In The World,
    it's just that I'm trying to squeeze every dollar out of my server
    hardware. Besides, if I'm writing something from scratch, why not make
    it scream? :)

    - ben
  • On Thu, May 15, 2008 at 6:28 PM, ben syverson <ben...> wrote:

    Besides, if I'm writing something from scratch, why not make it scream? :)
    >

    Make it work first. Then Shark it until it screams. :-)

    sherm--

    --
    Cocoa programming in Perl: http://camelbones.sourceforge.net
  • On Thu, May 15, 2008 at 8:16 PM, ben syverson <ben...> wrote:

    > I don't want to listen to any input, and I want the loop to execute as fast
    > as possible. However, NSRunLoop says I need to set a timer or an input...

    On Thu, May 15, 2008 at 8:53 PM, ben syverson <ben...> wrote:

    > I have one thread which runs the server's infinite loop. It can't be the
    > main thread, because it calls kevent, which doesn't return a value until
    > there's an event. So that thread's loop stops while kevent is waiting.

    The event you're using kqueue to block for is the input to which
    NSRunLoop refers.

    NSRunLoop is just Cocoa's implementation of a kevent()-style loop.
    Instead of adding your socket to the kqueue, add it to the run loop
    instead. (See e.g. http://cocoadevcentral.com/articles/000039.php --
    which uses the CFRunLoop interface, but it's the same run loop.) No
    need for a separate thread to handle the main task.

    Hamish
  • On May 15, 2008, at 5:33 PM, Hamish Allan wrote:

    > NSRunLoop is just Cocoa's implementation of a kevent()-style loop.
    > Instead of adding your socket to the kqueue, add it to the run loop
    > instead. (See e.g. http://cocoadevcentral.com/articles/000039.php --
    > which uses the CFRunLoop interface, but it's the same run loop.) No
    > need for a separate thread to handle the main task.

    I know it's kevent "style," but unless it's actually kevent (or a
    similar kernel-level event system) under there, I have my doubts about
    the performance under heavy load. I guess I could (should) do
    benchmarks of both versions. But kqueue is working great so far, so
    I'll probably leave any benchmarking & Sharking until after the rest
    of development is done...

    Thanks to everyone for their help! It's been most... helpful. :)

    - ben
  • On Thu, May 15, 2008 at 11:40 PM, ben syverson <ben...> wrote:

    > I know it's kevent "style," but unless it's actually kevent (or a similar
    > kernel-level event system) under there, I have my doubts about the
    > performance under heavy load. I guess I could (should) do benchmarks of both
    > versions. But kqueue is working great so far, so I'll probably leave any
    > benchmarking & Sharking until after the rest of development is done...

    Well, if all you want to do is to set up your run loop so that it will
    run without any inputs or timers:

    NSPort *dummyPort = [[NSPort port] retain];
    [[NSRunLoop currentRunLoop] addPort:dummyPort forMode:@"NSDefaultRunLoopMode"];

    But why do you want to perform a selector on the "main" thread rather
    than on the thread with your main event loop?

    Hamish
  • On 15 May '08, at 3:40 PM, ben syverson wrote:

    > I know it's kevent "style," but unless it's actually kevent (or a
    > similar kernel-level event system) under there, I have my doubts
    > about the performance under heavy load.

    I don't think the difference will be noticeable, if indeed there is
    any, unless you're trying to write The World's Fastest Web Server.
    Usually the important factor is how many hours it takes you to write
    and debug the code, not how many microseconds the program takes to
    handle a request.

    The thing is, by running your own blocking loops, you're going against
    the grain of the way the Cocoa frameworks do things. It doesn't mean
    you can't do it that way, but you won't be able to use NSRunLoop or
    anything built on it on threads where you're doing that.

    —Jens
  • On May 15, 2008, at 8:02 PM, Jens Alfke wrote:

    > I don't think the difference will be noticeable, if indeed there is
    > any, unless you're trying to write The World's Fastest Web Server.
    > Usually the important factor is how many hours it takes you to write
    > and debug the code, not how many microseconds the program takes to
    > handle a request.

    Perhaps the difference will be small... I'll be sure to post here when
    I test it. A few microseconds here and there can really add up,
    though. :) Apache begins to choke once you get to even 100 connections/
    sec, which is why I'm not just doing this in mod_perl. CGI and FastCGI
    are out of the question (too much overhead), so I'm stuck rolling my
    own. Using kqueue is a way to be certain that request handling will
    not be the bottleneck in my code.

    > The thing is, by running your own blocking loops, you're going
    > against the grain of the way the Cocoa frameworks do things. It
    > doesn't mean you can't do it that way, but you won't be able to use
    > NSRunLoop or anything built on it on threads where you're doing that.

    I'm not sure I'm missing anything NSRunLoop has to offer. After
    reevaluating my code, I don't think I need to run methods on my server
    thread from the worker threads, so I don't need the
    performSelectorOnMainThread: method after all. I just use an
    NSConditionLock to run the worker threads when the request queue has
    items, and another lock to write them to an output queue...

    - ben
previous month may 2008 next month
MTWTFSS
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
Go to today