Home > Back-end >  Shared pointer to a const object thread safety
Shared pointer to a const object thread safety

Time:08-16

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 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?

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 the sptr_ 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_ptrs 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.

  • Related