How to create thread safe cache of shared_ptr - c++

I have a code that reads a lot of files. Some files can be cached. Consumer receives shared_ptr when asks for a file. Other consumers can ask for this file and get it from the cache if file is still in memory. If file is not in memory, it will be loaded and put in to the cache.
Simplified code:
struct File
{
File(std::string);
bool AllowCache() const;
};
typedef std::shared_ptr<File> SharedPtr;
typedef std::weak_ptr<File> WeakPtr;
std::map<std::string, WeakPtr> Cache;
SharedPtr GetFile(std::wstring Name)
{
auto Found = Cache.find(Name);
if (Found != Cache.end())
if (auto Exist = Found->second.lock())
return Exist;
auto New = boost::make_shared<File>(Name);
if (New->AllowCache())
Cache[Name] = New;
return New;
}
My question is: how to make this code thead safe? Even if I protect content of GetFile() by a mutex, it still can return non-null pointer from weak_ptr::lock() while other thread is running destructor of pointed File object.
I see some solutions, like:
Store shared_ptrs in the cache and run a separate thread that will
continuously remove shared_ptr-s with use_count()==1 (let's call it Cleanup()).
Store shared_ptrs in the cache and require consumers to use special wrapper of shared_ptr<File>. This wrapper will have shared_ptr<File> as a member and will reset() it at destructor and then call Cleanup().
1st solution is a bit ofoverkill. 2nd solution require to refactor all the code in my project. Both solutions are bad for me. Is any other way to make it thead safe?

Unless I'm misunderstanding the scenario you've described, A lock() of a WeakPtr will fail (i.e. return a dummy shared_ptr) if another thread is running a destructor of the File object. That's the logic of shared and weak pointers. So - your current solution should be thread safe in this respect; but - it may be non-thread-safe as you add or remove elemnrs. Read about that, say, in this question: C++ Thread-Safe Map .

I expected the following code will fail. But it's not. It seems like weak_ptr::lock() will not return pointer to an object that is in destruction process. And if so, it is a simpliest solution to just add a mutex and don't worry about returning dead objects by weak_ptr::lock().
char const *TestPath = "file.xml";
void Log(char const *Message, std::string const &Name, void const *File)
{
std::cout << Message
<< ": " << Name
<< " at memory=" << File
<< ", thread=" << std::this_thread::get_id()
<< std::endl;
}
void Sleep(int Seconds)
{
std::this_thread::sleep_for(std::chrono::seconds(Seconds));
}
struct File
{
File(std::string Name) : Name(Name)
{
Log("created", Name, this);
}
~File()
{
Log("destroying", Name, this);
Sleep(5);
Log("destroyed", Name, this);
}
std::string Name;
};
std::map<std::string, std::weak_ptr<File>> Cache;
std::mutex Mutex;
std::shared_ptr<File> GetFile(std::string Name)
{
std::unique_lock<std::mutex> Lock(Mutex); // locking is added
auto Found = Cache.find(Name);
if (Found != Cache.end())
if (auto Exist = Found->second.lock())
{
Log("found in cache", Name, Exist.get());
return Exist;
}
auto New = std::make_shared<File>(Name);
Cache[Name] = New;
return New;
}
void Thread2()
{
auto File = GetFile(TestPath);
//Sleep(3); // uncomment to share file with main thead
}
int main()
{
std::thread thread(&Thread2);
Sleep(1);
auto File = GetFile(TestPath);
thread.join();
return 0;
}
My expectation:
thread2: created
thread2: destroying
thread1: found in cache <--- fail. dead object :(
thread2: destroyed
VS2017 results:
thread2: created
thread2: destroying
thread1: created <--- old object is not re-used! great ;)
thread2: destroyed

Related

Static member function and thread safety

I've a private static method in a class that has all the methods as static. It's a helper class that helps with logging and other stuff. This helper class would be called by multiple threads. I don't understand how the method in question is working safely with multiple threads without a lock.
//Helper.cpp
std::recursive_mutex Logger::logMutex_;
void Logger::write(const std::string &logFilePath, const std::string &formattedLog)
{
std::lock_guard<std::mutex> guard(logMutex_);
std::ofstream logFile(logFilePath.c_str(), std::ios::out | std::ios::app);
if (logFile.is_open())
{
logFile << formattedLog;
logFile.close();
}
}
void Logger::error(const string &appId, const std::string &fmt, ...)
{
auto logFile = validateLogFile(appId); //Also a private static method that validates if a file exists for this app ID.
if (!logFile.empty())
{
//Format the log
write(logFile, log);
}
}
//Helper.h
class Logger
{
public:
static void error(const std::string &Id, const std::string &fmt, ...);
private:
static void write(const std::string &fileName, const std::string &formattedLog);
static std::recursive_mutex logMutex_;
};
I understand that the local variables inside the static methods are purely local. i.e., A stack is created every time these methods are called and the variables are initialized in them. Now, in the Logger::write method I'm opening a file and writing to it. So when multiple threads calls the write method through the Logger::error method(Which is again static) and when there's no lock, I believe I should see some data race/crash.
Because multiple threads are trying to open the same file. Even if the kernel allows to open a file multiple times, I must see some fault in the data written to the file.
I tested this with running up to 100 threads and I see no crash, all the data is being written to the file concurrently. I can't completely understand how this is working. With or without the lock, I see the data is being written to the file perfectly.
TEST_F(GivenALogger, WhenLoggerMethodsAreCalledFromMultipleThreads_AllTheLogsMustBeLogged)
{
std::vector<std::thread> threads;
int num_threads = 100;
int i = 0;
for (; i < num_threads / 2; i++)
{
threads.push_back(std::thread(&Logger::error, validId, "Trial %d", i));
}
for (; i < num_threads; i++)
{
threads.push_back(std::thread(&Logger::debug, validId, "Trial %d", i));
}
std::for_each(threads.begin(), threads.end(), [](std::thread &t) { t.join(); });
auto actualLog = getActualLog(); // Returns a vector of log lines.
EXPECT_EQ(num_threads, actualLog.size());
}
Also, how should I properly/safely access the file?
The key is this line:
std::lock_guard<std::mutex> guard(logMutex_);
The std::lock_guard<std::mutex> will lock the mutex logMutex_ in the constructor and will unlock the mutex in the destructor when it goes out of scope, ie when the method returns.
If another thread attempts to write while the first thread is within the guard scope, the new (local) guard will try to lock the logMutex_ and that thread will be put to sleep until the lock is released.

About shared_mutex and shared_ptr across multiple threads

I implemented code such that multiple instances running on different threads reads other instances' data using reader-writer lock and shared_ptr. It seemed fine, but I am not 100% sure about that and I came up with some questions about usage of those.
Detail
I have multiple instances of a class called Chunk and each instance does some calculations in a dedicated thread. A chunk needs to read neighbour chunks' data as well as its own data, but it doesn't write neighbours' data, so reader-writer lock is used. Also, neighbours can be set at runtime. For example, I might want o set a different neighbour chunk at runtime, sometimes just nullptr. It is possible to delete a chunk at runtime, too. Raw pointers can be used but I thought shared_ptr and weak_ptr are better for this, in order to keep track of the lifetime. Own data in shared_ptr and neighbours' data in weak_ptr.
I provided a simpler version of my code below. ChunkData has data and a mutex for it. I use InitData for data initialization and DoWork function is called in a dedicated thread after that. other functions can be called from main thread.
This seems to work, but I am not so confident. Especially, about use of shared_ptr across multiple threads.
What happens if a thread calls shared_ptr's reset() (in ctor and InitData) and other uses it with weak_ptr's lock (in DoWork)? Does this need a lock dataMutex or chunkMutex?
How about copy(in SetNeighbour)? Do I need locks for this as well?
I think other parts are ok, but please let me know if you find anything dangerous. Appreciate that.
By the way, I considered about storing shared_ptr of Chunk instead of ChunkData, but decided not to use this method because internal code, which I don't manage, has GC system and it can delete a pointer to Chunk when I don't expect it.
class Chunk
{
public:
class ChunkData
{
public:
shared_mutex dataMutex; // mutex to read/write data
int* data;
int size;
ChunkData() : data(nullptr) { }
~ChunkData()
{
if (data)
{
delete[] data;
data = nullptr;
}
}
};
private:
mutex chunkMutex; // mutex to read/write member variables
shared_ptr<ChunkData> chunkData;
weak_ptr<ChunkData> neighbourChunkData;
string result;
public:
Chunk(string _name)
: chunkData(make_shared<ChunkData>())
{
}
~Chunk()
{
EndProcess();
unique_lock lock(chunkMutex); // is this needed?
chunkData.reset();
}
void InitData(int size)
{
ChunkData* NewData = new ChunkData();
NewData->size = size;
NewData->data = new int[size];
{
unique_lock lock(chunkMutex); // is this needed?
chunkData.reset(NewData);
cout << "init chunk " << name << endl;
}
}
// This is executed in other thread. e.g. thread t(&Chunk::DoWork, this);
void DoWork()
{
lock_guard lock(chunkMutex); // we modify some members such as result(string) reading chunk data, so need this.
if (chunkData)
{
shared_lock readLock(chunkData->dataMutex);
if (chunkData->data)
{
// read chunkData->data[i] and modify some members such as result(string)
for (int i = 0; i < chunkData->size; ++i)
{
// Is this fine, or should I write data result outside of readLock scope?
result += to_string(chunkData->data[i]) + " ";
}
}
}
// does this work?
if (shared_ptr<ChunkData> neighbour = neighbourChunkData.lock())
{
shared_lock readLock(neighbour->dataMutex);
if (neighbour->data)
{
// read neighbour->data[i] and modify some members as above
}
}
}
shared_ptr<ChunkData> GetChunkData()
{
unique_lock lock(chunkMutex);
return chunkData;
}
void SetNeighbour(Chunk* neighbourChunk)
{
if (neighbourChunk)
{
// safe?
shared_ptr<ChunkData> newNeighbourData = neighbourChunk->GetChunkData();
unique_lock lock(chunkMutex); // lock for chunk properties
{
shared_lock readLock(newNeighbourData->dataMutex); // not sure if this is needed.
neighbourChunkData = newNeighbourData;
}
}
}
int GetDataAt(int index)
{
shared_lock readLock(chunkData->dataMutex);
if (chunkData->data && 0 <= index && index < chunkData->size)
{
return chunkData->data[index];
}
return 0;
}
void SetDataAt(int index, int element)
{
unique_lock writeLock(chunkData->dataMutex);
if (chunkData->data && 0 <= index && index < chunkData->size)
{
chunkData->data[index] = element;
}
}
};
Edit 1
I added more detail for DoWork function. Chunk data is read and chunk's member variables are edited in the function.
After Homer512's anwer, I came up with other questions.
A) In DoWork function I write a member variable inside a read lock. Should I only read data in a read lock scope and if I need to modify other data based on read data, do I have to do outside of the read lock? For example, copy the whole array to a local variable in a read lock, and modify other members outside of the read lock using the local.
B) I followed Homer512 and modifed GetDataAt/SetDataAt as below. I do read/write lock chunkData->dataMutex before unlocking chunkMutex. I also do this in DoWork function. Should I instead do locks separately? For example, make a local variable shared_ptr and set chunkData to it in a chunkMutex lock, unlock it, then lastly read/write lock that local variable's dataMutex and read/write data.
int GetDataAt(int index)
{
lock_guard chunkLock(chunkMutex);
shared_lock readLock(chunkData->dataMutex);
if (chunkData->data && 0 <= index && index < chunkData->size)
{
return chunkData->data[index];
}
return 0;
}
void SetDataAt(int index, int element)
{
lock_guard chunkLock(chunkMutex);
unique_lock writeLock(chunkData->dataMutex);
if (chunkData->data && 0 <= index && index < chunkData->size)
{
chunkData->data[index] = element;
}
}
I have several remarks:
~ChunkData: You could change your data member from int* to unique_ptr<int[]> to get the same result without an explicit destructor. Your code is correct though, just less convenient.
~Chunk: I don't think you need a lock or call the reset method. By the time the destructor runs, by definition, no one should have a reference to the Chunk object. So the lock can never be contested. And reset is unnecessary because the shared_ptr destructor will handle that.
InitData: Yes, the lock is needed because InitData can race with DoWork. You could avoid this by moving InitData to the constructor but I assume there are reasons for this division. You could also change the shared_ptr to std::atomic<std::shared_ptr<ChunkData> > to avoid the lock.
It is more efficient to write InitData like this:
void InitData(int size)
{
std::shared_ptr<ChunkData> NewData = std::make_shared<ChunkData>();
NewData->size = size;
NewData->data = new int[size]; // or std::make_unique<int[]>(size)
{
std::lock_guard<std::mutex> lock(chunkMutex);
chunkData.swap(NewData);
}
// deletes old chunkData outside locked region if it was initialized before
}
make_shared avoids an additional memory allocation for the reference counter. This also moves all allocations and deallocations out of the critical section.
DoWork: Your comment "ready chunkData->data[i] and modify some members". You only take a shared_lock but say that you modify members. Well, which is it, reading or writing? Or do you mean to say that you modify Chunk but not ChunkData, with Chunk being protected by its own mutex?
SetNeighbour: You need to lock both your own chunkMutex and the neighbour's. You should not lock both at the same time to avoid the dining philosopher's problem (though std::lock solves this).
void SetNeighbour(Chunk* neighbourChunk)
{
if(! neighbourChunk)
return;
std::shared_ptr<ChunkData> newNeighbourData;
{
std::lock_guard<std::mutex> lock(neighbourChunk->chunkMutex);
newNeighbourData = neighbourChunk->chunkData;
}
std::lock_guard<std::mutex> lock(this->chunkMutex);
this->neighbourChunkData = newNeighbourData;
}
GetDataAt and SetDataAt: You need to lock chunkMutex. Otherwise you might race with InitData. There is no need to use std::lock because the order of locks is never swapped around.
EDIT 1:
DoWork: The line if (shared_ptr<ChunkData> neighbour = neighbourChunkData.lock()) doesn't keep the neighbur alive. Move the variable declaration out of the if to keep the reference.
EDIT: Alternative design proposal
What I'm bothered with is that your DoWork may be unable to proceed if InitData is still running or waiting to run. How do you want to deal with this? I suggest you make it possible to wait until the work can be done. Something like this:
class Chunk
{
std::mutex chunkMutex;
std::shared_ptr<ChunkData> chunkData;
std::weak_ptr<ChunkData> neighbourChunkData;
std::condition_variable chunkSet;
void waitForChunk(std::unique_lock<std::mutex>& lock)
{
while(! chunkData)
chunkSet.wait(lock);
}
public:
// modified version of my code above
void InitData(int size)
{
std::shared_ptr<ChunkData> NewData = std::make_shared<ChunkData>();
NewData->size = size;
NewData->data = new int[size]; // or std::make_unique<int[]>(size)
{
std::lock_guard<std::mutex> lock(chunkMutex);
chunkData.swap(NewData);
}
chunkSet.notify_all();
}
void DoWork()
{
std::unique_lock<std::mutex> ownLock(chunkMutex);
waitForChunk(lock); // blocks until other thread finishes InitData
{
shared_lock readLock(chunkData->dataMutex);
...
}
shared_ptr<ChunkData> neighbour = neighbourChunkData.lock();
if(! neighbour)
return;
shared_lock readLock(neighbour->dataMutex);
...
}
void SetNeighbour(Chunk* neighbourChunk)
{
if(! neighbourChunk)
return;
shared_ptr<ChunkData> newNeighbourData;
{
std::unique_lock<std::mutex> lock(neighbourChunk->chunkMutex);
neighbourChunk->waitForChunk(lock); // wait until neighbor has finished InitData
newNeighbourData = neighbourChunk->chunkData;
}
std::lock_guard<std::mutex> ownLock(this->chunkMutex);
this->neighbourChunkData = std::move(newNeighbourData);
}
};
The downside to this is that you could deadlock if InitData is never called or if it failed with an exception. There are ways around this, like using an std::shared_future which knows that it is valid (set when InitData is scheduled) and whether it failed (records exception of associated promise or packaged_task).

