It is a follow up question of Shared_ptr and Memory Visibility in c++ and Create object in thread A, use in thread B. Mutex required?.
This question is more about memory visibility rather than data race.
In Java, I have:
ExecutorService executor = Executors.newSingleThreadExecutor();
Integer i = new Integer(5); // no write to i afterwards
executor.submit(() -> {
System.out.println(i);
});
I don't think this is thread-safe. Because there is no need to put value 5 in the main memory, it could stay in the main thread's CPU cache. Since there is no memory barrier, the executor thread is not guaranteed to see the value 5. To make sure the value is in the main memory, you either use synchronization, or use AtomicInteger
, or volatile int.
If you do something similar with shared_ptr
in C++, is it safe?
auto sp = std::make_shared<int>(5); // no write to the int afterwards
myExecutor.submit([sp](){
std::cout << sp;
});
Is the executor thread guaranteed to see the value 5? Note that the shared_ptr
is copied to the lambda, not the int.
Here is a more complete example:
Suppose I have a main thread and worker thread. In main thread I've constructed a shared_ptr<Object>
and copy the shared_ptr
to the worker thread, is this safe to use the copy of the shared_ptr
if there is no synchronization in Object class at all (NO write to the object after construction)?
My main puzzle is, the Object is constructed in main thread on the heap, the shared_ptr
is copied but not the Object. Will the worker thread definitely have the memory visibility of the Object? Would it be possible that the value of Object is actually in main thread's CPU cache and not in the main memory?
struct WorkingQueue{
WorkingQueue()=default;
void push(std::function<void()> task){
std::lock_guard<std::mutex> lock{mutex};
queue.push(std::move(task));
}
std::optional<std::function<void()>> popIfNotEmpty(){
std::lock_guard<std::mutex> lock{mutex};
if(queue.empty()){
return std::nullopt;
}
auto task = queue.front();
queue.pop();
return task;
}
bool empty(){
std::lock_guard<std::mutex> lock{mutex};
return queue.empty();
}
mutable std::mutex mutex;
std::queue<std::function<void()>> queue;
};
int main(){
WorkingQueue queue;
std::atomic<bool> stopFlag{false};
auto f = std::async(std::launch::async, [&queue, &stopFlag](){
while(!stopFlag || !queue.empty()){
auto task = queue.popIfNotEmpty();
if(task){
(*task)();
}
}
});
auto sp = std::make_shared<int>(5);
queue.push([sp](){
std::cout << *sp;
});
stopFlag = true;
f.get();
}
Is this programmer guaranteed to output 5?