1

I have two methods that I need to run, lets call them metA and metB.

When I start coding this app, I called both methods without using threads, but the app started freezing, so I decided to go with threads.

metA and metB are called by touch events, so they can occur any time in any order. They don't depend on each other.

My problem is the time it takes to either threads start running. There's a lag between the time the thread is created with

[NSThread detachNewThreadSelector:@selector(.... bla bla

and the time the thread starts running.

I suppose this time is related to the amount of time required by iOS to create the thread itself. How can I speed this? If I pre create both threads, how do I make them just do their stuff when needed and never terminate? I mean, a kind of sleeping thread that is always alive and works when asked and sleeps after that?

thanks.

Duck
  • 34,902
  • 47
  • 248
  • 470
  • How much of a lag are you seeing? – hotpaw2 Nov 26 '10 at 21:49
  • About 0.18 seconds. A regular method called directly gives me 0.07. I am measuring the time it takes to run the first line of the method. Less than half the time. – Duck Nov 26 '10 at 21:57

2 Answers2

3

If you want to avoid the expensive startup time of creating new threads, create both threads at startup as you suggested. To have them only run when needed, you can have them wait on a condition variable. Since you're using the NSThread class for threading, I'd recommend using the NSCondition class for condition variables (an alternative would be to use the POSIX threading (pthread) condition variables, pthread_cond_t).

One thing you'll have to be careful of is if you get another touch event while the thread is still running. In that case, I'd recommend using a queue to keep track of work items, and then the touch event handler can just add the work item to the queue, and the worker thread can process them as long as the queue is not empty.

Here's one way to do this:

typedef struct WorkItem
{
    // information about the work item
    ...
    struct WorkItem *next;  // linked list of work items
} WorkItem;

WorkItem *workQueue = NULL;  // head of linked list of work items
WorkItem *workQueueTail = NULL;  // tail of linked list of work items
NSCondition *workCondition = NULL;  // condition variable for the queue

...

-(id) init
{
    if((self = [super init]))
    {
        // Make sure this gets initialized before the worker thread starts
        // running
        workCondition = [[NSCondition alloc] init];

        // Start the worker thread
       [NSThread detachNewThreadSelector:@selector(threadProc:)
                                         toTarget:self withObject:nil];
    }

    return self;
}

// Suppose this function gets called whenever we receive an appropriate touch
// event
-(void) onTouch
{
    // Construct a new work item.  Note that this must be allocated on the
    // heap (*not* the stack) so that it doesn't get destroyed before the
    // worker thread has a chance to work on it.
    WorkItem *workItem = (WorkItem *)malloc(sizeof(WorkItem));

    // fill out the relevant info about the work that needs to get done here
    ...
    workItem->next = NULL;

    // Lock the mutex & add the work item to the tail of the queue (we
    // maintain that the following invariant is always true:
    // (workQueueTail == NULL || workQueueTail->next == NULL)
    [workCondition lock];
    if(workQueueTail != NULL)
        workQueueTail->next = workItem;
    else
        workQueue = workItem;
    workQueueTail = workItem;
    [workCondition unlock];

    // Finally, signal the condition variable to wake up the worker thread
    [workCondition signal];
}

-(void) threadProc:(id)arg
{
    // Loop & wait for work to arrive.  Note that the condition variable must
    // be locked before it can be waited on.  You may also want to add
    // another variable that gets checked every iteration so this thread can
    // exit gracefully if need be.
    while(1)
    {
        [workCondition lock];
        while(workQueue == NULL)
        {
            [workCondition wait];
            // The work queue should have something in it, but there are rare
            // edge cases that can cause spurious signals.  So double-check
            // that it's not empty.
        }

        // Dequeue the work item & unlock the mutex so we don't block the
        // main thread more than we have to
        WorkItem *workItem = workQueue;
        workQueue = workQueue->next;
        if(workQueue == NULL)
            workQueueTail = NULL;
        [workCondition unlock];

        // Process the work item here
        ...
        free(workItem);  // don't leak memory
    }
}
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
1

If you can target iOS4 and higher, consider using blocks with Grand Central Dispatch asynch queue, which operates on background threads which the queue manages... or for backwards compatibility, as mentioned use NSOperations inside an NSOperation queue to have bits of work performed for you in the background. You can specify exactly how many background threads you want to support with an NSOperationQueue if both operations have to run at the same time.

Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150