Is there a way to protect a smart pointer from being deallocated on one thread, when work is being done on another thread?

In our program, we have a class FooLogger which logs specific events (strings). We use the FooLogger as a unique_ptr.
We have two threads which use this unique_ptr instance:
Thread 1 logs the latest event to file in a while loop, first checking if the instance is not nullptr
Thread 2 deallocates the FooLogger unique_ptr instance when the program has reached a certain point (set to nullptr)
However, due to bad interleaving, it is possible that, while logging, the member variables of FooLogger are deallocated, resulting in an EXC_BAD_ACCESS error.
class FooLogger {
public:
FooLogger() {};
void Log(const std::string& event="") {
const float32_t time_step_s = timer_.Elapsed() - runtime_s_; // Can get EXC_BAD_ACCESS on timer_
runtime_s_ += time_step_s;
std::cout << time_step_s << runtime_s_ << event << std::endl;
}
private:
Timer timer_; // Timer is a custom class
float32_t runtime_s_ = 0.0;
};
int main() {
auto foo_logger = std::make_unique<FooLogger>();
std::thread foo_logger_thread([&] {
while(true) {
if (foo_logger)
foo_logger->Log("some event");
else
break;
}
});
SleepMs(50); // pseudo code
foo_logger = nullptr;
foo_logger_thread.join();
}
Is it possible, using some sort of thread synchronisation/locks etc. to ensure that the foo_logger instance is not deallocated while logging? If not, are there any good ways of handling this case?
The purpose of std::unique_ptr is to deallocate the instance once std::unique_ptr is out of scope. In your case, you have multiple threads each having access to the element and the owning thread might get eliminated prior to other users.
You either need to ensure that owner thread never gets deleted prior to the user threads or change ownership model from std::unique_ptr to std::shared_ptr. It is the whole purpose of std::shared_ptr to ensure that the object is alive as long as you use it.
You just need to figure out what's required for program and use the right tools to achieve it.
Use a different mechanism than the disappearance of an object for determining when to stop.
(When you use a single thing for two separate purposes, you often get into trouble.)
For instance, an atomic bool:
int main() {
FooLogger foo_logger;
std::atomic<bool> keep_going = true;
std::thread foo_logger_thread([&] {
while(keep_going) {
foo_logger.Log("some event");
}
});
SleepMs(50);
keep_going = false;
foo_logger_thread.join();
}
It sounds like std::weak_ptr can help in this case.
You can make one from a std::shared_ptr and pass it to the logger thread.
For example:
class FooLogger {
public:
void Log(std::string const& event) {
// log the event ...
}
};
int main() {
auto shared_logger = std::make_shared<FooLogger>();
std::thread foo_logger_thread([w_logger = std::weak_ptr(shared_logger)]{
while (true) {
auto logger = w_logger.lock();
if (logger)
logger->Log("some event");
else
break;
}
});
// some work ...
shared_logger.reset();
foo_logger_thread.join();
}
Use should use make_shared instead of make_unique. And change:
std::thread foo_logger_thread([&] {
to
std::thread foo_logger_thread([foo_logger] {
It will create new instance of shared_ptr.

pending std::future get of std::async with shared_from_this argument blocks destruction of this

I wanted to create a class that would represent a task that can be started running asynchronously and will run continuously (effectively in a detached thread) until a stop signal is received. The usage for the sake of this question would look like this:
auto task = std::make_shared<Task>();
task->start(); // starts the task running asynchronously
... after some time passes ...
task->stop(); // signals to stop the task
task->future.get(); // waits for task to stop running and return its result
However, a key feature of this Task class is that I cannot guarantee that the future will be waited/got... i.e. the last line may not get called before the shared pointer is destroyed.
A stripped-down toy version of the class I wrote is as follows (please ignore that everything is in public, this is just for this example's simplicity):
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
~MyClass() { std::cout << "Destructor called" << std::endl; }
void start() {
future = std::async(std::launch::async, &MyClass::method, this->shared_from_this());
}
void stop() { m_stop = true; }
void method() {
std::cout << "running" << std::endl;
do {
std::this_thread::sleep_for(std::chrono::seconds(1));
} while(m_stop == false);
std::cout << "stopped" << std::endl;
return;
}
std::future<void> future;
std::atomic<bool> m_stop = false;
};
However, I discovered an undesirable feature of this code: if instead of get on the future, I just wait (e.g. if I don't care about the result of method, which in this case is a void anyway), then when task is deleted, the instance doesn't get destroyed.
I.e. doing task->future.get() gives:
running
stopped
Destructor called
But task->future.wait() gives:
running
stopped
From reading answer to What is the lifetime of the arguments of std::async? I believe the problem here is the this->shared_from_this() argument to std::async won't be destroyed until the future from the async has been made invalid (through get or destruction or otherwise). So this shared_ptr is keeping the class instance alive.
Solution Attempt 1:
Replace the line in start with:
future = std::async(std::launch::async, [this]() {
return this->shared_from_this()->method();
});
This ensures shared_ptr it creates is destroyed when the method completes, but I have been worried that there's nothing to stop this being destroyed between the time of it being captured by the lambda capture (which happens at this line, correct?) and the time the lambda is executed in the new thread. Is this a real possibility?
Solution Attempt 2:
To protect the this (task) being destroyed before the lambda function runs, I add another member variable std::shared_ptr<MyClass> myself then my start method can look like this:
myself = this->shared_from_this();
future = std::async(std::launch::async, [this]() {
auto my_ptr = std::move(this->myself);
return myself->method();
});
Here the idea is that myself will ensure that if I delete the task shared_ptr, I don't destroy the class. Then inside the lambda, the shared_ptr is transferred to the local my_ptr variable, which is destroyed on exit.
Are there issues with this solution, or have I overlooked a much cleaner way of achieving the sort functionality I'm after?
Thanks!
Solution attempt 2 I found in some scenarios would generate a deadlock exception. This appears to come from the async thread simultaneously trying to destroy the future (by destroying the instance of the class) while also trying to set the value of the future.
Solution attempt 3 - this seems to pass all my tests so far:
myself = this->shared_from_this();
std::promise<void> p;
future = p.get_future();
std::thread([this](std::promise<void>&& p) {
p.set_value_at_thread_exit( myself->method() );
myself.reset();
}, std::move(p)).detach();
The logic here is that it is safe to destroy myself (by resetting the shared pointer) once the method call is finished - its safe to delete the future of a promise before the promise has set its value. No deadlock occurs because the future is destroyed before the promise tries to transfer a value.
Any comments on this solution or potentially neater alternatives would be welcome. In particular it would be good to know if there are issues I've overlooked.
I would suggest one of the following solutions:
Solution 1, Use std::async with this instead of shared_from_this:
class MyClass /*: public std::enable_shared_from_this<MyClass> not needed here */ {
public:
~MyClass() { std::cout << "Destructor called" << std::endl; }
void start() {
future = std::async(std::launch::async, &MyClass::method, this);
}
void stop() { m_stop = true; }
void method() {
std::cout << "running" << std::endl;
do {
std::this_thread::sleep_for(std::chrono::seconds(1));
} while(m_stop == false);
std::cout << "stopped" << std::endl;
return;
}
std::atomic<bool> m_stop = false;
std::future<void> future; // IMPORTANT: future constructed last, destroyed first
};
This solution would work even if not calling wait or get on the future because the destructor of a future returned by std::async blocks until the termination of the task. It is important to construct the future last, so that it is destroyed (and thus blocks) before all other members are destroyed. If this is too risky, use solution 3 instead.
Solution 2, Use a detached thread like you did:
void start() {
std::promise<void> p;
future = p.get_future();
std::thread(
[m = this->shared_from_this()](std::promise<void>&& p) {
m->method();
p.set_value();
},
std::move(p))
.detach();
}
One drawback of this solution: If you have many instances of MyClass you will create a lot of threads maybe resulting in contention. So a better option would be to use a thread pool instead of a single thread per object.
Solution 3, Separate the executable from the task class e.g:
class ExeClass {
public:
~ExeClass() { std::cout << "Destructor of ExeClass" << std::endl; }
void method() {
std::cout << "running" << std::endl;
do {
std::this_thread::sleep_for(std::chrono::seconds(1));
} while (m_stop == false);
std::cout << "stopped" << std::endl;
return;
}
std::atomic<bool> m_stop = false;
};
class MyClass {
public:
~MyClass() { std::cout << "Destructor of MyClass" << std::endl; }
void start() {
future = std::async(std::launch::async, &ExeClass::method, exe);
}
void stop() { exe->m_stop = true; }
std::shared_ptr<ExeClass> exe = std::make_shared<ExeClass>();
std::future<void> future;
};
Like solution 1 this would block when the future is destroyed, however you don't need to take care of the order of construction and destruction. IMO this is the cleanest option.

Async constructor in C++11

Sometimes I need to create objects whose constructors take very long time to execute.
This leads to responsiveness problems in UI applications.
So I was wondering if it could be sensible to write a constructor designed to be called asynchronously, by passing a callback to it which will alert me when the object is available.
Below is a sample code:
class C
{
public:
// Standard ctor
C()
{
init();
}
// Designed for async ctor
C(std::function<void(void)> callback)
{
init();
callback();
}
private:
void init() // Should be replaced by delegating costructor (not yet supported by my compiler)
{
std::chrono::seconds s(2);
std::this_thread::sleep_for(s);
std::cout << "Object created" << std::endl;
}
};
int main(int argc, char* argv[])
{
auto msgQueue = std::queue<char>();
std::mutex m;
std::condition_variable cv;
auto notified = false;
// Some parallel task
auto f = []()
{
return 42;
};
// Callback to be called when the ctor ends
auto callback = [&m,&cv,&notified,&msgQueue]()
{
std::cout << "The object you were waiting for is now available" << std::endl;
// Notify that the ctor has ended
std::unique_lock<std::mutex> _(m);
msgQueue.push('x');
notified = true;
cv.notify_one();
};
// Start first task
auto ans = std::async(std::launch::async, f);
// Start second task (ctor)
std::async(std::launch::async, [&callback](){ auto c = C(callback); });
std::cout << "The answer is " << ans.get() << std::endl;
// Mimic typical UI message queue
auto done = false;
while(!done)
{
std::unique_lock<std::mutex> lock(m);
while(!notified)
{
cv.wait(lock);
}
while(!msgQueue.empty())
{
auto msg = msgQueue.front();
msgQueue.pop();
if(msg == 'x')
{
done = true;
}
}
}
std::cout << "Press a key to exit..." << std::endl;
getchar();
return 0;
}
Do you see any drawback in this design? Or do you know if there is a better approach?
EDIT
Following the hints of JoergB's answer, I tried to write a factory which will bear the responsibility to create an object in a sync or async way:
template <typename T, typename... Args>
class FutureFactory
{
public:
typedef std::unique_ptr<T> pT;
typedef std::future<pT> future_pT;
typedef std::function<void(pT)> callback_pT;
public:
static pT create_sync(Args... params)
{
return pT(new T(params...));
}
static future_pT create_async_byFuture(Args... params)
{
return std::async(std::launch::async, &FutureFactory<T, Args...>::create_sync, params...);
}
static void create_async_byCallback(callback_pT cb, Args... params)
{
std::async(std::launch::async, &FutureFactory<T, Args...>::manage_async_byCallback, cb, params...);
}
private:
FutureFactory(){}
static void manage_async_byCallback(callback_pT cb, Args... params)
{
auto ptr = FutureFactory<T, Args...>::create_sync(params...);
cb(std::move(ptr));
}
};
Your design seems very intrusive. I don't see a reason why the class would have to be aware of the callback.
Something like:
future<unique_ptr<C>> constructedObject = async(launchopt, [&callback]() {
unique_ptr<C> obj(new C());
callback();
return C;
})
or simply
future<unique_ptr<C>> constructedObject = async(launchopt, [&cv]() {
unique_ptr<C> ptr(new C());
cv.notify_all(); // or _one();
return ptr;
})
or just (without a future but a callback taking an argument):
async(launchopt, [&callback]() {
unique_ptr<C> ptr(new C());
callback(ptr);
})
should do just as well, shouldn't it? These also make sure that the callback is only ever called when a complete object is constructed (when deriving from C).
It shouldn't be too much effort to make any of these into a generic async_construct template.
Encapsulate your problem. Don't think about asynchronous constructors, just asynchronous methods which encapsulate your object creation.
It looks like you should be using std::future rather than constructing a message queue. std::future is a template class that holds a value and can retrieve the value blocking, timeout or polling:
std::future<int> fut = ans;
fut.wait();
auto result = fut.get();
I will suggest a hack using thread and signal handler.
1) Spawn a thread to do the task of the constructor. Lets call it child thread. This thread will intialise the values in your class.
2) After the constructor is completed, child thread uses the kill system call to send a signal to the parent thread. (Hint : SIGUSR1). The main thread on receiving the ASYNCHRONOUS handler call will know that the required object has been created.
Ofcourse, you can use fields like object-id to differentiate between multiple objects in creation.
My advice...
Think carefully about why you need to do such a long operation in a constructor.
I find often it is better to split the creation of an object into three parts
a) allocation
b) construction
c) initialization
For small objects it makes sense to do all three in one "new" operation. However, heavy weight objects, you really want to separate the stages. Figure out how much resource you need and allocate it. Construct the object in the memory into a valid, but empty state.
Then... do your long load operation into the already valid, but empty object.
I think I got this pattern a long time ago from reading a book (Scott Myers perhaps?) but I highly recommend it, it solves all sorts of problems. For example, if your object is a graphic object, you figure out how much memory it needs. If it fails, show the user an error as soon as possible. If not mark the object as not read yet. Then you can show it on screen, the user can also manipulate it, etc.
Initialize the object with an asynchronous file load, when it completes, set a flag in the object that says "loaded". When your update function sees it is loaded, it can draw the graphic.
It also REALLY helps with problems like construction order, where object A needs object B. You suddenly find you need to make A before B, oh no!! Simple, make an empty B, and pass it as a reference, as long as A is clever enough to know that be is empty, and wait to it is not before it uses it, all is well.
And... Not forgetting.. You can do the opposite on destruction.
Mark your object as empty first, so nothing new uses it (de-initialisation)
Free the resources, (destruction)
Then free the memory (deallocation)
The same benefits apply.
Having partially initialized objects could lead to bugs or unnecessarily complicated code, since you would have to check whether they're initialized or not.
I'd recommend using separate threads for UI and processing, and then use message queues for communicating between threads. Leave the UI thread for just handling the UI, which will then be more responsive all the time.
Place a message requesting creation of the object into the queue that the worker thread waits on, and then after the object has been created, the worker can put a message into UI queue indicating that the object is now ready.
Here's yet another pattern for consideration. It takes advantage of the fact that calling wait() on a future<> does not invalidate it. So, as long you never call get(), you're safe. This pattern's trade-off is that you incur the onerous overhead of calling wait() whenever a member function gets called.
class C
{
future<void> ready_;
public:
C()
{
ready_ = async([this]
{
this_thread::sleep_for(chrono::seconds(3));
cout << "I'm ready now." << endl;
});
}
// Every member function must start with ready_.wait(), even the destructor.
~C(){ ready_.wait(); }
void foo()
{
ready_.wait();
cout << __FUNCTION__ << endl;
}
};
int main()
{
C c;
c.foo();
return 0;
}