I've read a fair amount on thread-safety, and have been using GCD to keep the math-heavy code off the main thread for a while now (I learned about it before NSOperation, and it seems to still be the easier option). However, I wonder if I could improve part of my code that currently uses a lock.
I have an Objective-C++ class that is a wrapper for a c++ vector. (Reasons: primitive floats are added constantly without knowing a limit beforehand, the container must be contiguous, and the reason for using a vector vs NSMutableData is "just cause" it's what I settled on, and NSMutableData will still suffer from the same "expired" pointer when it goes to resize itself).
The class has instance methods to add data points that are processed and added to the vector (vector.push_back). After new data is added I need to analyze it (by a different object). That processing happens on a background thread, and it uses a pointer directly to the vector. Currently the wrapper has a getter method that will first lock the instance (it suspends a local serial queue for the writes) and then return the pointer. For those that don't know, this is done because when the vector runs out of space push_back causes the vector to move in memory to make room for the new entries - invalidating the pointer that was passed. Upon completion, the math-heavy code will call unlock on the wrapper, and the wrapper will resume the queued writes finish.
I don't see a way to pass the pointer along -for an unknown length of time- without using some type of lock or making a local copy -which would be prohibitively expensive.
Basically: Is there a better way to pass a primitive pointer to a vector (or NSMutableData, for those that are getting hung up by a vector), that while the pointer is being used, any additions to the vector are queued and then when the consumer of the pointer is done, automatically "unlock" the vector and process the write queue
Current Implementation
Classes:
DataArray
: a wrapper for a C++ vectorDataProcessor
: Takes the most raw data and cleans it up before sending it to the 'DataArray'DataAnalyzer
: Takes the 'DataArray' pointer and does analysis on arrayWorker
: owns and initializes all 3, it also coordinates the actions (it does other stuff as well that is beyond the scope here). it is also a delegate to the processor and analyzer
What happens:
Worker
is listening for new data from another class that handles external devices- When it receives a
NSNotification
with the data packet, it passes that ontoDataProcessor
by-(void)checkNewData:(NSArray*)data
DataProcessor
, working in a background thread cleans up the data (and keeps partial data) and then tellsDataArray
to-(void)addRawData:(float)data
(shown below)DataArray
then stores that data- When
DataProcessor
is done with the current chunk it tellsWorker
- When
Worker
is notified processing is done it tellsDataAnalyzer
to get started on the new data by-(void)analyzeAvailableData
DataAnalyzer
does some prep work, including askingDataArray
for the pointer by- (float*)dataPointer
(shown below)DataAnalyzer
does adispatch_async
to a global thread and starts the heavy-lifting. It needs access to the dataPointer the entire time.- When done, it does a
dispatch_async
to the main thread to tellDataArray
to unlock the array. DataArray
can is accessed by other objects for read only purposes as well, but those other reads super quick.
Code snips from DataArray
-(void)addRawData:(float)data {
//quick sanity check
dispatch_async(addDataQueue, ^{
rawVector.push_back(data);
});
}
- (float*)dataPointer {
[self lock];
return &rawVector[0];
}
- (void)lock {
if (!locked) {
locked = YES;
dispatch_suspend(addDataQueue);
}
}
- (void)unlock {
if (locked) {
dispatch_resume(addDataQueue);
locked = NO;
}
}
Code snip from DataAnalyzer
-(void)analyzeAvailableData {
//do some prep work
const float *rawArray = [self.dataArray dataPointer];
dispatch_async(global_queue, ^{
//lots of analysis
//done
dispatch_async(main_queue, ^{
//tell `Worker` analysis is done
[self.dataArray unlock];
};
};
}