pluck
pluck

Reputation: 179

How to create concurrent queues properly with three requests at the same time

I want to get three requests at the same time to improve performance. Here my current code, it returns only one platform at my tableview:

 dispatch_queue_t concurrentQueue = dispatch_queue_create("com.myapp.myqueue", DISPATCH_QUEUE_CONCURRENT);

void(^block_readPS4)() = ^{
    self.releasesDict = [NSDictionary dictionaryWithDictionary:[self getDataWithPlatform:@"ps4"]];
};
void(^block_readXONE)() = ^{
    self.releasesDict = [NSDictionary dictionaryWithDictionary:[self getDataWithPlatform:@"xboxone"]];
};
void(^block_readPC)() = ^{
    self.releasesDict = [NSDictionary dictionaryWithDictionary:[self getDataWithPlatform:@"pc"]];
};
void(^block_write)() = ^{dictionaryWithDictionary:self.releasesDict];
    self.releasesArr = [self.releasesDict allKeys];
    [self.tableView reloadData];
    [self.activityInd stopAnimating];
};
dispatch_async(concurrentQueue,block_readPS4);
dispatch_async(concurrentQueue,block_readXONE);
dispatch_async(concurrentQueue,block_readPC);

dispatch_barrier_async(concurrentQueue, block_write);

I know problem is write in self.releasesDict, how it could be improved?

Upvotes: 0

Views: 135

Answers (2)

ipmcc
ipmcc

Reputation: 29926

@ryancrunchi is correct:

You're overwriting self.releasesDict in each read. So in your write block the value of self.releasesDict will be the last read that the system performed.

...but his proposed solution doesn't protect the dictionary from concurrent writes, and NSMutableDictionary is not intrinsically thread-safe. You must protect it from concurrent reads + writes and/or writes + writes. Here is one way you might do that (with a detailed walkthrough of everything being done in the comments):

// A local (non-shared) mutable dictionary. Using a local dictionary allows us to know that no one
// else is reading from or writing to it concurrently, which is not guaranteed if we use self.releasesDict
NSMutableDictionary* localDict = [NSMutableDictionary dictionary];

// Your original concurrent queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.myapp.myqueue", DISPATCH_QUEUE_CONCURRENT);

// A serial queue to protect localDict from reading and writing.
dispatch_queue_t localDictWriteQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);

// Make this queue target the concurrent queue. Not strictly required, but you had everything executing on
// concurrentQueue before, and this preserves that, while also protecting localDict.
dispatch_set_target_queue(localDictWriteQueue, concurrentQueue);

// A dispatch group that allows us to perform an operation only after all the constituent writes have finished.
dispatch_group_t group = dispatch_group_create();

// For each platform, enter the group, then fire off the concurrent block
dispatch_group_enter(group);
dispatch_async(concurrentQueue, ^{
    // Fetch into a dictionary that is local to the block.
    NSDictionary* x = [self getDataWithPlatform:@"ps4"];

    // Then, on the serial localDictWriteQueue merge those entries into the shared local dictionary
    dispatch_async(localDictWriteQueue, ^{
        [localDict addEntriesFromDictionary: x];
        // This balances out the dispatch_group_enter operation we did right before we enqueued this
        dispatch_group_leave(group);
    });
});

// Second verse, same as the first
dispatch_group_enter(group);
dispatch_async(concurrentQueue, ^{
    NSDictionary* x = [self getDataWithPlatform:@"xboxone"];
    dispatch_async(localDictWriteQueue, ^{
        [localDict addEntriesFromDictionary: x];
        dispatch_group_leave(group);
    });
});

// Third verse, same as the first
dispatch_group_enter(group);
dispatch_async(concurrentQueue, ^{
    NSDictionary* x = [self getDataWithPlatform:@"pc"];
    dispatch_async(localDictWriteQueue, ^{
        [localDict addEntriesFromDictionary: x];
        dispatch_group_leave(group);
    });
});

// Then set up the block we want to run at the end... use main queue because it updates UI.
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // Update self.releasesDict all at once, on the main thread
    // Note: we do not need to do this read of localDict on localDictWriteQueue because if this block is executing, we know (semantically) that all possible
    // write operations have already completed, and that no other writes to localDict are possible because it's local to this method call.
    self.releasesDict = localDict;
    // Same idea
    self.releasesArr = [self.releasesDict allKeys];

    // Update UI based on state changes to self.
    [self.tableView reloadData];
    [self.activityInd stopAnimating];
});

Upvotes: 4

ryancrunchi
ryancrunchi

Reputation: 475

You're overwriting self.releasesDict in each read. So in your write block the value of self.releasesDict will be the last read that the system performed. If you want all read in same NSDictionary declare self.releasesDict as a NSMutableDictionary and init it with :

self.releasesDict = [NSMutableDictionary dictionary];

and in your reads :

[self.releasesDict addEntriesFromDictionary:[NSDictionary dictionaryWithDictionary:/*what you want*/]];

Upvotes: 1

Related Questions