A thread holds a shared_ptr to a const map object. This shared_ptr is occasionally updated by another thread. This shared_ptr could also be read by different threads. Code snippet:
class SharedPointerHolder {
private:
shared_ptr<const map<int, string>> sptr_;
public:
SharedPointerHolder() : sptr_(make_shared<const map<int, string>>()) {};
// new_sptr is guaranteed to be a valid pointer.
void UpdateSharedPointer(shared_ptr<const map<int, string>>&& new_sptr) {
if (!new_sptr) { return; }
sptr_ = move(new_sptr);
}
void ReadSharedPointer() {
// Since only UpdateSharedPointer() updates sptr_, it is guaranteed that sptr_ will always be valid. Still just in case, I have added this "if".
if (!sptr_) {
return;
}
local_sptr = sptr_;
// Read the map object pointed by the local_sptr and perform some operations.
}
}
UpdateSharedPointer()
and ReadSharedPointer()
can be called by different threads, but UpdateSharedPointer()
will always be called after a certain time period, which ensures that the shared_ptr is not updated by two different threads at the same time.
My question is:
Does the sptr_
need to be protected by a lock?
From what I read, a lock is required to protect the data pointed by the shared_ptr, but not for reading and updating the shared_ptr. Since in my case, the data is always const, I don't think a lock is required. It is possible that the private sptr_ is updated while we are in ReadSharedPointer()
, but I am not concerned about that situation since I am fine working on a stale data.
Let's say if UpdateSharedPointer()
is not necessarily invoked after certain time period and multiple threads might be contending to update the sptr_
at the same time. Would I require a lock then? I understand that this might depend on whether my program can tolerate reading stale data, but is there any other race condition that I should be aware of?
Use case: I am fine reading a stale map that was pointed by sptr_, in case sptr_ is updated by another thread. The most important thing that I am worried about is data corruption.
Thanks!
CodePudding user response:
UpdateSharedPointer()
will always be called after a certain time period, which ensures that theshared_ptr
is not updated by two different threads at the same time. My question is:Does the
sptr_
need to be protected by a lock?
Even if only 1 thread ever updates what the shared_ptr
is pointing at, if there is ever a possibility that other threads may read the shared_ptr
's pointer at the same time that a thread is updating the pointer, then you MUST protect the shared_ptr
with a lock. Period. This is no different than any other kind of resource shared over thread boundaries.
Let's say if
UpdateSharedPointer()
is not necessarily invoked after certain time period and multiple threads might be contending to update thesptr_
at the same time. Would I require a lock then?
Yes, absolutely.
CodePudding user response:
Yes, access to sptr_
needs to be synchronized.
Only modification of the shared_ptr
's control block is atomic. Modification of the shared_ptr
itself is not. If a single shared_ptr
object is shared across threads then it will generally need some form of synchronization.
Consider this logical diagram of multiple shared_ptr
s sharing ownership of a single object:
shared_ptr p1 shared_ptr p2 shared_ptr p3
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ │ │ │ │ │
│ control_block_ptr │ │ control_block_ptr │ │ control_block_ptr │
│ │ │ │ │ │
└─────────────┬─────┘ └─┬─────────────────┘ └┬──────────────────┘
│ ┌────────┘ │
│ │ ┌────────────────────────────┘
control block ▼ ▼ ▼ owned object
┌──────────────────┐ ┌────────────────────────┐
│ │ │ │
│ object_ptr ├──────────►│ │
│ │ │ │
├──────────────────┤ └────────────────────────┘
│ │
│ strong_ref_count │
│ │
├──────────────────┤
│ │
│ weak_ref_count │
│ │
└──────────────────┘
If p3
is assigned to, then its control_block_ptr
gets overwritten to point to the control block of the shared_ptr
on the right-hand side of the assignment. This is a member of the shared_ptr
itself, so this modification is not thread-safe. If any other thread is accessing p3
at the same time you will have a data race, and the behavior of your program becomes undefined.
On the other hand, if you make a copy of p2
in thread A at the same time you assign to p3
in thread B, then that will be safe. Both will need to modify the strong_ref_count
in their shared control block (the copy of p2
needs to increment it, and p3
needs to decrement it), but that ref count modification will be done atomically. You are guaranteed to get the correct result and no data race will occur.