Home > Net >  Why shared_ptr object works well without lock in such case?
Why shared_ptr object works well without lock in such case?

Time:11-22

One reader thread & multi writer threads concurrently access shared_ptr object and it works well, code as below (BUT, if i modify the write line code from "=" to "reset", it will coredump while reading) :

shared_ptr.reset means coredump and "operator =" means works well ? ( I tried more than 100 times)

bool start = false;
std::mutex mtx;
std::condition_variable cond;
std::shared_ptr<std::string> string_ptr(new std::string("hello"));

int main() {
  auto read = []() {
    {
      std::cout << "readddd" << std::endl;
      std::unique_lock<std::mutex> lck(mtx);
      while (!start) {
        cond.wait(lck);
      }
    }
    for (int i = 0; i < 100000;   i) {
      std::cout << *string_ptr.get() << std::endl;
    }
  };

  auto write = []() {
    {
      std::unique_lock<std::mutex> lck(mtx);
      while (!start) {
        cond.wait(lck);
      }
    }
    for (int i = 0; i < 100000;   i) {
      string_ptr = std::make_shared<std::string>(std::to_string(i));
      // string_ptr.reset(new std::string(std::to_string(i))); // will coredump
    }
  };

  std::thread w(write);
  std::thread rthreads[20];

  for (int i = 0; i < 20;   i) {
    rthreads[i] = std::thread(read);
  }

  {
    std::unique_lock<std::mutex> lck(mtx);
    start = true;
    cond.notify_all();
  }

  w.join();
  for (auto& t : rthreads) {
    t.join();
  }

  return 0;
}

coredumpe stack will be :

#0 0x00007fee5fca3113 in std::basic_ostream<char, std::char_traits >& std::operator<< <char, std::char_traits, std::allocator >(std::basic_ostream<char, std::char_traits >&, std::basic_string<char, std::char_traits, std::allocator > const&) () from /lib64/libstdc .so.6 #1 0x00000000004039f0 in <lambda()>::operator()(void) const (__closure=0xa54f98) at test_cast.cpp:395

line "std::cout << *string_ptr.get() << std::endl;"

CodePudding user response:

Both versions suffer from a race condition. std::shared_ptr is not magically thread-safe. If you want to share it between threads, you have to protect it with a mutex. Just that you tried it a few times is no proof: It could be that it's just very unlikely to cause errors or even that the error is impossible on your compiler/OS/CPU combination, but that is not a guarantee.

If you want to see it break more often, insert switches to the thread context into the code. The simplest way is to sleep for some time, at which point the OS schedules a different thread:

    for (int i = 0; i < 100000;   i) {
      std::string* tmp = string_ptr.get();
      std::this_thread::yield();
      std::cout << *tmp << std::endl;
    }

Note that this code enforces the context switch, but the same switch could happen at the same point spontaneously when the thread's timeslice is exceeded.

As for the why one is more likely to fail than the other, step through the instructions executed by shared_ptr's assignment operator and reset() memberfunction. Their implementation may have larger or smaller critical section, which controls how often the error shows itself.

  • Related