简体   繁体   中英

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:

  1. Thread 1 logs the latest event to file in a while loop, first checking if the instance is not nullptr
  2. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM