metsburg
metsburg

Reputation: 2021

iOS multithreading synchronization

I am building an iOS app which does some heavy lifting on a background thread.

I create my thread using

  dispatch_queue_t backgroundQueue;
  backgroundQueue =  dispatch_queue_create("MyQueue", NULL);

and then put it into GCD using:

   dispatch_async(backgroundQueue, ^
  {

   //some heavy operations here
   //each of them might run for >1 sec

  }

I just want a sequential execution of the queue, provided it doesn't block the main thread. If this block is called from method 3, method 2 and method 1 within 20ms.... they should be necessarily executed in the order 3 -> 2 -> 1, since the output of each method is used up by the next.

I am new to GCD and naively imagined a dispatch queue would use a FIFO queue to execute sequential calls. However, in its current implementation, it is far from sequential.

I tried using NSLock, but they didn't help either.

I would like to know the proper mechanism for enforcing sequential execution in GCD.

EDIT 1:

I am declaring a global queue:

 dispatch_queue_t backgroundQueue ;

and initiating it in viewDidLoad() :

 backgroundQueue = dispatch_queue_create("MyQueue", DISPATCH_QUEUE_SERIAL);
 //using DISPATCH_QUEUE_SERIAL was a new idea, I also tried using NULL

I'm using GCD to basically call method in another class:

 -(void)doAction()
  {

   dispatch_async(backgroundQueue, ^
        {
                   MyOtherClass *obj = [[MyOtherClass alloc] init];
                   [obj heavyMethod1:  param1 : param2];
                   [obj release];
               });

        }

 -(void)doAnotherAction()
  {

   dispatch_async(backgroundQueue, ^
        {
                   MyOtherClass *obj = [[MyOtherClass alloc] init];
                   [obj heavyMethod2:  param3 : param4];
                   [obj release];
               });

        }

Now, doAction and doAnotherAction method are being called from a number of other methods, depending upon the scenario.

What I'm seeing here is - if I'm calling these methods in the sequence :: doAction -> doAction -> doAnotherAction -> doAction

...I'm getting an output in a sequence like :: doAction -> doAnotherAction -> doAction -> doAction

How do I maintain its sequence of invokation and keep it serial at the same time ?

P.S: - If I remove GCD and allow things to carry on in the main thread, it is theoretically running fine - in simulator. (It crashes in iPhone.)

Upvotes: 1

Views: 1918

Answers (3)

German Saprykin
German Saprykin

Reputation: 6961

Your code is correct. Look at example:

dispatch_queue_t backgroundQueue;
backgroundQueue =  dispatch_queue_create("SerialQueue", NULL);

for (int i = 0; i < 10; i++){
    dispatch_async(backgroundQueue, ^{
        int sleepTime = rand()%5;
        sleep(sleepTime);
        NSLog(@"SerialQueue: task %d. sleepTime %d",i,sleepTime);
    });
}

dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 10; i++){
    dispatch_async(aQueue, ^{
        int sleepTime = rand()%5;
        sleep(sleepTime);
        NSLog(@"ConcurrentQueue task %d. sleepTime %d",i,sleepTime);
    });
}

EDIT What are "heavy" methods? Are the synchronous or async? Try such example:

- (void)makeTest{
    [self doActionId: 1];
    [self doActionId: 2];
    [self doAnotherActionId: 3];
    [self doActionId: 4];
}


- (void)doActionId: (NSInteger) id{
    dispatch_async(backgroundQueue, ^{
        int sleepTime = rand()%5;
        sleep(sleepTime);
        NSLog(@"doAction - %d.Sleep time %d", id, sleepTime);
    });
}
- (void)doAnotherActionId:(NSInteger) id{
    dispatch_async(backgroundQueue, ^{
        int sleepTime = rand()%5;
        sleep(sleepTime);
        NSLog(@"doAnotherAction %d. Sleep time %d", id, sleepTime);
    });
}

Upvotes: 1

jkh
jkh

Reputation: 3266

There seems to be a lot of confusion in this thread - to try and mitigate that, let me first state some basic facts about GCD:

  1. Serial queues always execute items serially and non-concurrently (with items on the same queue). There is no way to even make GCD "take items from wherever it wants" in a serial queue, so that seems to be some confusion right there.

  2. Concurrent queues can execute blocks submitted to them in any order and on any number of concurrent threads. Unless you are creating a queue as a concurrent queue or using one of the global concurrent queues, you won't get this behavior.

  3. dispatch_async always returns immediately regardless of the type of queue it is posting work to, so it is quite possible to get work interleaved in non-predictable order if you have multiple methods posting work to the same serial queue and those methods are called from multiple places in your code! You have doAction and doAnotherAction methods, for example, but it's not clear where they're being invoked and since each will likely return more or less immediately, you could easily see a doAction/doAction/doAnotherAction chain of submissions.

If the desire is to do multiple things concurrently but each in a predictable order, you should create a serial queue for each "thing" - this is how exclusive access is often maintained (without locks) to critical resources - each resource has its own associated serial queue.

Does that make more sense?

Upvotes: 5

Maarten
Maarten

Reputation: 1873

The docs provide the following answer:

Serial queues are useful when you want your tasks to execute in a specific order. A serial queue executes only one task at a time and always pulls tasks from the head of the queue. You might use a serial queue instead of a lock to protect a shared resource or mutable data structure. Unlike a lock, a serial queue ensures that tasks are executed in a predictable order.

...

    dispatch_queue_t queue;
    queue = dispatch_queue_create("com.example.MyQueue", NULL);

So you're doing the right thing in principle. You're probably spinning off each block on a separate thread. That won't work, because each separate serial queue will run concurrently. Just submit all the work onto the same queue and you should be golden.

Edit I:

Well, I'm stumped as far as GCD is concerned. Maybe you can try the NSOperation family of classes? You can create NSBlockOperation and add it to an NSOperationQueue, as described in this doc. If you create a second (or third etc.) NSBlockOperation, you can do [anotherBlock addDependency:[yourNSOperationQueue.operations lastObject]] and that should make anotherBlock wait for the operation you last added to yourNSOperationQueue.

For reference (source):

To establish dependencies between two operation objects, you use the addDependency: method of NSOperation. This method creates a one-way dependency from the current operation object to the target operation you specify as a parameter. This dependency means that the current object cannot begin executing until the target object finishes executing.

Upvotes: 1

Related Questions