4
votes

So with some help, I am more clear on how a nested GCD works in my program.

The original post is at:

Making sure I'm explaining nested GCD correctly

However, you don't need to go through the original post, but basically the code here runs database execution in the background and the UI is responsive:

-(void)viewDidLoad {

 dispatch_queue_t concurrencyQueue = dispatch_queue_create("com.epam.halo.queue", DISPATCH_QUEUE_CONCURRENT);
 dispatch_queue_t serialQueue = dispatch_queue_create("com.epam.halo.queue2", DISPATCH_QUEUE_SERIAL);

  for ( int i = 0; i < 10; i++) {

    dispatch_async(concurrencyQueue, ^() {

        NSLog(@"START insertion method%d <--", i);

        dispatch_sync(serialQueue, ^() {
            //this is to simulate writing to database
            NSLog(@"----------START %d---------", i);
            [NSThread sleepForTimeInterval:1.0f];
            NSLog(@"--------FINISHED %d--------", i);

        });

        NSLog(@"END insertion method%d <--", i);
    });
  }
}

However, when I start refactoring them and putting them into methods and making everything look nice, the UI does not respond anymore:

//some database singleton class

//the serial queues are declared in the class's private extension. And created in init()

-(void)executeDatabaseStuff:(int)i {
   dispatch_sync(serialQueue, ^() {
       //this is to simulate writing to database
       NSLog(@"----------START--------- %d", i);
       [NSThread sleepForTimeInterval:1.0f];
       NSLog(@"--------FINISHED-------- %d", i);
   });
}

-(void)testInsert:(int)i {
    dispatch_async(concurrencyQueue, ^() {
        [self executeDatabaseStuff:i];
    });
}

//ViewController.m

- (void)viewDidLoad {

    //UI is unresponsive :(
    for ( int i = 0; i < totalNumberOfPortfolios; i++) {

        NSLog(@"START insertion method%d <--", i);
        [[DatabaseFunctions sharedDatabaseFunctions] testInsert: i];
        NSLog(@"END insertion method%d <--", i);
    }
}

The only way to make the refactored version work is when I put dispatch_async(dispatch_get_main_queue():

for ( int i = 0; i < totalNumberOfPortfolios; i++) {

    dispatch_async(dispatch_get_main_queue(), ^() {

        NSLog(@"START insertion method%d <--", i);
        [[DatabaseFunctions sharedDatabaseFunctions] testInsert: i];
        NSLog(@"END insertion method%d <--", i);
    });
}

So my question is, I thought the fact that I use dispatch_async the concurrencyQueue will ensure that my main thread is not touched by the dispatch_sync serialQueue combo. Why is it that when I wrap it in an object/method, I must use dispatch_async(dispatch_get_main_queue()...) ?

Seems that whether my main thread does dispatch_async on a concurrent queue in viewDidLoad, or within a method, does indeed matter.

I am thinking that the main thread is getting all these testInsert methods pushed onto its thread stack. The methods must then be processed by the main thread. Hence, even though the dispatch_sync is not blocking the main thread, the main thread runs to the end of viewDidLoad, and must wait for all the testInsert methods to be processed and done before it can move onto the next task in the Main Queue??

Notes

So I went home and tested it again with this:

for ( int i = 0; i < 80; i++) {

        NSLog(@"main thread %d <-- ", i);

        dispatch_async(concurrencyQueue, ^() {

            [NSThread isMainThread] ? NSLog(@"its the main thread") : NSLog(@"not main thread");

            NSLog(@"concurrent Q thread %i <--", i);

            dispatch_sync(serialQueue, ^() {

                //this is to simulate writing to database
                NSLog(@"serial Q thread ----------START %d---------", i);
                [NSThread sleepForTimeInterval:1.0f];
                NSLog(@"serial Q thread --------FINISHED %d--------", i);

            });

            NSLog(@"concurrent Q thread %i -->", i);

        });

        NSLog(@"main thread %d --> ", i);
    } //for loop

When I run the loop from 1 - 63, the UI is not blocked. And I see my database operation processing in the background.

Then when the loop is 64, UI is blocked for 1 database operation, then returns fine.

When I use 65, UI freezes for 2 database operations, then returns fine...

When I use something like 80, it gets blocked from 64-80...so I wait 16 seconds before my UI is responsive.

At the time, I couldn't figure out why 64. So now I know that its 64 concurrent threads allowed at once. ...and has nothing to do with wrapping it in a object/method. :D

Many thanks for the awesome help from the contributors!

1
Could you please post the [DatabaseFunctions sharedDatabaseFunctions] method? And the method where you create the queues, if you don't mind.FreeNickname
Is using an NSOperationQueue a suitable approach for you?FreeNickname
NSOperationQueue is great ...but I just want to familiarize myself with GCD before I move on to NSOperationQueue.rtsao

1 Answers

3
votes

There is a hard limit of 64 GCD concurrent operations (per top level concurrent queue) that can be run together.

What's happening is you're submitting over 64 blocks to your concurrent queue, each of them getting blocked by the [NSThread sleepForTimeInterval:1.0f], forcing a new thread to be created for each operation. Therefore, once the thread limit is reached, it backs up and starts to block the main thread.

I have tested this with 100 "database write" operations (on device), and the main thread appears to be blocked until 36 operations have taken place (there are now only 64 operations left, therefore the main thread is now un-blocked).

The use of a singleton shouldn't cause you any problems, as you're calling methods to that synchronously, therefore there shouldn't be thread conflicts.

The simplest solution to this is just to use a single background serial queue for your "database write" operations. This way, only one thread is being created to handle the operation.

- (void)viewDidLoad {
    [super viewDidLoad];
 
    static dispatch_once_t t;
    
    dispatch_once(&t, ^{
        serialQueue = dispatch_queue_create("com.epam.halo.queue2", DISPATCH_QUEUE_SERIAL);
    });

    
    for (int i = 0; i < 100; i++) {
        [self testInsert:i];
    }
    
}

-(void)executeDatabaseStuff:(int)i {
    //this is to simulate writing to database
    NSLog(@"----------START--------- %d", i);
    [NSThread sleepForTimeInterval:1.0f];
    NSLog(@"--------FINISHED-------- %d", i);
}

-(void)testInsert:(int)i {
    NSLog(@"Start insert.... %d", i);
    dispatch_async(serialQueue, ^() {
        [self executeDatabaseStuff:i];
    });
    NSLog(@"End insert... %d", i);
}

I don't know why inserting dispatch_async(dispatch_get_main_queue(), ^() {} inside your for loop was working for you... I can only assume it was off-loading the "database writing" until after the interface had loaded.

Further resources on threads & GCD