My API computes some data in its own thread:
/*** API is running in its own thread ***/
class API {
public:
std::shared_ptr<Data> retrieveData() { return mData; }
private:
std::shared_ptr<Data> mData;
std::mutex mDataMutex;
void run () {
std::thread t([](){
while (!exitApi) {
mDataMutex.lock();
updateData(mData);
mDataMutex.unlock();
});
t.join();
}
};
An application that uses my API will retrieve the shared data in another thread:
/*** Application is running in another thread ***/
class Application {
private:
Api mApi;
void run () {
std::thread t([](){
while (!exitApp) {
std::shared_ptr<Data> data = mApi.retrieveData();
/* API thread can update the data while the App is using it! */
useData(data);
});
t.join();
}
How can I design my API so that there are no pitfalls for the application-developer when retrieving the data? I can think of three options but do not like any of them:
retrieveData
will also return an already locked std::unique_lock
. Once the application is done with using the data it has to unlock the unique_lock
. This is potentially less prone to error but still not very obvious for the application developer.Are there any better options to design the API (in modern C++11 and beyond) that is as developer-friendly as possible?
TL;DR: Use shared_ptr
with a custom deleter that calls unlock.
It think the two main approaches are:
Returning an immutable data structure so it can be shared between threads. This is makes for a clean API, but (as already mentioned) copying could be expensive. Some approaches to reduce the need for copying would be:
Using locks around a mutable data structure. As is pointed out, this requires the API user to perform extra actions that may not be obvious. But smart pointers can be used to lessen the burden on the consumers:
func(locking_ptr& ptr)
. A simple implementation can be found here: https://stackoverflow.com/a/15876719/1617480.shared_ptr
internal to the locking smart pointer to avoid rolling your own thread-safe reference counting. More simply pass a custom deleter to shared_ptr
that unlocks and deletes (No need to write a smart pointer at all).->
in locks. I don't think this is appropriate for this use case, since it looks the API consumer wants a consistent view of the results. Here's an example of such a pointer: https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Execute-Around_Pointer